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