push sheeet
Some checks failed
Periodic Merges (6h) / master → staging-nixos (push) Failing after 12m50s
Periodic Merges (6h) / master → staging-next (push) Failing after 12m54s
Periodic Merges (24h) / merge-base(master,staging) → haskell-updates (push) Failing after 11m54s
Periodic Merges (6h) / staging-next → staging (push) Failing after 12m13s
Periodic Merges (24h) / staging-next-25.05 → staging-25.05 (push) Failing after 13m24s
Periodic Merges (24h) / release-25.05 → staging-next-25.05 (push) Failing after 14m28s

This commit is contained in:
Dark Steveneq
2025-10-09 14:15:47 +02:00
commit 646b892680
49168 changed files with 5897842 additions and 0 deletions

View File

@@ -0,0 +1,597 @@
# To run these tests:
# nix-build -A tests.stdenv
{
stdenv,
pkgs,
lib,
testers,
}:
let
# early enough not to rebuild gcc but late enough to have patchelf
earlyPkgs = stdenv.__bootPackages.stdenv.__bootPackages;
earlierPkgs =
stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages;
# use a early stdenv so when hacking on stdenv this test can be run quickly
bootStdenv =
stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv;
pkgsStructured = import pkgs.path {
config = {
structuredAttrsByDefault = true;
};
inherit (stdenv.hostPlatform) system;
};
bootStdenvStructuredAttrsByDefault =
pkgsStructured.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.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
'';
};
in
{
# tests for hooks in `stdenv.defaultNativeBuildInputs`
hooks = lib.recurseIntoAttrs (
import ./hooks.nix {
stdenv = bootStdenv;
pkgs = earlyPkgs;
inherit lib;
}
);
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-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;
};
ensure-no-execve-in-setup-sh =
derivation {
name = "ensure-no-execve-in-setup-sh";
inherit (stdenv.hostPlatform) system;
builder = "${stdenv.bootstrapTools}/bin/bash";
PATH = "${pkgs.strace}/bin:${stdenv.bootstrapTools}/bin";
initialPath = [
stdenv.bootstrapTools
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 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
'';
};
};
}

View File

@@ -0,0 +1,53 @@
# This test *must* be run prior to releasing any build of either stdenv or the
# gcc that it exports! This check should also be part of CI for any PR that
# causes a rebuild of `stdenv.cc`.
#
# When we used gcc's internal bootstrap it did this check as part of (and
# serially with) the gcc derivation. Now that we bootstrap externally this
# check can be done in parallel with any/all of stdenv's referrers. But we
# must remember to do the check.
#
{
stdenv,
pkgs,
lib,
}:
with pkgs;
# rebuild gcc using the "final" stdenv
let
gcc-stageCompare =
(gcc-unwrapped.override {
reproducibleBuild = true;
profiledCompiler = false;
stdenv = overrideCC stdenv (wrapCCWith {
cc = stdenv.cc;
});
}).overrideAttrs
(_: {
NIX_OUTPATH_USED_AS_RANDOM_SEED = stdenv.cc.cc.out;
});
in
(runCommand "gcc-stageCompare"
{
checksumCompare =
assert lib.assertMsg (gcc-stageCompare ? checksum)
"tests-stdenv-gcc-stageCompare: No `checksum` output in `gcc-stageCompare` see conditional in `gcc/common/checksum.nix`";
gcc-stageCompare.checksum;
checksumUnwrapped =
assert lib.assertMsg (pkgs.gcc-unwrapped ? checksum)
"tests-stdenv-gcc-stageCompare: No `checksum` output in `gcc-stageCompare` see conditional in `gcc/common/checksum.nix`";
pkgs.gcc-unwrapped.checksum;
}
''
diff -sr "$checksumUnwrapped"/checksums "$checksumCompare"/checksums && touch $out
''
).overrideAttrs
(a: {
meta = (a.meta or { }) // {
platforms = lib.platforms.linux;
};
})

146
pkgs/test/stdenv/hooks.nix Normal file
View File

