Files
nixpkgs/pkgs/test/stdenv/no-broken-symlinks.nix
2025-07-24 14:58:18 +02:00

330 lines
11 KiB
Nix

{
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;
};
}