at master 9.5 kB view raw
1# Tests for the Python interpreters, package sets and environments. 2# 3# Each Python interpreter has a `passthru.tests` which is the attribute set 4# returned by this function. For example, for Python 3 the tests are run with 5# 6# $ nix-build -A python3.tests 7# 8{ 9 stdenv, 10 python, 11 runCommand, 12 lib, 13 callPackage, 14 pkgs, 15}: 16 17let 18 # Test whether the interpreter behaves in the different types of environments 19 # we aim to support. 20 environmentTests = 21 let 22 environments = 23 let 24 inherit python; 25 pythonEnv = python.withPackages (ps: with ps; [ ]); 26 pythonVirtualEnv = 27 if python.isPy3k then 28 python.withPackages (ps: with ps; [ virtualenv ]) 29 else 30 python.buildEnv.override { 31 extraLibs = with python.pkgs; [ virtualenv ]; 32 # Collisions because of namespaces __init__.py 33 ignoreCollisions = true; 34 }; 35 in 36 { 37 # Plain Python interpreter 38 plain = rec { 39 environment = python; 40 interpreter = environment.interpreter; 41 is_venv = "False"; 42 is_nixenv = "False"; 43 is_virtualenv = "False"; 44 }; 45 } 46 // lib.optionalAttrs (!python.isPyPy && !stdenv.hostPlatform.isDarwin) { 47 # Use virtualenv from a Nix env. 48 # Fails on darwin with 49 # virtualenv: error: argument dest: the destination . is not write-able at /nix/store 50 nixenv-virtualenv = rec { 51 environment = runCommand "${python.name}-virtualenv" { } '' 52 ${pythonVirtualEnv.interpreter} -m virtualenv venv 53 mv venv $out 54 ''; 55 interpreter = "${environment}/bin/${python.executable}"; 56 is_venv = "False"; 57 is_nixenv = "True"; 58 is_virtualenv = "True"; 59 }; 60 } 61 // lib.optionalAttrs (python.implementation != "graal") { 62 # Python Nix environment (python.buildEnv) 63 nixenv = rec { 64 environment = pythonEnv; 65 interpreter = environment.interpreter; 66 is_venv = "False"; 67 is_nixenv = "True"; 68 is_virtualenv = "False"; 69 }; 70 } 71 // lib.optionalAttrs (python.isPy3k && (!python.isPyPy)) { 72 # Venv built using plain Python 73 # Python 2 does not support venv 74 # TODO: PyPy executable name is incorrect, it should be pypy-c or pypy-3c instead of pypy and pypy3. 75 plain-venv = rec { 76 environment = runCommand "${python.name}-venv" { } '' 77 ${python.interpreter} -m venv $out 78 ''; 79 interpreter = "${environment}/bin/${python.executable}"; 80 is_venv = "True"; 81 is_nixenv = "False"; 82 is_virtualenv = "False"; 83 }; 84 85 } 86 // { 87 # Venv built using Python Nix environment (python.buildEnv) 88 # TODO: Cannot create venv from a nix env 89 # Error: Command '['/nix/store/ddc8nqx73pda86ibvhzdmvdsqmwnbjf7-python3-3.7.6-venv/bin/python3.7', '-Im', 'ensurepip', '--upgrade', '--default-pip']' returned non-zero exit status 1. 90 nixenv-venv = rec { 91 environment = runCommand "${python.name}-venv" { } '' 92 ${pythonEnv.interpreter} -m venv $out 93 ''; 94 interpreter = "${environment}/bin/${pythonEnv.executable}"; 95 is_venv = "True"; 96 is_nixenv = "True"; 97 is_virtualenv = "False"; 98 }; 99 }; 100 101 testfun = 102 name: attrs: 103 runCommand "${python.name}-tests-${name}" 104 ( 105 { 106 inherit (python) pythonVersion; 107 } 108 // attrs 109 ) 110 '' 111 cp -r ${./tests/test_environments} tests 112 chmod -R +w tests 113 substituteAllInPlace tests/test_python.py 114 ${attrs.interpreter} -m unittest discover --verbose tests #/test_python.py 115 mkdir $out 116 touch $out/success 117 ''; 118 119 in 120 lib.mapAttrs testfun environments; 121 122 # Integration tests involving the package set. 123 # All PyPy package builds are broken at the moment 124 integrationTests = lib.optionalAttrs (!python.isPyPy) ( 125 { 126 # Make sure tkinter is importable. See https://github.com/NixOS/nixpkgs/issues/238990 127 tkinter = callPackage ./tests/test_tkinter { 128 interpreter = python; 129 }; 130 } 131 // lib.optionalAttrs (python.isPy3k && python.pythonOlder "3.13" && !stdenv.hostPlatform.isDarwin) { 132 # darwin has no split-debug 133 # fails on python3.13 134 cpython-gdb = callPackage ./tests/test_cpython_gdb { 135 interpreter = python; 136 }; 137 } 138 // lib.optionalAttrs (python.isPy3k && python.pythonOlder "3.13") { 139 # Before the addition of NIX_PYTHONPREFIX mypy was broken with typed packages 140 # mypy does not yet support python3.13 141 # https://github.com/python/mypy/issues/17264 142 nix-pythonprefix-mypy = callPackage ./tests/test_nix_pythonprefix { 143 interpreter = python; 144 }; 145 } 146 ); 147 148 # Test editable package support 149 editableTests = 150 let 151 testPython = python.override { 152 self = testPython; 153 packageOverrides = pyfinal: pyprev: { 154 # An editable package with a script that loads our mutable location 155 my-editable = pyfinal.mkPythonEditablePackage { 156 pname = "my-editable"; 157 version = "0.1.0"; 158 root = "$NIX_BUILD_TOP/src"; # Use environment variable expansion at runtime 159 # Inject a script 160 scripts = { 161 my-script = "my_editable.main:main"; 162 }; 163 }; 164 }; 165 }; 166 167 in 168 { 169 editable-script = 170 runCommand "editable-test" 171 { 172 nativeBuildInputs = [ (testPython.withPackages (ps: [ ps.my-editable ])) ]; 173 } 174 '' 175 mkdir -p src/my_editable 176 177 cat > src/my_editable/main.py << EOF 178 def main(): 179 print("hello mutable") 180 EOF 181 182 test "$(my-script)" == "hello mutable" 183 test "$(python -c 'import sys; print(sys.path[1])')" == "$NIX_BUILD_TOP/src" 184 185 touch $out 186 ''; 187 }; 188 189 # Tests to ensure overriding works as expected. 190 overrideTests = 191 let 192 extension = self: super: { 193 foobar = super.numpy; 194 }; 195 # `pythonInterpreters.pypy39_prebuilt` does not expose an attribute 196 # name (is not present in top-level `pkgs`). 197 is_prebuilt = python: python.pythonAttr == null; 198 in 199 lib.optionalAttrs (python.isPy3k) ( 200 { 201 test-packageOverrides = 202 let 203 myPython = 204 let 205 self = python.override { 206 packageOverrides = extension; 207 inherit self; 208 }; 209 in 210 self; 211 in 212 assert myPython.pkgs.foobar == myPython.pkgs.numpy; 213 myPython.withPackages (ps: with ps; [ foobar ]); 214 # overrideScope is broken currently 215 # test-overrideScope = let 216 # myPackages = python.pkgs.overrideScope extension; 217 # in assert myPackages.foobar == myPackages.numpy; myPackages.python.withPackages(ps: with ps; [ foobar ]); 218 # 219 # Have to skip prebuilt python as it's not present in top-level 220 # `pkgs` as an attribute. 221 } 222 // lib.optionalAttrs (python ? pythonAttr && !is_prebuilt python) { 223 # Test applying overrides using pythonPackagesOverlays. 224 test-pythonPackagesExtensions = 225 let 226 pkgs_ = pkgs.extend ( 227 final: prev: { 228 pythonPackagesExtensions = prev.pythonPackagesExtensions ++ [ 229 (python-final: python-prev: { 230 foo = python-prev.setuptools; 231 }) 232 ]; 233 } 234 ); 235 in 236 pkgs_.${python.pythonAttr}.pkgs.foo; 237 } 238 ); 239 240 # depends on mypy, which depends on CPython internals 241 condaTests = lib.optionalAttrs (!python.isPyPy) ( 242 let 243 requests = callPackage ( 244 { 245 autoPatchelfHook, 246 fetchurl, 247 pythonCondaPackages, 248 }: 249 python.pkgs.buildPythonPackage { 250 pname = "requests"; 251 version = "2.24.0"; 252 format = "other"; 253 src = fetchurl { 254 url = "https://repo.anaconda.com/pkgs/main/noarch/requests-2.24.0-py_0.tar.bz2"; 255 sha256 = "02qzaf6gwsqbcs69pix1fnjxzgnngwzvrsy65h1d521g750mjvvp"; 256 }; 257 nativeBuildInputs = [ 258 autoPatchelfHook 259 ] 260 ++ (with python.pkgs; [ 261 condaUnpackHook 262 condaInstallHook 263 ]); 264 buildInputs = [ 265 pythonCondaPackages.condaPatchelfLibs 266 ]; 267 propagatedBuildInputs = with python.pkgs; [ 268 chardet 269 idna 270 urllib3 271 certifi 272 ]; 273 } 274 ) { }; 275 pythonWithRequests = requests.pythonModule.withPackages (ps: [ requests ]); 276 in 277 lib.optionalAttrs (python.isPy3k && stdenv.hostPlatform.isLinux) { 278 condaExamplePackage = runCommand "import-requests" { } '' 279 ${pythonWithRequests.interpreter} -c "import requests" > $out 280 ''; 281 } 282 ); 283 284in 285lib.optionalAttrs (stdenv.hostPlatform == stdenv.buildPlatform) ( 286 environmentTests // integrationTests // overrideTests // condaTests // editableTests 287)