@@ -0,0 +1,146 @@
{
stdenv,
pkgs,
lib,
}:
# ordering should match defaultNativeBuildInputs
{
no-broken-symlinks = lib.recurseIntoAttrs (
import ./no-broken-symlinks.nix { inherit stdenv lib pkgs; }
);
# TODO: add audit-tmpdir
compress-man-pages =
let
manFile = pkgs.writeText "small-man" ''
.TH HELLO "1" "May 2022" "hello 2.12.1" "User Commands"
.SH NAME
hello - friendly greeting program
'';
in
stdenv.mkDerivation {
name = "test-compress-man-pages";
buildCommand = ''
mkdir -p $out/share/man
cp ${manFile} $out/share/man/small-man.1
compressManPages $out
[[ -e $out/share/man/small-man.1.gz ]]
'';
};
make-symlinks-relative = stdenv.mkDerivation {
name = "test-make-symlinks-relative";
outputs = [
"out"
"man"
];
buildCommand = ''
mkdir -p $out/{bar,baz}
mkdir -p $man/share/{x,y}
source1="$out/bar/foo"
destination1="$out/baz/foo"
source2="$man/share/x/file1"
destination2="$man/share/y/file2"
echo foo > $source1
echo foo > $source2
ln -s $source1 $destination1
ln -s $source2 $destination2
echo "symlink before patching: $(readlink $destination1)"
echo "symlink before patching: $(readlink $destination2)"
_makeSymlinksRelativeInAllOutputs
echo "symlink after patching: $(readlink $destination1)"
([[ -e $destination1 ]] && echo "symlink isn't broken") || (echo "symlink is broken" && exit 1)
([[ $(readlink $destination1) == "../bar/foo" ]] && echo "absolute symlink was made relative") || (echo "symlink was not made relative" && exit 1)
echo "symlink after patching: $(readlink $destination2)"
([[ -e $destination2 ]] && echo "symlink isn't broken") || (echo "symlink is broken" && exit 1)
([[ $(readlink $destination2) == "../x/file1" ]] && echo "absolute symlink was made relative") || (echo "symlink was not made relative" && exit 1)
'';
};
move-docs = stdenv.mkDerivation {
name = "test-move-docs";
buildCommand = ''
mkdir -p $out/{man,doc,info}
touch $out/{man,doc,info}/foo
cat $out/{man,doc,info}/foo
_moveToShare
(cat $out/share/{man,doc,info}/foo 2>/dev/null && echo "man,doc,info were moved") || (echo "man,doc,info were not moved" && exit 1)
'';
};
move-lib64 = stdenv.mkDerivation {
name = "test-move-lib64";
buildCommand = ''
mkdir -p $out/lib64
touch $out/lib64/foo
cat $out/lib64/foo
_moveLib64
# check symlink
[[ -h $out/lib64 ]]
([[ -e $out/lib64 ]] && echo "symlink isn't broken") || (echo "symlink is broken" && exit 1)
[[ -e $out/lib/foo ]]
'';
};
move-sbin = stdenv.mkDerivation {
name = "test-move-sbin";
buildCommand = ''
mkdir -p $out/sbin
touch $out/sbin/foo
cat $out/sbin/foo
_moveSbin
# check symlink
[[ -h $out/sbin ]]
([[ -e $out/sbin ]] && echo "symlink isn't broken") || (echo "symlink is broken" && exit 1)
[[ -e $out/bin/foo ]]
'';
};
# TODO: add multiple-outputs
patch-shebangs = import ./patch-shebangs.nix { inherit stdenv lib pkgs; };
prune-libtool-files =
let
libFoo = pkgs.writeText "libFoo" ''
# Generated by libtool (GNU libtool) 2.4.6
old_library='''
dependency_libs=' -Lbar.la -Lbaz.la'
'';
in
stdenv.mkDerivation {
name = "test-prune-libtool-files";
buildCommand = ''
mkdir -p $out/lib
cp ${libFoo} $out/lib/libFoo.la
_pruneLibtoolFiles
grep "^dependency_libs=''' #pruned" $out/lib/libFoo.la
# confirm file doesn't only contain the above
grep "^old_library='''" $out/lib/libFoo.la
'';
};
reproducible-builds = stdenv.mkDerivation {
name = "test-reproducible-builds";
buildCommand = ''
# can't be tested more precisely because the value of random-seed changes depending on the output
[[ $NIX_CFLAGS_COMPILE =~ "-frandom-seed=" ]]
touch $out
'';
};
set-source-date-epoch-to-latest = stdenv.mkDerivation {
name = "test-set-source-date-epoch-to-latest";
buildCommand = ''
sourceRoot=$NIX_BUILD_TOP/source
mkdir -p $sourceRoot
touch --date=1/1/2015 $sourceRoot/foo
_updateSourceDateEpochFromSourceRoot
[[ $SOURCE_DATE_EPOCH == "1420070400" ]]
touch $out
'';
};
# TODO: add strip
}

