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)