Files
nixpkgs/pkgs/test/stdenv/default.nix
Aliaksandr f8b9b7a087 stdenv/darwin: move the libiconv alias assertion into tests.stdenv
The final stage asserted

    assert prevStage.libiconv == prevStage.darwin.libiconv;

On Darwin `libiconv` is `darwin.libiconv`, so this compares a value with
itself. Nix does not notice: `eqValues` short-circuits only on pointer
identity of the two value slots, and these are two slots that resolve to
one attribute set. It therefore falls through to the derivation case,
which compares `outPath`.

`lib.extendDerivation` guards `outPath` behind the derivation's validity,
which reads `config.allowUnfree`, so the assertion demanded a `config`
option while the package set was still being assembled. When `config` is a
function it is passed `pkgs`, and the loop closes:

    (import <nixpkgs> {
      system = "aarch64-darwin";
      config = { pkgs, lib, ... }: { allowUnfree = lib.isAttrs pkgs; };
    }).config.allowUnfree

failed with an infinite recursion on Darwin while evaluating on Linux,
whose stdenv never forces a derivation that early. It now evaluates on
both.

Keep checking the invariant, from `tests.stdenv` instead. By then the
package set is complete, so forcing `outPath` is just a read.
The other twelve assertions in the stage read `passthru` and
`stdenv.cc.cc`, which are ungated, and stay where they are.

Assisted-by: claude-code with claude-opus-5[1m]-high
2026-08-10 00:50:54 +03:00

677 lines
21 KiB
Nix