View File

@@ -0,0 +1,329 @@
{
lib,
pkgs,
stdenv,
}:
let
inherit (lib.strings) concatStringsSep optionalString;
inherit (pkgs) runCommand;
inherit (pkgs.testers) testBuildFailure;
mkDanglingSymlink = absolute: ''
ln -s${optionalString (!absolute) "r"} "$out/dangling" "$out/dangling-symlink"
'';
mkReflexiveSymlink = absolute: ''
ln -s${optionalString (!absolute) "r"} "$out/reflexive-symlink" "$out/reflexive-symlink"
'';
# Some platforms implement permissions for symlinks, while others - including Linux - ignore them.
# As a result, testing this hook's handling of unreadable symlinks requires careful attention to
# which kind of platform we're on. See the comments by `lib.optionalAttrs` below for details.
hasSymlinkPermissions = with stdenv.hostPlatform; isDarwin || isBSD;
mkUnreadableSymlink = absolute: ''
touch "$out/unreadable-symlink-target"
(
umask 777
ln -s${optionalString (!absolute) "r"} "$out/unreadable-symlink-target" "$out/unreadable-symlink"
)
'';
mkValidSymlink = absolute: ''
touch "$out/valid"
ln -s${optionalString (!absolute) "r"} "$out/valid" "$out/valid-symlink"
'';
mkValidSymlinkOutsideNixStore = absolute: ''
ln -s${optionalString (!absolute) "r"} "/etc/my_file" "$out/valid-symlink"
'';
testBuilder =
{
name,
commands ? [ ],
derivationArgs ? { },
}:
stdenv.mkDerivation (
{
inherit name;
strictDeps = true;
dontUnpack = true;
dontPatch = true;
dontConfigure = true;
dontBuild = true;
installPhase = ''
mkdir -p "$out"
''
+ concatStringsSep "\n" commands;
}
// derivationArgs
);
in
{
fail-dangling-symlink-relative =
runCommand "fail-dangling-symlink-relative"
{
failed = testBuildFailure (testBuilder {
name = "fail-dangling-symlink-relative-inner";
commands = [ (mkDanglingSymlink false) ];
});
}
''
(( 1 == "$(cat "$failed/testBuildFailure.exit")" ))
grep -F 'found 1 dangling symlinks, 0 reflexive symlinks and 0 unreadable symlinks' "$failed/testBuildFailure.log"
touch $out
'';
pass-dangling-symlink-relative-allowed = testBuilder {
name = "pass-dangling-symlink-relative-allowed";
commands = [ (mkDanglingSymlink false) ];
derivationArgs.dontCheckForBrokenSymlinks = true;
};
fail-dangling-symlink-absolute =
runCommand "fail-dangling-symlink-absolute"
{
failed = testBuildFailure (testBuilder {
name = "fail-dangling-symlink-absolute-inner";
commands = [ (mkDanglingSymlink true) ];
});
}
''
(( 1 == "$(cat "$failed/testBuildFailure.exit")" ))
grep -F 'found 1 dangling symlinks, 0 reflexive symlinks and 0 unreadable symlinks' "$failed/testBuildFailure.log"
touch $out
'';
pass-dangling-symlink-absolute-allowed = testBuilder {
name = "pass-dangling-symlink-absolute-allowed";
commands = [ (mkDanglingSymlink true) ];
derivationArgs.dontCheckForBrokenSymlinks = true;
};
fail-reflexive-symlink-relative =
runCommand "fail-reflexive-symlink-relative"
{
failed = testBuildFailure (testBuilder {
name = "fail-reflexive-symlink-relative-inner";
commands = [ (mkReflexiveSymlink false) ];
});
}
''
(( 1 == "$(cat "$failed/testBuildFailure.exit")" ))
grep -F 'found 0 dangling symlinks, 1 reflexive symlinks and 0 unreadable symlinks' "$failed/testBuildFailure.log"
touch $out
'';
pass-reflexive-symlink-relative-allowed = testBuilder {
name = "pass-reflexive-symlink-relative-allowed";
commands = [ (mkReflexiveSymlink false) ];
derivationArgs.dontCheckForBrokenSymlinks = true;
};
fail-reflexive-symlink-absolute =
runCommand "fail-reflexive-symlink-absolute"
{
failed = testBuildFailure (testBuilder {
name = "fail-reflexive-symlink-absolute-inner";
commands = [ (mkReflexiveSymlink true) ];
});
}
''
(( 1 == "$(cat "$failed/testBuildFailure.exit")" ))
grep -F 'found 0 dangling symlinks, 1 reflexive symlinks and 0 unreadable symlinks' "$failed/testBuildFailure.log"
touch $out
'';
pass-reflexive-symlink-absolute-allowed = testBuilder {
name = "pass-reflexive-symlink-absolute-allowed";
commands = [ (mkReflexiveSymlink true) ];
derivationArgs.dontCheckForBrokenSymlinks = true;
};
# Leave the unreadable symlink out of the combined 'broken' test since it doesn't work on all platforms.
fail-broken-symlinks-relative =
runCommand "fail-broken-symlinks-relative"
{
failed = testBuildFailure (testBuilder {
name = "fail-broken-symlinks-relative-inner";
commands = [
(mkDanglingSymlink false)
(mkReflexiveSymlink false)
];
});
}
''
(( 1 == "$(cat "$failed/testBuildFailure.exit")" ))
grep -F 'found 1 dangling symlinks, 1 reflexive symlinks and 0 unreadable symlinks' "$failed/testBuildFailure.log"
touch $out
'';
pass-broken-symlinks-relative-allowed = testBuilder {
name = "pass-broken-symlinks-relative-allowed";
commands = [
(mkDanglingSymlink false)
(mkReflexiveSymlink false)
];
derivationArgs.dontCheckForBrokenSymlinks = true;
};
fail-broken-symlinks-absolute =
runCommand "fail-broken-symlinks-absolute"
{
failed = testBuildFailure (testBuilder {
name = "fail-broken-symlinks-absolute-inner";
commands = [
(mkDanglingSymlink true)
(mkReflexiveSymlink true)
];
});
}
''
(( 1 == "$(cat "$failed/testBuildFailure.exit")" ))
grep -F 'found 1 dangling symlinks, 1 reflexive symlinks and 0 unreadable symlinks' "$failed/testBuildFailure.log"
touch $out
'';
pass-broken-symlinks-absolute-allowed = testBuilder {
name = "pass-broken-symlinks-absolute-allowed";
commands = [
(mkDanglingSymlink true)
(mkReflexiveSymlink true)
];
derivationArgs.dontCheckForBrokenSymlinks = true;
};
# The `all-broken` tests include unreadable symlinks along with the other kinds of broken links.
# They should be run/skipped on the same sets platforms as the corresponding `unreadable` tests.
# See below.
pass-valid-symlink-relative = testBuilder {
name = "pass-valid-symlink-relative";
commands = [ (mkValidSymlink false) ];
};
pass-valid-symlink-absolute = testBuilder {
name = "pass-valid-symlink-absolute";
commands = [ (mkValidSymlink true) ];
};
pass-valid-symlink-outside-nix-store-relative = testBuilder {
name = "pass-valid-symlink-outside-nix-store-relative";
commands = [ (mkValidSymlinkOutsideNixStore false) ];
};
pass-valid-symlink-outside-nix-store-absolute = testBuilder {
name = "pass-valid-symlink-outside-nix-store-absolute";
commands = [ (mkValidSymlinkOutsideNixStore true) ];
};
}
# Skip these tests if symlink permissions are not supported, since the hook won't have anything to report.
// lib.optionalAttrs hasSymlinkPermissions {
fail-unreadable-symlink-relative =
runCommand "fail-unreadable-symlink-relative"
{
failed = testBuildFailure (testBuilder {
name = "fail-unreadable-symlink-relative-inner";
commands = [ (mkUnreadableSymlink false) ];
});
}
''
(( 1 == "$(cat "$failed/testBuildFailure.exit")" ))
grep -F 'found 0 dangling symlinks, 0 reflexive symlinks and 1 unreadable symlinks' "$failed/testBuildFailure.log"
touch $out
'';
fail-unreadable-symlink-absolute =
runCommand "fail-unreadable-symlink-absolute"
{
failed = testBuildFailure (testBuilder {
name = "fail-unreadable-symlink-absolute-inner";
commands = [ (mkUnreadableSymlink true) ];
});
}
''
(( 1 == "$(cat "$failed/testBuildFailure.exit")" ))
grep -F 'found 0 dangling symlinks, 0 reflexive symlinks and 1 unreadable symlinks' "$failed/testBuildFailure.log"
touch $out
'';
fail-all-broken-symlinks-relative =
runCommand "fail-all-broken-symlinks-relative"
{
failed = testBuildFailure (testBuilder {
name = "fail-all-broken-symlinks-relative-inner";
commands = [
(mkDanglingSymlink false)
(mkReflexiveSymlink false)
(mkUnreadableSymlink false)
];
});
}
''
(( 1 == "$(cat "$failed/testBuildFailure.exit")" ))
if ! grep -F 'found 1 dangling symlinks, 1 reflexive symlinks and 1 unreadable symlinks' "$failed/testBuildFailure.log"; then
grep -F 'symlink permissions not supported' "$failed/testBuildFailure.log"
grep -F 'found 1 dangling symlinks, 1 reflexive symlinks and 0 unreadable symlinks' "$failed/testBuildFailure.log"
fi
touch $out
'';
fail-all-broken-symlinks-absolute =
runCommand "fail-all-broken-symlinks-absolute"
{
failed = testBuildFailure (testBuilder {
name = "fail-all-broken-symlinks-absolute-inner";
commands = [
(mkDanglingSymlink true)
(mkReflexiveSymlink true)
(mkUnreadableSymlink true)
];
});
}
''
(( 1 == "$(cat "$failed/testBuildFailure.exit")" ))
if ! grep -F 'found 1 dangling symlinks, 1 reflexive symlinks and 1 unreadable symlinks' "$failed/testBuildFailure.log"; then
grep -F 'symlink permissions not supported' "$failed/testBuildFailure.log"
grep -F 'found 1 dangling symlinks, 1 reflexive symlinks and 0 unreadable symlinks' "$failed/testBuildFailure.log"
fi
touch $out
'';
}
# These tests will break on platforms that do use symlink permissions, because even though this hook will be okay, later ones will error out.
# They should be safe to run on other platforms, just to make sure the hook isn't completely broken. It won't have anything to report, though.
// lib.optionalAttrs (!hasSymlinkPermissions) {
pass-unreadable-symlink-relative-allowed = testBuilder {
name = "pass-unreadable-symlink-relative-allowed";
commands = [ (mkUnreadableSymlink false) ];
derivationArgs.dontCheckForBrokenSymlinks = true;
};
pass-unreadable-symlink-absolute-allowed = testBuilder {
name = "pass-unreadable-symlink-absolute-allowed";
commands = [ (mkUnreadableSymlink true) ];
derivationArgs.dontCheckForBrokenSymlinks = true;
};
pass-all-broken-symlinks-relative-allowed = testBuilder {
name = "pass-all-broken-symlinks-relative-allowed";
commands = [
(mkDanglingSymlink false)
(mkReflexiveSymlink false)
(mkUnreadableSymlink false)
];
derivationArgs.dontCheckForBrokenSymlinks = true;
};
pass-all-broken-symlinks-absolute-allowed = testBuilder {
name = "pass-all-broken-symlinks-absolute-allowed";
commands = [
(mkDanglingSymlink true)
(mkReflexiveSymlink true)
(mkUnreadableSymlink true)
];
derivationArgs.dontCheckForBrokenSymlinks = true;
};
}

