1# To run these tests:
2# nix-build -A tests.stdenv
3
4{
5 stdenv,
6 pkgs,
7 lib,
8 testers,
9}:
10
11let
12 # early enough not to rebuild gcc but late enough to have patchelf
13 earlyPkgs = stdenv.__bootPackages.stdenv.__bootPackages;
14 earlierPkgs =
15 stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages;
16 # use a early stdenv so when hacking on stdenv this test can be run quickly
17 bootStdenv =
18 stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv;
19 pkgsStructured = import pkgs.path {
20 config = {
21 structuredAttrsByDefault = true;
22 };
23 inherit (stdenv.hostPlatform) system;
24 };
25 bootStdenvStructuredAttrsByDefault =
26 pkgsStructured.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv;
27
28 runCommand = earlierPkgs.runCommand;
29
30 ccWrapperSubstitutionsTest =
31 {
32 name,
33 stdenv',
34 extraAttrs ? { },
35 }:
36
37 stdenv'.cc.overrideAttrs (
38 previousAttrs:
39 (
40 {
41 inherit name;
42
43 postFixup = previousAttrs.postFixup + ''
44 declare -p wrapperName
45 echo "env.wrapperName = $wrapperName"
46 [[ $wrapperName == "CC_WRAPPER" ]] || (echo "'\$wrapperName' was not 'CC_WRAPPER'" && false)
47 declare -p suffixSalt
48 echo "env.suffixSalt = $suffixSalt"
49 [[ $suffixSalt == "${stdenv'.cc.suffixSalt}" ]] || (echo "'\$suffxSalt' was not '${stdenv'.cc.suffixSalt}'" && false)
50
51 grep -q "@out@" $out/bin/cc || echo "@out@ in $out/bin/cc was substituted"
52 grep -q "@suffixSalt@" $out/bin/cc && (echo "$out/bin/cc contains unsubstituted variables" && false)
53
54 touch $out
55 '';
56 }
57 // extraAttrs
58 )
59 );
60
61 testEnvAttrset =
62 {
63 name,
64 stdenv',
65 extraAttrs ? { },
66 }:
67 stdenv'.mkDerivation (
68 {
69 inherit name;
70 env = {
71 string = "testing-string";
72 };
73
74 passAsFile = [ "buildCommand" ];
75 buildCommand = ''
76 declare -p string
77 echo "env.string = $string"
78 [[ $string == "testing-string" ]] || (echo "'\$string' was not 'testing-string'" && false)
79 [[ "$(declare -p string)" == 'declare -x string="testing-string"' ]] || (echo "'\$string' was not exported" && false)
80 touch $out
81 '';
82 }
83 // extraAttrs
84 );
85
86 testPrependAndAppendToVar =
87 {
88 name,
89 stdenv',
90 extraAttrs ? { },
91 }:
92 stdenv'.mkDerivation (
93 {
94 inherit name;
95 env = {
96 string = "testing-string";
97 };
98
99 passAsFile = [ "buildCommand" ] ++ lib.optionals (extraAttrs ? extraTest) [ "extraTest" ];
100 buildCommand = ''
101 declare -p string
102 appendToVar string hello
103 # test that quoted strings work
104 prependToVar string "world"
105 declare -p string
106
107 declare -A associativeArray=(["X"]="Y")
108 [[ $(appendToVar associativeArray "fail" 2>&1) =~ "trying to use" ]] || (echo "appendToVar did not throw appending to associativeArray" && false)
109 [[ $(prependToVar associativeArray "fail" 2>&1) =~ "trying to use" ]] || (echo "prependToVar did not throw prepending associativeArray" && false)
110
111 [[ $string == "world testing-string hello" ]] || (echo "'\$string' was not 'world testing-string hello'" && false)
112
113 # test appending to a unset variable
114 appendToVar nonExistant created hello
115 declare -p nonExistant
116 if [[ -n $__structuredAttrs ]]; then
117 [[ "''${nonExistant[@]}" == "created hello" ]]
118 else
119 # there's a extra " " in front here and a extra " " in the end of prependToVar
120 # shouldn't matter because these functions will mostly be used for $*Flags and the Flag variable will in most cases already exist
121 [[ "$nonExistant" == " created hello" ]]
122 fi
123
124 eval "$extraTest"
125
126 touch $out
127 '';
128 }
129 // extraAttrs
130 );
131
132 testConcatTo =
133 {
134 name,
135 stdenv',
136 extraAttrs ? { },
137 }:
138 stdenv'.mkDerivation (
139 {
140 inherit name;
141
142 string = "a *";
143 list = [
144 "c"
145 "d"
146 ];
147
148 passAsFile = [ "buildCommand" ] ++ lib.optionals (extraAttrs ? extraTest) [ "extraTest" ];
149 buildCommand = ''
150 declare -A associativeArray=(["X"]="Y")
151 [[ $(concatTo nowhere associativeArray 2>&1) =~ "trying to use" ]] || (echo "concatTo did not throw concatenating associativeArray" && false)
152
153 empty_array=()
154 empty_string=""
155
156 declare -a flagsArray
157 concatTo flagsArray string list notset=e=f empty_array=g empty_string=h
158 declare -p flagsArray
159 [[ "''${flagsArray[0]}" == "a" ]] || (echo "'\$flagsArray[0]' was not 'a'" && false)
160 [[ "''${flagsArray[1]}" == "*" ]] || (echo "'\$flagsArray[1]' was not '*'" && false)
161 [[ "''${flagsArray[2]}" == "c" ]] || (echo "'\$flagsArray[2]' was not 'c'" && false)
162 [[ "''${flagsArray[3]}" == "d" ]] || (echo "'\$flagsArray[3]' was not 'd'" && false)
163 [[ "''${flagsArray[4]}" == "e=f" ]] || (echo "'\$flagsArray[4]' was not 'e=f'" && false)
164 [[ "''${flagsArray[5]}" == "g" ]] || (echo "'\$flagsArray[5]' was not 'g'" && false)
165 [[ "''${flagsArray[6]}" == "h" ]] || (echo "'\$flagsArray[6]' was not 'h'" && false)
166
167 # test concatenating to unset variable
168 concatTo nonExistant string list notset=e=f empty_array=g empty_string=h
169 declare -p nonExistant
170 [[ "''${nonExistant[0]}" == "a" ]] || (echo "'\$nonExistant[0]' was not 'a'" && false)
171 [[ "''${nonExistant[1]}" == "*" ]] || (echo "'\$nonExistant[1]' was not '*'" && false)
172 [[ "''${nonExistant[2]}" == "c" ]] || (echo "'\$nonExistant[2]' was not 'c'" && false)
173 [[ "''${nonExistant[3]}" == "d" ]] || (echo "'\$nonExistant[3]' was not 'd'" && false)
174 [[ "''${nonExistant[4]}" == "e=f" ]] || (echo "'\$nonExistant[4]' was not 'e=f'" && false)
175 [[ "''${nonExistant[5]}" == "g" ]] || (echo "'\$nonExistant[5]' was not 'g'" && false)
176 [[ "''${nonExistant[6]}" == "h" ]] || (echo "'\$nonExistant[6]' was not 'h'" && false)
177
178 eval "$extraTest"
179
180 touch $out
181 '';
182 }
183 // extraAttrs
184 );
185
186 testConcatStringsSep =
187 { name, stdenv' }:
188 stdenv'.mkDerivation {
189 inherit name;
190
191 # NOTE: Testing with "&" as separator is intentional, because unquoted
192 # "&" has a special meaning in the "${var//pattern/replacement}" syntax.
193 # Cf. https://github.com/NixOS/nixpkgs/pull/318614#discussion_r1706191919
194 passAsFile = [ "buildCommand" ];
195 buildCommand = ''
196 declare -A associativeArray=(["X"]="Y")
197 [[ $(concatStringsSep ";" associativeArray 2>&1) =~ "trying to use" ]] || (echo "concatStringsSep did not throw concatenating associativeArray" && false)
198
199 string="lorem ipsum dolor sit amet"
200 stringWithSep="$(concatStringsSep "&" string)"
201 [[ "$stringWithSep" == "lorem&ipsum&dolor&sit&amet" ]] || (echo "'\$stringWithSep' was not 'lorem&ipsum&dolor&sit&amet'" && false)
202
203 array=("lorem ipsum" "dolor" "sit amet")
204 arrayWithSep="$(concatStringsSep "&" array)"
205 [[ "$arrayWithSep" == "lorem ipsum&dolor&sit amet" ]] || (echo "'\$arrayWithSep' was not 'lorem ipsum&dolor&sit amet'" && false)
206
207 array=("lorem ipsum" "dolor" "sit amet")
208 arrayWithSep="$(concatStringsSep "++" array)"
209 [[ "$arrayWithSep" == "lorem ipsum++dolor++sit amet" ]] || (echo "'\$arrayWithSep' was not 'lorem ipsum++dolor++sit amet'" && false)
210
211 array=("lorem ipsum" "dolor" "sit amet")
212 arrayWithSep="$(concatStringsSep " and " array)"
213 [[ "$arrayWithSep" == "lorem ipsum and dolor and sit amet" ]] || (echo "'\$arrayWithSep' was not 'lorem ipsum and dolor and sit amet'" && false)
214
215 touch $out
216 '';
217 };
218in
219
220{
221 # tests for hooks in `stdenv.defaultNativeBuildInputs`
222 hooks = lib.recurseIntoAttrs (
223 import ./hooks.nix {
224 stdenv = bootStdenv;
225 pkgs = earlyPkgs;
226 inherit lib;
227 }
228 );
229
230 outputs-no-out =
231 runCommand "outputs-no-out-assert"
232 {
233 result = earlierPkgs.testers.testBuildFailure (
234 bootStdenv.mkDerivation {
235 NIX_DEBUG = 1;
236 name = "outputs-no-out";
237 outputs = [ "foo" ];
238 buildPhase = ":";
239 installPhase = ''
240 touch $foo
241 '';
242 }
243 );
244
245 # Assumption: the first output* variable to be configured is
246 # _overrideFirst outputDev "dev" "out"
247 expectedMsg = "error: _assignFirst: could not find a non-empty variable whose name to assign to outputDev.\n The following variables were all unset or empty:\n dev out";
248 }
249 ''
250 grep -F "$expectedMsg" $result/testBuildFailure.log >/dev/null
251 touch $out
252 '';
253
254 test-env-attrset = testEnvAttrset {
255 name = "test-env-attrset";
256 stdenv' = bootStdenv;
257 };
258
259 # Check that mkDerivation rejects MD5 hashes
260 rejectedHashes = lib.recurseIntoAttrs {
261 md5 =
262 let
263 drv = runCommand "md5 outputHash rejected" {
264 outputHash = "md5-fPt7dxVVP7ffY3MxkQdwVw==";
265 } "true";
266 in
267 assert !(builtins.tryEval drv).success;
268 { };
269 };
270
271 test-inputDerivation =
272 let
273 inherit
274 (stdenv.mkDerivation {
275 dep1 = derivation {
276 name = "dep1";
277 builder = "/bin/sh";
278 args = [
279 "-c"
280 ": > $out"
281 ];
282 system = builtins.currentSystem;
283 };
284 dep2 = derivation {
285 name = "dep2";
286 builder = "/bin/sh";
287 args = [
288 "-c"
289 ": > $out"
290 ];
291 system = builtins.currentSystem;
292 };
293 passAsFile = [ "dep2" ];
294 })
295 inputDerivation
296 ;
297 in
298 runCommand "test-inputDerivation"
299 {
300 exportReferencesGraph = [
301 "graph"
302 inputDerivation
303 ];
304 }
305 ''
306 grep ${inputDerivation.dep1} graph
307 grep ${inputDerivation.dep2} graph
308 touch $out
309 '';
310
311 test-inputDerivation-fixed-output =
312 let
313 inherit
314 (stdenv.mkDerivation {
315 dep1 = derivation {
316 name = "dep1";
317 builder = "/bin/sh";
318 args = [
319 "-c"
320 ": > $out"
321 ];
322 system = builtins.currentSystem;
323 };
324 dep2 = derivation {
325 name = "dep2";
326 builder = "/bin/sh";
327 args = [
328 "-c"
329 ": > $out"
330 ];
331 system = builtins.currentSystem;
332 };
333 name = "meow";
334 outputHash = "sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=";
335 outputHashMode = "flat";
336 outputHashAlgo = "sha256";
337 buildCommand = ''
338 touch $out
339 '';
340 passAsFile = [ "dep2" ];
341 })
342 inputDerivation
343 ;
344 in
345 runCommand "test-inputDerivation"
346 {
347 exportReferencesGraph = [
348 "graph"
349 inputDerivation
350 ];
351 }
352 ''
353 grep ${inputDerivation.dep1} graph
354 grep ${inputDerivation.dep2} graph
355 touch $out
356 '';
357
358 test-prepend-append-to-var = testPrependAndAppendToVar {
359 name = "test-prepend-append-to-var";
360 stdenv' = bootStdenv;
361 };
362
363 test-concat-to = testConcatTo {
364 name = "test-concat-to";
365 stdenv' = bootStdenv;
366 };
367
368 test-concat-strings-sep = testConcatStringsSep {
369 name = "test-concat-strings-sep";
370 stdenv' = bootStdenv;
371 };
372
373 test-structured-env-attrset = testEnvAttrset {
374 name = "test-structured-env-attrset";
375 stdenv' = bootStdenv;
376 extraAttrs = {
377 __structuredAttrs = true;
378 };
379 };
380
381 test-cc-wrapper-substitutions = ccWrapperSubstitutionsTest {
382 name = "test-cc-wrapper-substitutions";
383 stdenv' = bootStdenv;
384 };
385
386 ensure-no-execve-in-setup-sh =
387 derivation {
388 name = "ensure-no-execve-in-setup-sh";
389 system = stdenv.system;
390 builder = "${stdenv.bootstrapTools}/bin/bash";
391 PATH = "${pkgs.strace}/bin:${stdenv.bootstrapTools}/bin";
392 initialPath = [
393 stdenv.bootstrapTools
394 pkgs.strace
395 ];
396 args = [
397 "-c"
398 ''
399 countCall() {
400 echo "$stats" | tr -s ' ' | grep "$1" | cut -d ' ' -f5
401 }
402
403 # prevent setup.sh from running `nproc` when cores=0
404 # (this would mess up the syscall stats)
405 export NIX_BUILD_CORES=1
406
407 echo "Analyzing setup.sh with strace"
408 stats=$(strace -fc bash -c ". ${../../stdenv/generic/setup.sh}" 2>&1)
409 echo "$stats" | head -n15
410
411 # fail if execve calls is > 1
412 stats=$(strace -fc bash -c ". ${../../stdenv/generic/setup.sh}" 2>&1)
413 execveCalls=$(countCall execve)
414 if [ "$execveCalls" -gt 1 ]; then
415 echo "execve calls: $execveCalls; expected: 1"
416 echo "ERROR: setup.sh should not launch additional processes when being sourced"
417 exit 1
418 else
419 echo "setup.sh doesn't launch extra processes when sourcing, as expected"
420 fi
421
422 touch $out
423 ''
424 ];
425 }
426 // {
427 meta = { };
428 };
429
430 structuredAttrsByDefault = lib.recurseIntoAttrs {
431
432 hooks = lib.recurseIntoAttrs (
433 import ./hooks.nix {
434 stdenv = bootStdenvStructuredAttrsByDefault;
435 pkgs = earlyPkgs;
436 inherit lib;
437 }
438 );
439
440 test-cc-wrapper-substitutions = ccWrapperSubstitutionsTest {
441 name = "test-cc-wrapper-substitutions-structuredAttrsByDefault";
442 stdenv' = bootStdenvStructuredAttrsByDefault;
443 };
444
445 test-structured-env-attrset = testEnvAttrset {
446 name = "test-structured-env-attrset-structuredAttrsByDefault";
447 stdenv' = bootStdenvStructuredAttrsByDefault;
448 };
449
450 test-prepend-append-to-var = testPrependAndAppendToVar {
451 name = "test-prepend-append-to-var-structuredAttrsByDefault";
452 stdenv' = bootStdenvStructuredAttrsByDefault;
453 extraAttrs = {
454 # will be a bash indexed array in attrs.sh
455 # declare -a list=('a' 'b' )
456 # and a json array in attrs.json
457 # "list":["a","b"]
458 list = [
459 "a"
460 "b"
461 ];
462 # will be a bash associative array(dictionary) in attrs.sh
463 # declare -A array=(['a']='1' ['b']='2' )
464 # and a json object in attrs.json
465 # {"array":{"a":"1","b":"2"}
466 array = {
467 a = "1";
468 b = "2";
469 };
470 extraTest = ''
471 declare -p array
472 array+=(["c"]="3")
473 declare -p array
474
475 [[ "''${array[c]}" == "3" ]] || (echo "c element of '\$array' was not '3'" && false)
476
477 declare -p list
478 prependToVar list hello
479 # test that quoted strings work
480 appendToVar list "world"
481 declare -p list
482
483 [[ "''${list[0]}" == "hello" ]] || (echo "first element of '\$list' was not 'hello'" && false)
484 [[ "''${list[1]}" == "a" ]] || (echo "first element of '\$list' was not 'a'" && false)
485 [[ "''${list[-1]}" == "world" ]] || (echo "last element of '\$list' was not 'world'" && false)
486 '';
487 };
488 };
489
490 test-concat-to = testConcatTo {
491 name = "test-concat-to-structuredAttrsByDefault";
492 stdenv' = bootStdenvStructuredAttrsByDefault;
493 extraAttrs = {
494 # test that whitespace is kept in the bash array for structuredAttrs
495 listWithSpaces = [
496 "c c"
497 "d d"
498 ];
499 extraTest = ''
500 declare -a flagsWithSpaces
501 concatTo flagsWithSpaces string listWithSpaces
502 declare -p flagsWithSpaces
503 [[ "''${flagsWithSpaces[0]}" == "a" ]] || (echo "'\$flagsWithSpaces[0]' was not 'a'" && false)
504 [[ "''${flagsWithSpaces[1]}" == "*" ]] || (echo "'\$flagsWithSpaces[1]' was not '*'" && false)
505 [[ "''${flagsWithSpaces[2]}" == "c c" ]] || (echo "'\$flagsWithSpaces[2]' was not 'c c'" && false)
506 [[ "''${flagsWithSpaces[3]}" == "d d" ]] || (echo "'\$flagsWithSpaces[3]' was not 'd d'" && false)
507 '';
508 };
509 };
510
511 test-concat-strings-sep = testConcatStringsSep {
512 name = "test-concat-strings-sep-structuredAttrsByDefault";
513 stdenv' = bootStdenvStructuredAttrsByDefault;
514 };
515
516 test-golden-example-structuredAttrs =
517 let
518 goldenSh = earlyPkgs.writeText "goldenSh" ''
519 declare -A EXAMPLE_ATTRS=(['foo']='bar' )
520 declare EXAMPLE_BOOL_FALSE=
521 declare EXAMPLE_BOOL_TRUE=1
522 declare EXAMPLE_INT=123
523 declare EXAMPLE_INT_NEG=-123
524 declare -a EXAMPLE_LIST=('foo' 'bar' )
525 declare EXAMPLE_STR='foo bar'
526 '';
527 goldenJson = earlyPkgs.writeText "goldenSh" ''
528 {
529 "EXAMPLE_ATTRS": {
530 "foo": "bar"
531 },
532 "EXAMPLE_BOOL_FALSE": false,
533 "EXAMPLE_BOOL_TRUE": true,
534 "EXAMPLE_INT": 123,
535 "EXAMPLE_INT_NEG": -123,
536 "EXAMPLE_LIST": [
537 "foo",
538 "bar"
539 ],
540 "EXAMPLE_NESTED_ATTRS": {
541 "foo": {
542 "bar": "baz"
543 }
544 },
545 "EXAMPLE_NESTED_LIST": [
546 [
547 "foo",
548 "bar"
549 ],
550 [
551 "baz"
552 ]
553 ],
554 "EXAMPLE_STR": "foo bar"
555 }
556 '';
557 in
558 bootStdenvStructuredAttrsByDefault.mkDerivation {
559 name = "test-golden-example-structuredAttrsByDefault";
560 nativeBuildInputs = [ earlyPkgs.jq ];
561
562 EXAMPLE_BOOL_TRUE = true;
563 EXAMPLE_BOOL_FALSE = false;
564 EXAMPLE_INT = 123;
565 EXAMPLE_INT_NEG = -123;
566 EXAMPLE_STR = "foo bar";
567 EXAMPLE_LIST = [
568 "foo"
569 "bar"
570 ];
571 EXAMPLE_NESTED_LIST = [
572 [
573 "foo"
574 "bar"
575 ]
576 [ "baz" ]
577 ];
578 EXAMPLE_ATTRS = {
579 foo = "bar";
580 };
581 EXAMPLE_NESTED_ATTRS = {
582 foo.bar = "baz";
583 };
584
585 inherit goldenSh;
586 inherit goldenJson;
587
588 buildCommand = ''
589 mkdir -p $out
590 cat $NIX_ATTRS_SH_FILE | grep "EXAMPLE" | grep -v -E 'installPhase|jq' > $out/sh
591 jq 'with_entries(select(.key|match("EXAMPLE")))' $NIX_ATTRS_JSON_FILE > $out/json
592 diff $out/sh $goldenSh
593 diff $out/json $goldenJson
594 '';
595 };
596 };
597}