# To run these tests:
# nix-build -A tests.stdenv
{
stdenv,
pkgs,
lib,
config,
testers,
}:
let
# tests can be based on builtins.derivation and stage0 or bootstrapTools directly to minimize rebuilds
# see test 'make-symlinks-relative' in ./hooks.nix as an example.
initialBash = if stdenv ? stage0 then stdenv.stage0.bash else stdenv.bootstrapTools;
initialPath = if stdenv ? stage0 then stdenv.stage0.initialPath else [ stdenv.bootstrapTools ];
# early enough not to rebuild gcc but late enough to have patchelf
earlyPkgs = stdenv.__bootPackages.stdenv.__bootPackages or pkgs;
earlierPkgs =
stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages
or earlyPkgs;
# use a early stdenv so when hacking on stdenv this test can be run quickly
bootStdenv = earlyPkgs.stdenv.__bootPackages.stdenv.__bootPackages.stdenv or earlyPkgs.stdenv;
pkgsStructured = import pkgs.path {
config = config // {
structuredAttrsByDefault = true;
};
inherit (stdenv.hostPlatform) system;
};
bootStdenvStructuredAttrsByDefault =
pkgsStructured.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv
or pkgsStructured.stdenv;
runCommand = earlierPkgs.runCommand;
ccWrapperSubstitutionsTest =
{
name,
stdenv',
extraAttrs ? { },
}:
stdenv'.cc.overrideAttrs (
previousAttrs:
(
{
inherit name;
postFixup = previousAttrs.postFixup + ''
declare -p wrapperName
echo "env.wrapperName = $wrapperName"
[[ $wrapperName == "CC_WRAPPER" ]] || (echo "'\$wrapperName' was not 'CC_WRAPPER'" && false)
declare -p suffixSalt
echo "env.suffixSalt = $suffixSalt"
[[ $suffixSalt == "${stdenv'.cc.suffixSalt}" ]] || (echo "'\$suffxSalt' was not '${stdenv'.cc.suffixSalt}'" && false)
grep -q "@out@" $out/bin/cc || echo "@out@ in $out/bin/cc was substituted"
grep -q "@suffixSalt@" $out/bin/cc && (echo "$out/bin/cc contains unsubstituted variables" && false)
touch $out
'';
}
// extraAttrs
)
);
testEnvAttrset =
{
name,
stdenv',
extraAttrs ? { },
}:
stdenv'.mkDerivation (
{
inherit name;
env = {
string = "testing-string";
};
passAsFile = [ "buildCommand" ];
buildCommand = ''
declare -p string
echo "env.string = $string"
[[ $string == "testing-string" ]] || (echo "'\$string' was not 'testing-string'" && false)
[[ "$(declare -p string)" == 'declare -x string="testing-string"' ]] || (echo "'\$string' was not exported" && false)
touch $out
'';
}
// extraAttrs
);
testPrependAndAppendToVar =
{
name,
stdenv',
extraAttrs ? { },
}:
stdenv'.mkDerivation (
{
inherit name;
env = {
string = "testing-string";
};
passAsFile = [ "buildCommand" ] ++ lib.optionals (extraAttrs ? extraTest) [ "extraTest" ];
buildCommand = ''
declare -p string
appendToVar string hello
# test that quoted strings work
prependToVar string "world"
declare -p string
declare -A associativeArray=(["X"]="Y")
[[ $(appendToVar associativeArray "fail" 2>&1) =~ "trying to use" ]] || (echo "appendToVar did not throw appending to associativeArray" && false)
[[ $(prependToVar associativeArray "fail" 2>&1) =~ "trying to use" ]] || (echo "prependToVar did not throw prepending associativeArray" && false)
[[ $string == "world testing-string hello" ]] || (echo "'\$string' was not 'world testing-string hello'" && false)
# test appending to a unset variable
appendToVar nonExistant created hello
declare -p nonExistant
if [[ -n $__structuredAttrs ]]; then
[[ "''${nonExistant[@]}" == "created hello" ]]
else
# there's a extra " " in front here and a extra " " in the end of prependToVar
# shouldn't matter because these functions will mostly be used for $*Flags and the Flag variable will in most cases already exist
[[ "$nonExistant" == " created hello" ]]
fi
eval "$extraTest"
touch $out
'';
}
// extraAttrs
);
testConcatTo =
{
name,
stdenv',
extraAttrs ? { },
}:
stdenv'.mkDerivation (
{
inherit name;
string = "a *";
list = [
"c"
"d"
];
passAsFile = [ "buildCommand" ] ++ lib.optionals (extraAttrs ? extraTest) [ "extraTest" ];
buildCommand = ''
declare -A associativeArray=(["X"]="Y")
[[ $(concatTo nowhere associativeArray 2>&1) =~ "trying to use" ]] || (echo "concatTo did not throw concatenating associativeArray" && false)
empty_array=()
empty_string=""
declare -a flagsArray
concatTo flagsArray string list notset=e=f empty_array=g empty_string=h
declare -p flagsArray
[[ "''${flagsArray[0]}" == "a" ]] || (echo "'\$flagsArray[0]' was not 'a'" && false)
[[ "''${flagsArray[1]}" == "*" ]] || (echo "'\$flagsArray[1]' was not '*'" && false)
[[ "''${flagsArray[2]}" == "c" ]] || (echo "'\$flagsArray[2]' was not 'c'" && false)
[[ "''${flagsArray[3]}" == "d" ]] || (echo "'\$flagsArray[3]' was not 'd'" && false)
[[ "''${flagsArray[4]}" == "e=f" ]] || (echo "'\$flagsArray[4]' was not 'e=f'" && false)
[[ "''${flagsArray[5]}" == "g" ]] || (echo "'\$flagsArray[5]' was not 'g'" && false)
[[ "''${flagsArray[6]}" == "h" ]] || (echo "'\$flagsArray[6]' was not 'h'" && false)
# test concatenating to unset variable
concatTo nonExistant string list notset=e=f empty_array=g empty_string=h
declare -p nonExistant
[[ "''${nonExistant[0]}" == "a" ]] || (echo "'\$nonExistant[0]' was not 'a'" && false)
[[ "''${nonExistant[1]}" == "*" ]] || (echo "'\$nonExistant[1]' was not '*'" && false)
[[ "''${nonExistant[2]}" == "c" ]] || (echo "'\$nonExistant[2]' was not 'c'" && false)
[[ "''${nonExistant[3]}" == "d" ]] || (echo "'\$nonExistant[3]' was not 'd'" && false)
[[ "''${nonExistant[4]}" == "e=f" ]] || (echo "'\$nonExistant[4]' was not 'e=f'" && false)
[[ "''${nonExistant[5]}" == "g" ]] || (echo "'\$nonExistant[5]' was not 'g'" && false)
[[ "''${nonExistant[6]}" == "h" ]] || (echo "'\$nonExistant[6]' was not 'h'" && false)
eval "$extraTest"
touch $out
'';
}
// extraAttrs
);
testConcatStringsSep =
{ name, stdenv' }:
stdenv'.mkDerivation {
inherit name;
# NOTE: Testing with "&" as separator is intentional, because unquoted
# "&" has a special meaning in the "${var//pattern/replacement}" syntax.
# Cf. https://github.com/NixOS/nixpkgs/pull/318614#discussion_r1706191919
passAsFile = [ "buildCommand" ];
buildCommand = ''
declare -A associativeArray=(["X"]="Y")
[[ $(concatStringsSep ";" associativeArray 2>&1) =~ "trying to use" ]] || (echo "concatStringsSep did not throw concatenating associativeArray" && false)
string="lorem ipsum dolor sit amet"
stringWithSep="$(concatStringsSep "&" string)"
[[ "$stringWithSep" == "lorem&ipsum&dolor&sit&amet" ]] || (echo "'\$stringWithSep' was not 'lorem&ipsum&dolor&sit&amet'" && false)
array=("lorem ipsum" "dolor" "sit amet")
arrayWithSep="$(concatStringsSep "&" array)"
[[ "$arrayWithSep" == "lorem ipsum&dolor&sit amet" ]] || (echo "'\$arrayWithSep' was not 'lorem ipsum&dolor&sit amet'" && false)
array=("lorem ipsum" "dolor" "sit amet")
arrayWithSep="$(concatStringsSep "++" array)"
[[ "$arrayWithSep" == "lorem ipsum++dolor++sit amet" ]] || (echo "'\$arrayWithSep' was not 'lorem ipsum++dolor++sit amet'" && false)
array=("lorem ipsum" "dolor" "sit amet")
arrayWithSep="$(concatStringsSep " and " array)"
[[ "$arrayWithSep" == "lorem ipsum and dolor and sit amet" ]] || (echo "'\$arrayWithSep' was not 'lorem ipsum and dolor and sit amet'" && false)
touch $out
'';
};
testInputDerivationDep = stdenv.mkDerivation {
name = "test-input-derivation-dependency";
buildCommand = "touch $out";
};
testInputDerivation =
attrs:
(stdenv.mkDerivation (
attrs
// {
buildInputs = [ testInputDerivationDep ];
}
)).inputDerivation
// {
meta = { };
};
in
{
# tests for hooks in `stdenv.defaultNativeBuildInputs`
hooks = lib.recurseIntoAttrs (
import ./hooks.nix {
stdenv = bootStdenv;
pkgs = earlyPkgs;
inherit initialPath initialBash lib;
}
);
# `libiconv` should be an alias for `darwin.libiconv`. Checked here and not in
# the Darwin stdenv because comparing derivations forces `outPath`, which is
# guarded behind validity, which reads `config` — a cycle during bootstrap.
darwin-libiconv-alias =
assert stdenv.hostPlatform.isDarwin -> pkgs.libiconv == pkgs.darwin.libiconv;
pkgs.emptyFile;
outputs-no-out =
runCommand "outputs-no-out-assert"
{
result = earlierPkgs.testers.testBuildFailure (
bootStdenv.mkDerivation {
NIX_DEBUG = 1;
name = "outputs-no-out";
outputs = [ "foo" ];
buildPhase = ":";
installPhase = ''
touch $foo
'';
}
);
# Assumption: the first output* variable to be configured is
# _overrideFirst outputDev "dev" "out"
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";
}
''
grep -F "$expectedMsg" $result/testBuildFailure.log >/dev/null
touch $out
'';
test-env-attrset = testEnvAttrset {
name = "test-env-attrset";
stdenv' = bootStdenv;
};
# Check that mkDerivation rejects MD5 hashes
rejectedHashes = lib.recurseIntoAttrs {
md5 =
let
drv = runCommand "md5 outputHash rejected" {
outputHash = "md5-fPt7dxVVP7ffY3MxkQdwVw==";
} "true";
in
assert !(builtins.tryEval drv).success;
{ };
};
test-inputDerivation =
let
inherit
(stdenv.mkDerivation {
dep1 = derivation {
name = "dep1";
builder = "/bin/sh";
args = [
"-c"
": > $out"
];
inherit (stdenv.buildPlatform) system;
};
dep2 = derivation {
name = "dep2";
builder = "/bin/sh";
args = [
"-c"
": > $out"
];
inherit (stdenv.buildPlatform) system;
};
passAsFile = [ "dep2" ];
})
inputDerivation
;
in
runCommand "test-inputDerivation"
{
exportReferencesGraph = [
"graph"
inputDerivation
];
}
''
grep ${inputDerivation.dep1} graph
grep ${inputDerivation.dep2} graph
touch $out
'';
test-inputDerivation-fixed-output =
let
inherit
(stdenv.mkDerivation {
dep1 = derivation {
name = "dep1";
builder = "/bin/sh";
args = [
"-c"
": > $out"
];
inherit (stdenv.buildPlatform) system;
};
dep2 = derivation {
name = "dep2";
builder = "/bin/sh";
args = [
"-c"
": > $out"
];
inherit (stdenv.buildPlatform) system;
};
name = "meow";
outputHash = "sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=";
outputHashMode = "flat";
outputHashAlgo = "sha256";
buildCommand = ''
touch $out
'';
passAsFile = [ "dep2" ];
})
inputDerivation
;
in
runCommand "test-inputDerivation"
{
exportReferencesGraph = [
"graph"
inputDerivation
];
}
''
grep ${inputDerivation.dep1} graph
grep ${inputDerivation.dep2} graph
touch $out
'';
test-inputDerivation-structured = testInputDerivation {
name = "test-inDrv-structured";
__structuredAttrs = true;
};
test-inputDerivation-allowedReferences = testInputDerivation {
name = "test-inDrv-allowedReferences";
allowedReferences = [ ];
};
test-inputDerivation-disallowedReferences = testInputDerivation {
name = "test-inDrv-disallowedReferences";
disallowedReferences = [ "${testInputDerivationDep}" ];
};
test-inputDerivation-allowedRequisites = testInputDerivation {
name = "test-inDrv-allowedRequisites";
allowedRequisites = [ ];
};
test-inputDerivation-disallowedRequisites = testInputDerivation {
name = "test-inDrv-disallowedRequisites";
disallowedRequisites = [ "${testInputDerivationDep}" ];
};
test-inputDerivation-structured-allowedReferences = testInputDerivation {
name = "test-inDrv-structured-allowedReferences";
__structuredAttrs = true;
outputChecks.out.allowedReferences = [ ];
};
test-inputDerivation-structured-disallowedReferences = testInputDerivation {
name = "test-inDrv-structured-disallowedReferences";
__structuredAttrs = true;
outputChecks.out.disallowedReferences = [ "${testInputDerivationDep}" ];
};
test-inputDerivation-structured-allowedRequisites = testInputDerivation {
name = "test-inDrv-structured-allowedRequisites";
__structuredAttrs = true;
outputChecks.out.allowedRequisites = [ ];
};
test-inputDerivation-structured-disallowedRequisites = testInputDerivation {
name = "test-inDrv-structured-disallowedRequisites";
__structuredAttrs = true;
outputChecks.out.disallowedRequisites = [ "${testInputDerivationDep}" ];
};
test-prepend-append-to-var = testPrependAndAppendToVar {
name = "test-prepend-append-to-var";
stdenv' = bootStdenv;
};
test-concat-to = testConcatTo {
name = "test-concat-to";
stdenv' = bootStdenv;
};
test-concat-strings-sep = testConcatStringsSep {
name = "test-concat-strings-sep";
stdenv' = bootStdenv;
};
test-structured-env-attrset = testEnvAttrset {
name = "test-structured-env-attrset";
stdenv' = bootStdenv;
extraAttrs = {
__structuredAttrs = true;
};
};
test-cc-wrapper-substitutions = ccWrapperSubstitutionsTest {
name = "test-cc-wrapper-substitutions";
stdenv' = bootStdenv;
};
tests-stdenv-gcc-stageCompare = pkgs.callPackage ./gcc-stageCompare.nix { };
ensure-no-execve-in-setup-sh =
derivation {
name = "ensure-no-execve-in-setup-sh";
inherit (stdenv.hostPlatform) system;
builder = "${initialBash}/bin/bash";
PATH = "${pkgs.strace}/bin:${lib.strings.makeSearchPath "bin" initialPath}";
initialPath = initialPath ++ [
pkgs.strace
];
args = [
"-c"
''
countCall() {
echo "$stats" | tr -s ' ' | grep "$1" | cut -d ' ' -f5
}
# prevent setup.sh from running `nproc` when cores=0
# (this would mess up the syscall stats)
export NIX_BUILD_CORES=1
echo "Analyzing setup.sh with strace"
stats=$(strace -fc bash -c ". ${../../stdenv/generic/setup.sh}" 2>&1)
echo "$stats" | head -n15
# fail if execve calls is > 1
stats=$(strace -fc bash -c ". ${../../stdenv/generic/setup.sh}" 2>&1)
execveCalls=$(countCall execve)
if [ "$execveCalls" -gt 1 ]; then
echo "execve calls: $execveCalls; expected: 1"
echo "ERROR: setup.sh should not launch additional processes when being sourced"
exit 1
else
echo "setup.sh doesn't launch extra processes when sourcing, as expected"
fi
touch $out
''
];
}
// {
meta = { };
};
structuredAttrsByDefault = lib.recurseIntoAttrs {
hooks = lib.recurseIntoAttrs (
import ./hooks.nix {
stdenv = bootStdenvStructuredAttrsByDefault;
pkgs = earlyPkgs;
inherit initialBash initialPath lib;
}
);
test-cc-wrapper-substitutions = ccWrapperSubstitutionsTest {
name = "test-cc-wrapper-substitutions-structuredAttrsByDefault";
stdenv' = bootStdenvStructuredAttrsByDefault;
};
test-structured-env-attrset = testEnvAttrset {
name = "test-structured-env-attrset-structuredAttrsByDefault";
stdenv' = bootStdenvStructuredAttrsByDefault;
};
test-prepend-append-to-var = testPrependAndAppendToVar {
name = "test-prepend-append-to-var-structuredAttrsByDefault";
stdenv' = bootStdenvStructuredAttrsByDefault;
extraAttrs = {
# will be a bash indexed array in attrs.sh
# declare -a list=('a' 'b' )
# and a json array in attrs.json
# "list":["a","b"]
list = [
"a"
"b"
];
# will be a bash associative array(dictionary) in attrs.sh
# declare -A array=(['a']='1' ['b']='2' )
# and a json object in attrs.json
# {"array":{"a":"1","b":"2"}
array = {
a = "1";
b = "2";
};
extraTest = ''
declare -p array
array+=(["c"]="3")
declare -p array
[[ "''${array[c]}" == "3" ]] || (echo "c element of '\$array' was not '3'" && false)
declare -p list
prependToVar list hello
# test that quoted strings work
appendToVar list "world"
declare -p list
[[ "''${list[0]}" == "hello" ]] || (echo "first element of '\$list' was not 'hello'" && false)
[[ "''${list[1]}" == "a" ]] || (echo "first element of '\$list' was not 'a'" && false)
[[ "''${list[-1]}" == "world" ]] || (echo "last element of '\$list' was not 'world'" && false)
'';
};
};
test-concat-to = testConcatTo {
name = "test-concat-to-structuredAttrsByDefault";
stdenv' = bootStdenvStructuredAttrsByDefault;
extraAttrs = {
# test that whitespace is kept in the bash array for structuredAttrs
listWithSpaces = [
"c c"
"d d"
];
extraTest = ''
declare -a flagsWithSpaces
concatTo flagsWithSpaces string listWithSpaces
declare -p flagsWithSpaces
[[ "''${flagsWithSpaces[0]}" == "a" ]] || (echo "'\$flagsWithSpaces[0]' was not 'a'" && false)
[[ "''${flagsWithSpaces[1]}" == "*" ]] || (echo "'\$flagsWithSpaces[1]' was not '*'" && false)
[[ "''${flagsWithSpaces[2]}" == "c c" ]] || (echo "'\$flagsWithSpaces[2]' was not 'c c'" && false)
[[ "''${flagsWithSpaces[3]}" == "d d" ]] || (echo "'\$flagsWithSpaces[3]' was not 'd d'" && false)
'';
};
};
test-concat-strings-sep = testConcatStringsSep {
name = "test-concat-strings-sep-structuredAttrsByDefault";
stdenv' = bootStdenvStructuredAttrsByDefault;
};
test-golden-example-structuredAttrs =
let
goldenSh = earlyPkgs.writeText "goldenSh" ''
declare -A EXAMPLE_ATTRS=(['foo']='bar' )
declare EXAMPLE_BOOL_FALSE=
declare EXAMPLE_BOOL_TRUE=1
declare EXAMPLE_INT=123
declare EXAMPLE_INT_NEG=-123
declare -a EXAMPLE_LIST=('foo' 'bar' )
declare EXAMPLE_STR='foo bar'
'';
goldenJson = earlyPkgs.writeText "goldenSh" ''
{
"EXAMPLE_ATTRS": {
"foo": "bar"
},
"EXAMPLE_BOOL_FALSE": false,
"EXAMPLE_BOOL_TRUE": true,
"EXAMPLE_INT": 123,
"EXAMPLE_INT_NEG": -123,
"EXAMPLE_LIST": [
"foo",
"bar"
],
"EXAMPLE_NESTED_ATTRS": {
"foo": {
"bar": "baz"
}
},
"EXAMPLE_NESTED_LIST": [
[
"foo",
"bar"
],
[
"baz"
]
],
"EXAMPLE_STR": "foo bar"
}
'';
in
bootStdenvStructuredAttrsByDefault.mkDerivation {
name = "test-golden-example-structuredAttrsByDefault";
nativeBuildInputs = [ earlyPkgs.jq ];
EXAMPLE_BOOL_TRUE = true;
EXAMPLE_BOOL_FALSE = false;
EXAMPLE_INT = 123;
EXAMPLE_INT_NEG = -123;
EXAMPLE_STR = "foo bar";
EXAMPLE_LIST = [
"foo"
"bar"
];
EXAMPLE_NESTED_LIST = [
[
"foo"
"bar"
]
[ "baz" ]
];
EXAMPLE_ATTRS = {
foo = "bar";
};
EXAMPLE_NESTED_ATTRS = {
foo.bar = "baz";
};
inherit goldenSh;
inherit goldenJson;
buildCommand = ''
mkdir -p $out
cat $NIX_ATTRS_SH_FILE | grep "EXAMPLE" | grep -v -E 'installPhase|jq' > $out/sh
jq 'with_entries(select(.key|match("EXAMPLE")))' $NIX_ATTRS_JSON_FILE > $out/json
diff $out/sh $goldenSh
diff $out/json $goldenJson
'';
};
};
}