View File

@@ -0,0 +1,300 @@
{
lib,
stdenv,
pkgs,
}:
# since the tests are using a early stdenv, the stdenv will have dontPatchShebangs=1, so it has to be unset
# https://github.com/NixOS/nixpkgs/blob/768a982bfc9d29a6bd3beb963ed4b054451ce3d0/pkgs/stdenv/linux/default.nix#L148-L153
# strictDeps has to be disabled because the shell isn't in buildInputs
let
tests = {
bad-shebang = stdenv.mkDerivation {
name = "bad-shebang";
strictDeps = false;
dontUnpack = true;
installPhase = ''
mkdir -p $out/bin
echo "#!/bin/bash" > $out/bin/test
echo "echo -n hello" >> $out/bin/test
chmod +x $out/bin/test
dontPatchShebangs=
'';
passthru = {
assertion = "grep '^#!${stdenv.shell}' $out/bin/test > /dev/null";
};
};
ignores-nix-store = stdenv.mkDerivation {
name = "ignores-nix-store";
strictDeps = false;
dontUnpack = true;
installPhase = ''
mkdir -p $out/bin
echo "#!$NIX_STORE/path/to/bash" > $out/bin/test
echo "echo -n hello" >> $out/bin/test
chmod +x $out/bin/test
dontPatchShebangs=
'';
passthru = {
assertion = "grep \"^#!$NIX_STORE/path/to/bash\" $out/bin/test > /dev/null";
};
};
updates-nix-store = stdenv.mkDerivation {
name = "updates-nix-store";
strictDeps = false;
dontUnpack = true;
installPhase = ''
mkdir -p $out/bin
echo "#!$NIX_STORE/path/to/bash" > $out/bin/test
echo "echo -n hello" >> $out/bin/test
chmod +x $out/bin/test
patchShebangs --update $out/bin/test
dontPatchShebangs=1
'';
passthru = {
assertion = "grep '^#!${stdenv.shell}' $out/bin/test > /dev/null";
};
};
split-string = stdenv.mkDerivation {
name = "split-string";
strictDeps = false;
dontUnpack = true;
installPhase = ''
mkdir -p $out/bin
echo "#!/usr/bin/env -S bash --posix" > $out/bin/test
echo "echo -n hello" >> $out/bin/test
chmod +x $out/bin/test
dontPatchShebangs=
'';
passthru = {
assertion = "grep -v '^#!${pkgs.coreutils}/bin/env -S ${stdenv.shell} --posix' $out/bin/test > /dev/null";
};
};
without-trailing-newline = stdenv.mkDerivation {
name = "without-trailing-newline";
strictDeps = false;
dontUnpack = true;
installPhase = ''
mkdir -p $out/bin
printf "#!/bin/bash" > $out/bin/test
chmod +x $out/bin/test
dontPatchShebangs=
'';
passthru = {
assertion = "grep '^#!${stdenv.shell}' $out/bin/test > /dev/null";
};
};
dont-patch-builtins = stdenv.mkDerivation {
name = "dont-patch-builtins";
strictDeps = false;
dontUnpack = true;
installPhase = ''
mkdir -p $out/bin
echo "#!/usr/bin/builtin" > $out/bin/test
chmod +x $out/bin/test
dontPatchShebangs=
'';
passthru = {
assertion = "grep '^#!/usr/bin/builtin' $out/bin/test > /dev/null";
};
};
read-only-script =
(derivation {
name = "read-only-script";
system = stdenv.buildPlatform.system;
builder = "${stdenv.__bootPackages.stdenv.__bootPackages.bashNonInteractive}/bin/bash";
initialPath = [
stdenv.__bootPackages.stdenv.__bootPackages.coreutils
];
strictDeps = false;
args = [
"-c"
''
set -euo pipefail
. ${../../stdenv/generic/setup.sh}
. ${../../build-support/setup-hooks/patch-shebangs.sh}
mkdir -p $out/bin
echo "#!/bin/bash" > $out/bin/test
echo "echo -n hello" >> $out/bin/test
chmod 555 $out/bin/test
patchShebangs $out/bin/test
''
];
assertion = "grep '^#!${stdenv.shell}' $out/bin/test > /dev/null";
})
// {
meta = { };
};
preserves-read-only =
(derivation {
name = "preserves-read-only";
system = stdenv.buildPlatform.system;
builder = "${stdenv.__bootPackages.stdenv.__bootPackages.bashNonInteractive}/bin/bash";
initialPath = [
stdenv.__bootPackages.stdenv.__bootPackages.coreutils
];
strictDeps = false;
args = [
"-c"
''
set -euo pipefail
. ${../../stdenv/generic/setup.sh}
. ${../../build-support/setup-hooks/patch-shebangs.sh}
mkdir -p $out/bin
echo "#!/bin/bash" > $out/bin/test
echo "echo -n hello" >> $out/bin/test
chmod 555 $out/bin/test
original_perms=$(stat -c %a $out/bin/test)
patchShebangs $out/bin/test
new_perms=$(stat -c %a $out/bin/test)
if ! [ "$original_perms" = "$new_perms" ]; then
echo "Permissions changed from $original_perms to $new_perms"
exit 1
fi
''
];
assertion = "grep '^#!${stdenv.shell}' $out/bin/test > /dev/null";
})
// {
meta = { };
};
# Preserve times, see: https://github.com/NixOS/nixpkgs/pull/33281
preserves-timestamp =
(derivation {
name = "preserves-timestamp";
system = stdenv.buildPlatform.system;
builder = "${stdenv.__bootPackages.stdenv.__bootPackages.bashNonInteractive}/bin/bash";
initialPath = [
stdenv.__bootPackages.stdenv.__bootPackages.coreutils
];
strictDeps = false;
args = [
"-c"
''
set -euo pipefail
. ${../../stdenv/generic/setup.sh}
. ${../../build-support/setup-hooks/patch-shebangs.sh}
mkdir -p $out/bin
echo "#!/bin/bash" > $out/bin/test
echo "echo -n hello" >> $out/bin/test
chmod +x $out/bin/test
# Set a specific timestamp (2000-01-01 00:00:00)
touch -t 200001010000 $out/bin/test
original_timestamp=$(stat -c %Y $out/bin/test)
patchShebangs $out/bin/test
new_timestamp=$(stat -c %Y $out/bin/test)
if ! [ "$original_timestamp" = "$new_timestamp" ]; then
echo "Timestamp changed from $original_timestamp to $new_timestamp"
exit 1
fi
''
];
assertion = "grep '^#!${stdenv.shell}' $out/bin/test > /dev/null";
})
// {
meta = { };
};
preserves-binary-data =
(derivation {
name = "preserves-binary-data";
system = stdenv.buildPlatform.system;
builder = "${stdenv.__bootPackages.stdenv.__bootPackages.bashNonInteractive}/bin/bash";
initialPath = [
stdenv.__bootPackages.stdenv.__bootPackages.coreutils
];
strictDeps = false;
args = [
"-c"
''
set -euo pipefail
. ${../../stdenv/generic/setup.sh}
. ${../../build-support/setup-hooks/patch-shebangs.sh}
mkdir -p $out/bin
# Create a script with binary data after the shebang
echo "#!/bin/bash" > $out/bin/test
echo "echo 'script start'" >> $out/bin/test
# Add some binary data (null bytes and other non-printable chars)
printf '\x00\x01\x02\xff\xfe' >> $out/bin/test
echo >> $out/bin/test
echo "echo 'script end'" >> $out/bin/test
chmod +x $out/bin/test
patchShebangs $out/bin/test
# Verify binary data is still present by checking file size and content
if ! printf '\x00\x01\x02\xff\xfe' | cmp -s - <(sed -n '3p' $out/bin/test | tr -d '\n'); then
echo "Binary data corrupted during patching"
exit 1
fi
''
];
assertion = "grep '^#!${stdenv.shell}' $out/bin/test > /dev/null";
})
// {
meta = { };
};
};
in
stdenv.mkDerivation {
name = "test-patch-shebangs";
passthru = {
inherit (tests)
bad-shebang
ignores-nix-store
updates-nix-store
split-string
without-trailing-newline
dont-patch-builtins
read-only-script
preserves-read-only
preserves-timestamp
preserves-binary-data
;
};
buildCommand = ''
validate() {
local name=$1
local testout=$2
local assertion=$3
echo -n "... $name: " >&2
local rc=0
(out=$testout eval "$assertion") || rc=1
if [ "$rc" -eq 0 ]; then
echo "yes" >&2
else
echo "no" >&2
fi
return "$rc"
}
echo "checking whether patchShebangs works properly... ">&2
fail=
${lib.concatStringsSep "\n" (
lib.mapAttrsToList (_: test: ''
validate "${test.name}" "${test}" ${lib.escapeShellArg test.assertion} || fail=1
'') tests
)}
if [ "$fail" ]; then
echo "failed"
exit 1
else
echo "succeeded"
touch $out
fi
'';
}