mirror of
https://github.com/CHN-beta/nixpkgs.git
synced 2026-01-12 02:40:31 +08:00
[Backport release-25.05] arrayUtilities: init (#416144)
This commit is contained in:
@@ -62,6 +62,7 @@
|
||||
/pkgs/build-support/cc-wrapper @Ericson2314
|
||||
/pkgs/build-support/bintools-wrapper @Ericson2314
|
||||
/pkgs/build-support/setup-hooks @Ericson2314
|
||||
/pkgs/build-support/setup-hooks/arrayUtilities @ConnorBaker
|
||||
/pkgs/build-support/setup-hooks/auto-patchelf.sh @layus
|
||||
/pkgs/by-name/au/auto-patchelf @layus
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# shellcheck shell=bash
|
||||
|
||||
# getSortedMapKeys
|
||||
# Stores the sorted keys of the input associative array referenced by inputMapRef in the indexed arrray referenced by
|
||||
# outputArrRef.
|
||||
#
|
||||
# Note from the Bash manual on arrays:
|
||||
# There is no maximum limit on the size of an array, nor any requirement that members be indexed or assigned contiguously.
|
||||
# - https://www.gnu.org/software/bash/manual/html_node/Arrays.html
|
||||
#
|
||||
# Since no guarantees are made about the order in which associative maps are traversed, this function is primarly
|
||||
# useful for getting rid of yet another source of non-determinism. As an added benefit, it checks that the arguments
|
||||
# provided are of correct type, unlike native parameter expansion which will accept expansions of strings.
|
||||
#
|
||||
# Arguments:
|
||||
# - inputMapRef: a reference to an associative array (not mutated)
|
||||
# - outputArrRef: a reference to an indexed array (contents are replaced entirely)
|
||||
#
|
||||
# Returns 0.
|
||||
getSortedMapKeys() {
|
||||
if (($# != 2)); then
|
||||
nixErrorLog "expected two arguments!"
|
||||
nixErrorLog "usage: getSortedMapKeys inputMapRef outputArrRef"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local -rn inputMapRef="$1"
|
||||
# shellcheck disable=SC2178
|
||||
# Don't warn about outputArrRef being used as an array because it is an array.
|
||||
local -rn outputArrRef="$2"
|
||||
|
||||
if ! isDeclaredMap "${!inputMapRef}"; then
|
||||
nixErrorLog "first argument inputMapRef must be a reference to an associative array"
|
||||
exit 1
|
||||
elif ! isDeclaredArray "${!outputArrRef}"; then
|
||||
nixErrorLog "second argument outputArrRef must be a reference to an indexed array"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2034
|
||||
local -a keys=("${!inputMapRef[@]}")
|
||||
sortArray keys "${!outputArrRef}"
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
callPackages,
|
||||
isDeclaredArray,
|
||||
isDeclaredMap,
|
||||
makeSetupHook,
|
||||
sortArray,
|
||||
}:
|
||||
makeSetupHook {
|
||||
name = "getSortedMapKeys";
|
||||
propagatedBuildInputs = [
|
||||
isDeclaredArray
|
||||
isDeclaredMap
|
||||
sortArray
|
||||
];
|
||||
passthru.tests = callPackages ./tests.nix { };
|
||||
meta.description = "Gets the sorted indices of an associative array";
|
||||
} ./getSortedMapKeys.bash
|
||||
@@ -0,0 +1,80 @@
|
||||
# NOTE: Tests related to getSortedMapKeys go here.
|
||||
{
|
||||
getSortedMapKeys,
|
||||
lib,
|
||||
testers,
|
||||
}:
|
||||
let
|
||||
inherit (lib.attrsets) recurseIntoAttrs;
|
||||
inherit (testers) shellcheck shfmt testEqualArrayOrMap;
|
||||
|
||||
check =
|
||||
{
|
||||
name,
|
||||
valuesMap,
|
||||
expectedArray,
|
||||
}:
|
||||
(testEqualArrayOrMap {
|
||||
inherit name valuesMap expectedArray;
|
||||
script = ''
|
||||
set -eu
|
||||
nixLog "running getSortedMapKeys with valuesMap to populate actualArray"
|
||||
getSortedMapKeys valuesMap actualArray
|
||||
'';
|
||||
}).overrideAttrs
|
||||
(prevAttrs: {
|
||||
nativeBuildInputs = prevAttrs.nativeBuildInputs or [ ] ++ [ getSortedMapKeys ];
|
||||
});
|
||||
in
|
||||
recurseIntoAttrs {
|
||||
shellcheck = shellcheck {
|
||||
name = "getSortedMapKeys";
|
||||
src = ./getSortedMapKeys.bash;
|
||||
};
|
||||
|
||||
shfmt = shfmt {
|
||||
name = "getSortedMapKeys";
|
||||
src = ./getSortedMapKeys.bash;
|
||||
};
|
||||
|
||||
empty = check {
|
||||
name = "empty";
|
||||
valuesMap = { };
|
||||
expectedArray = [ ];
|
||||
};
|
||||
|
||||
singleton = check {
|
||||
name = "singleton";
|
||||
valuesMap = {
|
||||
"apple" = "fruit";
|
||||
};
|
||||
expectedArray = [ "apple" ];
|
||||
};
|
||||
|
||||
keysAreSorted = check {
|
||||
name = "keysAreSorted";
|
||||
valuesMap = {
|
||||
"apple" = "fruit";
|
||||
"bee" = "insect";
|
||||
"carrot" = "vegetable";
|
||||
};
|
||||
expectedArray = [
|
||||
"apple"
|
||||
"bee"
|
||||
"carrot"
|
||||
];
|
||||
};
|
||||
|
||||
# NOTE: While keys can be whitespace, they cannot be null (empty).
|
||||
keysCanBeWhitespace = check {
|
||||
name = "keysCanBeWhitespace";
|
||||
valuesMap = {
|
||||
" " = 1;
|
||||
" " = 2;
|
||||
};
|
||||
expectedArray = [
|
||||
" "
|
||||
" "
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
# shellcheck shell=bash
|
||||
|
||||
# isDeclaredArray
|
||||
# Tests if inputArrayRef refers to a declared, indexed array.
|
||||
#
|
||||
# Arguments:
|
||||
# - inputArrayRef: a reference to an indexed array (not mutated)
|
||||
#
|
||||
# Returns 0 if the indexed array is declared, 1 otherwise.
|
||||
isDeclaredArray() {
|
||||
# NOTE: We must dereference the name ref to get the type of the underlying variable.
|
||||
# shellcheck disable=SC2034
|
||||
local -nr inputArrayRef="$1" && [[ ${!inputArrayRef@a} =~ a ]]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
callPackages,
|
||||
makeSetupHook,
|
||||
}:
|
||||
makeSetupHook {
|
||||
name = "isDeclaredArray";
|
||||
passthru.tests = callPackages ./tests.nix { };
|
||||
meta.description = "Tests if an array is declared";
|
||||
} ./isDeclaredArray.bash
|
||||
@@ -0,0 +1,353 @@
|
||||
# NOTE: Tests related to isDeclaredArray go here.
|
||||
{
|
||||
isDeclaredArray,
|
||||
lib,
|
||||
runCommand,
|
||||
testers,
|
||||
}:
|
||||
let
|
||||
inherit (lib.attrsets) recurseIntoAttrs;
|
||||
inherit (testers) shellcheck shfmt testBuildFailure';
|
||||
|
||||
commonArgs = {
|
||||
__structuredAttrs = true;
|
||||
strictDeps = true;
|
||||
preferLocalBuild = true;
|
||||
nativeBuildInputs = [ isDeclaredArray ];
|
||||
};
|
||||
|
||||
check =
|
||||
let
|
||||
mkLine =
|
||||
intro: values:
|
||||
"${if intro == null then "" else intro + " "}check${if values == null then "" else "=" + values}";
|
||||
mkScope =
|
||||
scope: line:
|
||||
if scope == null then
|
||||
line
|
||||
else if scope == "function" then
|
||||
''
|
||||
foo() {
|
||||
${line}
|
||||
}
|
||||
foo
|
||||
''
|
||||
else
|
||||
builtins.throw "Invalid scope: ${scope}";
|
||||
in
|
||||
{
|
||||
name,
|
||||
scope,
|
||||
intro,
|
||||
values,
|
||||
}:
|
||||
runCommand name commonArgs ''
|
||||
set -eu
|
||||
|
||||
${mkScope scope (mkLine intro values)}
|
||||
|
||||
if isDeclaredArray check; then
|
||||
nixLog "test passed"
|
||||
touch "$out"
|
||||
else
|
||||
nixErrorLog "test failed"
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
in
|
||||
recurseIntoAttrs {
|
||||
shellcheck = shellcheck {
|
||||
name = "isDeclaredArray";
|
||||
src = ./isDeclaredArray.bash;
|
||||
};
|
||||
|
||||
shfmt = shfmt {
|
||||
name = "isDeclaredArray";
|
||||
src = ./isDeclaredArray.bash;
|
||||
};
|
||||
|
||||
undeclaredFails = testBuildFailure' {
|
||||
name = "undeclaredFails";
|
||||
drv = runCommand "undeclared" commonArgs ''
|
||||
set -eu
|
||||
if isDeclaredArray undeclared; then
|
||||
nixLog "test passed"
|
||||
touch "$out"
|
||||
else
|
||||
nixErrorLog "test failed"
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
mapFails = testBuildFailure' {
|
||||
name = "mapFails";
|
||||
drv = runCommand "map" commonArgs ''
|
||||
set -eu
|
||||
local -A map
|
||||
if isDeclaredArray map; then
|
||||
nixLog "test passed"
|
||||
touch "$out"
|
||||
else
|
||||
nixErrorLog "test failed"
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
emptyStringNamerefFails = testBuildFailure' {
|
||||
name = "emptyStringNamerefFails";
|
||||
drv = runCommand "emptyStringNameref" commonArgs ''
|
||||
set -eu
|
||||
if isDeclaredArray ""; then
|
||||
nixLog "test passed"
|
||||
touch "$out"
|
||||
else
|
||||
nixErrorLog "test failed"
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
expectedBuilderLogEntries = [
|
||||
"local: `': not a valid identifier"
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
namerefToEmptyStringFails = testBuildFailure' {
|
||||
name = "namerefToEmptyStringFails";
|
||||
drv = check {
|
||||
name = "namerefToEmptyString";
|
||||
scope = null;
|
||||
intro = "local -n";
|
||||
values = "";
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"local: `': not a valid identifier"
|
||||
# The test fails in such a way that it exits immediately, without returning to the else branch.
|
||||
];
|
||||
};
|
||||
|
||||
sameScopeEmptyStringFails = testBuildFailure' {
|
||||
name = "sameScopeEmptyStringFails";
|
||||
drv = check {
|
||||
name = "sameScopeEmptyString";
|
||||
scope = null;
|
||||
intro = null;
|
||||
values = "";
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
sameScopeEmptyArray = check {
|
||||
name = "sameScopeEmptyArray";
|
||||
scope = null;
|
||||
intro = null;
|
||||
values = "()";
|
||||
};
|
||||
|
||||
sameScopeSingletonArray = check {
|
||||
name = "sameScopeSingletonArray";
|
||||
scope = null;
|
||||
intro = null;
|
||||
values = ''("hello!")'';
|
||||
};
|
||||
|
||||
sameScopeLocalUnsetArray = check {
|
||||
name = "sameScopeLocalUnsetArray";
|
||||
scope = null;
|
||||
intro = "local -a";
|
||||
values = null;
|
||||
};
|
||||
|
||||
sameScopeLocalEmptyArray = check {
|
||||
name = "sameScopeLocalEmptyArray";
|
||||
scope = null;
|
||||
intro = "local -a";
|
||||
values = "()";
|
||||
};
|
||||
|
||||
sameScopeLocalSingletonArray = check {
|
||||
name = "sameScopeLocalSingletonArray";
|
||||
scope = null;
|
||||
intro = "local -a";
|
||||
values = ''("hello!")'';
|
||||
};
|
||||
|
||||
sameScopeDeclareUnsetArray = check {
|
||||
name = "sameScopeDeclareUnsetArray";
|
||||
scope = null;
|
||||
intro = "declare -a";
|
||||
values = null;
|
||||
};
|
||||
|
||||
sameScopeDeclareEmptyArray = check {
|
||||
name = "sameScopeDeclareEmptyArray";
|
||||
scope = null;
|
||||
intro = "declare -a";
|
||||
values = "()";
|
||||
};
|
||||
|
||||
sameScopeDeclareSingletonArray = check {
|
||||
name = "sameScopeDeclareSingletonArray";
|
||||
scope = null;
|
||||
intro = "declare -a";
|
||||
values = ''("hello!")'';
|
||||
};
|
||||
|
||||
previousScopeEmptyStringFails = testBuildFailure' {
|
||||
name = "previousScopeEmptyStringFails";
|
||||
drv = check {
|
||||
name = "previousScopeEmptyString";
|
||||
scope = "function";
|
||||
intro = null;
|
||||
values = "";
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
# Works because the variable isn't lexically scoped.
|
||||
previousScopeEmptyArray = check {
|
||||
name = "previousScopeEmptyArray";
|
||||
scope = "function";
|
||||
intro = null;
|
||||
values = "()";
|
||||
};
|
||||
|
||||
# Works because the variable isn't lexically scoped.
|
||||
previousScopeSingletonArray = check {
|
||||
name = "previousScopeSingletonArray";
|
||||
scope = "function";
|
||||
intro = null;
|
||||
values = ''("hello!")'';
|
||||
};
|
||||
|
||||
previousScopeLocalUnsetArrayFails = testBuildFailure' {
|
||||
name = "previousScopeLocalUnsetArrayFails";
|
||||
drv = check {
|
||||
name = "previousScopeLocalUnsetArray";
|
||||
scope = "function";
|
||||
intro = "local -a";
|
||||
values = null;
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
previousScopeLocalEmptyArrayFails = testBuildFailure' {
|
||||
name = "previousScopeLocalEmptyArrayFails";
|
||||
drv = check {
|
||||
name = "previousScopeLocalEmptyArray";
|
||||
scope = "function";
|
||||
intro = "local -a";
|
||||
values = "()";
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
previousScopeLocalSingletonArrayFails = testBuildFailure' {
|
||||
name = "previousScopeLocalSingletonArrayFails";
|
||||
drv = check {
|
||||
name = "previousScopeLocalSingletonArray";
|
||||
scope = "function";
|
||||
intro = "local -a";
|
||||
values = ''("hello!")'';
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
previousScopeLocalGlobalUnsetArray = check {
|
||||
name = "previousScopeLocalGlobalUnsetArray";
|
||||
scope = "function";
|
||||
intro = "local -ag";
|
||||
values = null;
|
||||
};
|
||||
|
||||
previousScopeLocalGlobalEmptyArray = check {
|
||||
name = "previousScopeLocalGlobalEmptyArray";
|
||||
scope = "function";
|
||||
intro = "local -ag";
|
||||
values = "()";
|
||||
};
|
||||
|
||||
previousScopeLocalGlobalSingletonArray = check {
|
||||
name = "previousScopeLocalGlobalSingletonArray";
|
||||
scope = "function";
|
||||
intro = "local -ag";
|
||||
values = ''("hello!")'';
|
||||
};
|
||||
|
||||
previousScopeDeclareUnsetArrayFails = testBuildFailure' {
|
||||
name = "previousScopeDeclareUnsetArrayFails";
|
||||
drv = check {
|
||||
name = "previousScopeDeclareUnsetArray";
|
||||
scope = "function";
|
||||
intro = "declare -a";
|
||||
values = null;
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
previousScopeDeclareEmptyArrayFails = testBuildFailure' {
|
||||
name = "previousScopeDeclareEmptyArrayFails";
|
||||
drv = check {
|
||||
name = "previousScopeDeclareEmptyArray";
|
||||
scope = "function";
|
||||
intro = "declare -a";
|
||||
values = "()";
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
previousScopeDeclareSingletonArrayFails = testBuildFailure' {
|
||||
name = "previousScopeDeclareSingletonArrayFails";
|
||||
drv = check {
|
||||
name = "previousScopeDeclareSingletonArray";
|
||||
scope = "function";
|
||||
intro = "declare -a";
|
||||
values = ''("hello!")'';
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
previousScopeDeclareGlobalUnsetArray = check {
|
||||
name = "previousScopeDeclareGlobalUnsetArray";
|
||||
scope = "function";
|
||||
intro = "declare -ag";
|
||||
values = null;
|
||||
};
|
||||
|
||||
previousScopeDeclareGlobalEmptyArray = check {
|
||||
name = "previousScopeDeclareGlobalEmptyArray";
|
||||
scope = "function";
|
||||
intro = "declare -ag";
|
||||
values = "()";
|
||||
};
|
||||
|
||||
previousScopeDeclareGlobalSingletonArray = check {
|
||||
name = "previousScopeDeclareGlobalSingletonArray";
|
||||
scope = "function";
|
||||
intro = "declare -ag";
|
||||
values = ''("hello!")'';
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
# shellcheck shell=bash
|
||||
|
||||
# isDeclaredMap
|
||||
# Tests if inputMapRef refers to a declared, associative array.
|
||||
#
|
||||
# Arguments:
|
||||
# - inputMapRef: a reference to an associative array (not mutated)
|
||||
#
|
||||
# Returns 0 if the associative array is declared, 1 otherwise.
|
||||
isDeclaredMap() {
|
||||
# NOTE: We must dereference the name ref to get the type of the underlying variable.
|
||||
# shellcheck disable=SC2034
|
||||
local -nr inputMapRef="$1" && [[ ${!inputMapRef@a} =~ A ]]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
callPackages,
|
||||
makeSetupHook,
|
||||
}:
|
||||
makeSetupHook {
|
||||
name = "isDeclaredMap";
|
||||
passthru.tests = callPackages ./tests.nix { };
|
||||
meta.description = "Tests if an associative array is declared";
|
||||
} ./isDeclaredMap.bash
|
||||
@@ -0,0 +1,377 @@
|
||||
# NOTE: Tests related to isDeclaredMap go here.
|
||||
{
|
||||
isDeclaredMap,
|
||||
lib,
|
||||
runCommand,
|
||||
testers,
|
||||
}:
|
||||
let
|
||||
inherit (lib.attrsets) recurseIntoAttrs;
|
||||
inherit (testers) shellcheck shfmt testBuildFailure';
|
||||
|
||||
commonArgs = {
|
||||
__structuredAttrs = true;
|
||||
strictDeps = true;
|
||||
preferLocalBuild = true;
|
||||
nativeBuildInputs = [ isDeclaredMap ];
|
||||
};
|
||||
|
||||
check =
|
||||
let
|
||||
mkLine =
|
||||
intro: values:
|
||||
"${if intro == null then "" else intro + " "}check${if values == null then "" else "=" + values}";
|
||||
mkScope =
|
||||
scope: line:
|
||||
if scope == null then
|
||||
line
|
||||
else if scope == "function" then
|
||||
''
|
||||
foo() {
|
||||
${line}
|
||||
}
|
||||
foo
|
||||
''
|
||||
else
|
||||
builtins.throw "Invalid scope: ${scope}";
|
||||
in
|
||||
{
|
||||
name,
|
||||
scope,
|
||||
intro,
|
||||
values,
|
||||
}:
|
||||
runCommand name commonArgs ''
|
||||
set -eu
|
||||
|
||||
${mkScope scope (mkLine intro values)}
|
||||
|
||||
if isDeclaredMap check; then
|
||||
nixLog "test passed"
|
||||
touch "$out"
|
||||
else
|
||||
nixErrorLog "test failed"
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
in
|
||||
recurseIntoAttrs {
|
||||
shellcheck = shellcheck {
|
||||
name = "isDeclaredMap";
|
||||
src = ./isDeclaredMap.bash;
|
||||
};
|
||||
|
||||
shfmt = shfmt {
|
||||
name = "isDeclaredMap";
|
||||
src = ./isDeclaredMap.bash;
|
||||
};
|
||||
|
||||
undeclaredFails = testBuildFailure' {
|
||||
name = "undeclaredFails";
|
||||
drv = runCommand "undeclared" commonArgs ''
|
||||
set -eu
|
||||
if isDeclaredMap undeclared; then
|
||||
nixLog "test passed"
|
||||
touch "$out"
|
||||
else
|
||||
nixErrorLog "test failed"
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
arrayFails = testBuildFailure' {
|
||||
name = "arrayFails";
|
||||
drv = runCommand "array" commonArgs ''
|
||||
set -eu
|
||||
local -a array
|
||||
if isDeclaredMap array; then
|
||||
nixLog "test passed"
|
||||
touch "$out"
|
||||
else
|
||||
nixErrorLog "test failed"
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
emptyStringNamerefFails = testBuildFailure' {
|
||||
name = "emptyStringNamerefFails";
|
||||
drv = runCommand "emptyStringNameref" commonArgs ''
|
||||
set -eu
|
||||
if isDeclaredMap ""; then
|
||||
nixLog "test passed"
|
||||
touch "$out"
|
||||
else
|
||||
nixErrorLog "test failed"
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
expectedBuilderLogEntries = [
|
||||
"local: `': not a valid identifier"
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
namerefToEmptyStringFails = testBuildFailure' {
|
||||
name = "namerefToEmptyStringFails";
|
||||
drv = check {
|
||||
name = "namerefToEmptyString";
|
||||
scope = null;
|
||||
intro = "local -n";
|
||||
values = "";
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"local: `': not a valid identifier"
|
||||
# The test fails in such a way that it exits immediately, without returning to the else branch.
|
||||
];
|
||||
};
|
||||
|
||||
sameScopeEmptyStringFails = testBuildFailure' {
|
||||
name = "sameScopeEmptyStringFails";
|
||||
drv = check {
|
||||
name = "sameScopeEmptyString";
|
||||
scope = null;
|
||||
intro = null;
|
||||
values = "";
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
sameScopeEmptyMapFails = testBuildFailure' {
|
||||
name = "sameScopeEmptyMapFails";
|
||||
drv = check {
|
||||
name = "sameScopeEmptyMap";
|
||||
scope = null;
|
||||
intro = null;
|
||||
values = "()";
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
# Fails because maps must be declared with the -A flag.
|
||||
sameScopeSingletonMapFails = testBuildFailure' {
|
||||
name = "sameScopeSingletonMapFails";
|
||||
drv = check {
|
||||
name = "sameScopeSingletonMap";
|
||||
scope = null;
|
||||
intro = null;
|
||||
values = ''([greeting]="hello!")'';
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"greeting: unbound variable"
|
||||
];
|
||||
};
|
||||
|
||||
sameScopeLocalUnsetMap = check {
|
||||
name = "sameScopeLocalUnsetMap";
|
||||
scope = null;
|
||||
intro = "local -A";
|
||||
values = null;
|
||||
};
|
||||
|
||||
sameScopeLocalEmptyMap = check {
|
||||
name = "sameScopeLocalEmptyMap";
|
||||
scope = null;
|
||||
intro = "local -A";
|
||||
values = "()";
|
||||
};
|
||||
|
||||
sameScopeLocalSingletonMap = check {
|
||||
name = "sameScopeLocalSingletonMap";
|
||||
scope = null;
|
||||
intro = "local -A";
|
||||
values = ''([greeting]="hello!")'';
|
||||
};
|
||||
|
||||
sameScopeDeclareUnsetMap = check {
|
||||
name = "sameScopeDeclareUnsetMap";
|
||||
scope = null;
|
||||
intro = "declare -A";
|
||||
values = null;
|
||||
};
|
||||
|
||||
sameScopeDeclareEmptyMap = check {
|
||||
name = "sameScopeDeclareEmptyMap";
|
||||
scope = null;
|
||||
intro = "declare -A";
|
||||
values = "()";
|
||||
};
|
||||
|
||||
sameScopeDeclareSingletonMap = check {
|
||||
name = "sameScopeDeclareSingletonMap";
|
||||
scope = null;
|
||||
intro = "declare -A";
|
||||
values = ''([greeting]="hello!")'';
|
||||
};
|
||||
|
||||
previousScopeEmptyStringFails = testBuildFailure' {
|
||||
name = "previousScopeEmptyStringFails";
|
||||
drv = check {
|
||||
name = "previousScopeEmptyString";
|
||||
scope = "function";
|
||||
intro = null;
|
||||
values = "";
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
# Fails because () is ambiguous and defaults to array rather than associative array.
|
||||
previousScopeEmptyMapFails = testBuildFailure' {
|
||||
name = "previousScopeEmptyMapFails";
|
||||
drv = check {
|
||||
name = "previousScopeEmptyMap";
|
||||
scope = "function";
|
||||
intro = null;
|
||||
values = "()";
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
previousScopeSingletonMapFails = testBuildFailure' {
|
||||
name = "previousScopeSingletonMapFails";
|
||||
drv = check {
|
||||
name = "previousScopeSingletonMap";
|
||||
scope = "function";
|
||||
intro = null;
|
||||
values = ''([greeting]="hello!")'';
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"greeting: unbound variable"
|
||||
];
|
||||
};
|
||||
|
||||
previousScopeLocalUnsetMapFails = testBuildFailure' {
|
||||
name = "previousScopeLocalUnsetMapFails";
|
||||
drv = check {
|
||||
name = "previousScopeLocalUnsetMap";
|
||||
scope = "function";
|
||||
intro = "local -A";
|
||||
values = null;
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
previousScopeLocalEmptyMapFails = testBuildFailure' {
|
||||
name = "previousScopeLocalEmptyMapFails";
|
||||
drv = check {
|
||||
name = "previousScopeLocalEmptyMap";
|
||||
scope = "function";
|
||||
intro = "local -A";
|
||||
values = "()";
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
previousScopeLocalSingletonMapFails = testBuildFailure' {
|
||||
name = "previousScopeLocalSingletonMapFails";
|
||||
drv = check {
|
||||
name = "previousScopeLocalSingletonMap";
|
||||
scope = "function";
|
||||
intro = "local -A";
|
||||
values = ''([greeting]="hello!")'';
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
previousScopeLocalGlobalUnsetMap = check {
|
||||
name = "previousScopeLocalGlobalUnsetMap";
|
||||
scope = "function";
|
||||
intro = "local -Ag";
|
||||
values = null;
|
||||
};
|
||||
|
||||
previousScopeLocalGlobalEmptyMap = check {
|
||||
name = "previousScopeLocalGlobalEmptyMap";
|
||||
scope = "function";
|
||||
intro = "local -Ag";
|
||||
values = "()";
|
||||
};
|
||||
|
||||
previousScopeLocalGlobalSingletonMap = check {
|
||||
name = "previousScopeLocalGlobalSingletonMap";
|
||||
scope = "function";
|
||||
intro = "local -Ag";
|
||||
values = ''([greeting]="hello!")'';
|
||||
};
|
||||
|
||||
previousScopeDeclareUnsetMapFails = testBuildFailure' {
|
||||
name = "previousScopeDeclareUnsetMapFails";
|
||||
drv = check {
|
||||
name = "previousScopeDeclareUnsetMap";
|
||||
scope = "function";
|
||||
intro = "declare -A";
|
||||
values = null;
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
previousScopeDeclareEmptyMapFails = testBuildFailure' {
|
||||
name = "previousScopeDeclareEmptyMapFails";
|
||||
drv = check {
|
||||
name = "previousScopeDeclareEmptyMap";
|
||||
scope = "function";
|
||||
intro = "declare -A";
|
||||
values = "()";
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
previousScopeDeclareSingletonMapFails = testBuildFailure' {
|
||||
name = "previousScopeDeclareSingletonMapFails";
|
||||
drv = check {
|
||||
name = "previousScopeDeclareSingletonMap";
|
||||
scope = "function";
|
||||
intro = "declare -A";
|
||||
values = ''([greeting]="hello!")'';
|
||||
};
|
||||
expectedBuilderLogEntries = [
|
||||
"test failed"
|
||||
];
|
||||
};
|
||||
|
||||
previousScopeDeclareGlobalUnsetMap = check {
|
||||
name = "previousScopeDeclareGlobalUnsetMap";
|
||||
scope = "function";
|
||||
intro = "declare -Ag";
|
||||
values = null;
|
||||
};
|
||||
|
||||
previousScopeDeclareGlobalEmptyMap = check {
|
||||
name = "previousScopeDeclareGlobalEmptyMap";
|
||||
scope = "function";
|
||||
intro = "declare -Ag";
|
||||
values = "()";
|
||||
};
|
||||
|
||||
previousScopeDeclareGlobalSingletonMap = check {
|
||||
name = "previousScopeDeclareGlobalSingletonMap";
|
||||
scope = "function";
|
||||
intro = "declare -Ag";
|
||||
values = ''([greeting]="hello!")'';
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
callPackages,
|
||||
isDeclaredArray,
|
||||
makeSetupHook,
|
||||
}:
|
||||
makeSetupHook {
|
||||
name = "sortArray";
|
||||
propagatedBuildInputs = [ isDeclaredArray ];
|
||||
passthru.tests = callPackages ./tests.nix { };
|
||||
meta.description = "Sorts an array";
|
||||
} ./sortArray.bash
|
||||
@@ -0,0 +1,53 @@
|
||||
# shellcheck shell=bash
|
||||
|
||||
# sortArray
|
||||
# Sorts the indexed array referenced by inputArrRef and stores the result in the indexed array referenced by
|
||||
# outputArrRef.
|
||||
#
|
||||
# Arguments:
|
||||
# - inputArrRef: a reference to an indexed array (not mutated, may alias outputArrRef)
|
||||
# - outputArrRef: a reference to an indexed array (contents are replaced entirely, may alias inputArrRef)
|
||||
#
|
||||
# Returns 0.
|
||||
sortArray() {
|
||||
if (($# != 2)); then
|
||||
nixErrorLog "expected two arguments!"
|
||||
nixErrorLog "usage: sortArray inputArrRef outputArrRef"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local -rn inputArrRef="$1"
|
||||
local -rn outputArrRef="$2"
|
||||
|
||||
if ! isDeclaredArray "${!inputArrRef}"; then
|
||||
nixErrorLog "first argument inputArrRef must be a reference to an indexed array"
|
||||
exit 1
|
||||
elif ! isDeclaredArray "${!outputArrRef}"; then
|
||||
nixErrorLog "second argument outputArrRef must be a reference to an indexed array"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local -a sortedArray=()
|
||||
|
||||
# Guard on the length of the input array, as empty array will expand to nothing, but printf will still see it as an
|
||||
# argument, producing an empty string.
|
||||
if ((${#inputArrRef[@]} > 0)); then
|
||||
# NOTE from Bash's printf documentation:
|
||||
# The format is reused as necessary to consume all of the arguments. If the format requires more arguments than
|
||||
# are supplied, the extra format specifications behave as if a zero value or null string, as appropriate, had
|
||||
# been supplied.
|
||||
# - https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html#index-printf
|
||||
# NOTE from sort manpage:
|
||||
# If you use a non-POSIX locale (e.g., by setting LC_ALL to 'en_US'), then sort may produce output that is sorted
|
||||
# differently than you're accustomed to. In that case, set the LC_ALL environment variable to 'C'. Setting only
|
||||
# LC_COLLATE has two problems. First, it is ineffective if LC_ALL is also set. Second, it has undefined behavior
|
||||
# if LC_CTYPE (or LANG, if LC_CTYPE is unset) is set to an incompatible value. For example, you get undefined
|
||||
# behavior if LC_CTYPE is ja_JP.PCK but LC_COLLATE is en_US.UTF-8.
|
||||
# - https://www.gnu.org/software/coreutils/manual/html_node/sort-invocation.html#FOOT1
|
||||
mapfile -d $'\0' -t sortedArray < <(printf '%s\0' "${inputArrRef[@]}" | LC_ALL=C sort --stable --zero-terminated)
|
||||
fi
|
||||
|
||||
outputArrRef=("${sortedArray[@]}")
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
# NOTE: Tests related to sortArray go here.
|
||||
{
|
||||
lib,
|
||||
sortArray,
|
||||
testers,
|
||||
}:
|
||||
let
|
||||
inherit (lib.attrsets) recurseIntoAttrs;
|
||||
inherit (testers) shellcheck shfmt testEqualArrayOrMap;
|
||||
check =
|
||||
{
|
||||
name,
|
||||
valuesArray,
|
||||
expectedArray,
|
||||
}:
|
||||
(testEqualArrayOrMap {
|
||||
inherit name valuesArray expectedArray;
|
||||
script = ''
|
||||
set -eu
|
||||
nixLog "running sortArray with valuesArray to populate actualArray"
|
||||
sortArray valuesArray actualArray
|
||||
'';
|
||||
}).overrideAttrs
|
||||
(prevAttrs: {
|
||||
nativeBuildInputs = prevAttrs.nativeBuildInputs or [ ] ++ [ sortArray ];
|
||||
});
|
||||
|
||||
checkInPlace =
|
||||
{
|
||||
name,
|
||||
valuesArray,
|
||||
expectedArray,
|
||||
}:
|
||||
(testEqualArrayOrMap {
|
||||
inherit name valuesArray expectedArray;
|
||||
script = ''
|
||||
set -eu
|
||||
nixLog "running sortArray with valuesArray as input and output"
|
||||
sortArray valuesArray valuesArray
|
||||
nixLog "copying valuesArray to actualArray"
|
||||
actualArray=("''${valuesArray[@]}")
|
||||
'';
|
||||
}).overrideAttrs
|
||||
(prevAttrs: {
|
||||
nativeBuildInputs = prevAttrs.nativeBuildInputs or [ ] ++ [ sortArray ];
|
||||
});
|
||||
in
|
||||
recurseIntoAttrs {
|
||||
shellcheck = shellcheck {
|
||||
name = "sortArray";
|
||||
src = ./sortArray.bash;
|
||||
};
|
||||
|
||||
shfmt = shfmt {
|
||||
name = "sortArray";
|
||||
src = ./sortArray.bash;
|
||||
};
|
||||
|
||||
empty = check {
|
||||
name = "empty";
|
||||
valuesArray = [ ];
|
||||
expectedArray = [ ];
|
||||
};
|
||||
|
||||
singleton = check {
|
||||
name = "singleton";
|
||||
valuesArray = [ "apple" ];
|
||||
expectedArray = [ "apple" ];
|
||||
};
|
||||
|
||||
oneDuplicate = check {
|
||||
name = "oneDuplicate";
|
||||
valuesArray = [
|
||||
"apple"
|
||||
"apple"
|
||||
];
|
||||
expectedArray = [
|
||||
"apple"
|
||||
"apple"
|
||||
];
|
||||
};
|
||||
|
||||
oneUnique = check {
|
||||
name = "oneUnique";
|
||||
valuesArray = [
|
||||
"bee"
|
||||
"apple"
|
||||
"bee"
|
||||
];
|
||||
expectedArray = [
|
||||
"apple"
|
||||
"bee"
|
||||
"bee"
|
||||
];
|
||||
};
|
||||
|
||||
duplicatesWithSpacesAndLineBreaks = check {
|
||||
name = "duplicatesWithSpacesAndLineBreaks";
|
||||
valuesArray = [
|
||||
"dog"
|
||||
"bee"
|
||||
''
|
||||
line
|
||||
break
|
||||
''
|
||||
"cat"
|
||||
"zebra"
|
||||
"bee"
|
||||
"cat"
|
||||
"elephant"
|
||||
"dog with spaces"
|
||||
''
|
||||
line
|
||||
break
|
||||
''
|
||||
];
|
||||
expectedArray = [
|
||||
"bee"
|
||||
"bee"
|
||||
"cat"
|
||||
"cat"
|
||||
"dog"
|
||||
"dog with spaces"
|
||||
"elephant"
|
||||
# NOTE: lead whitespace is removed, so the following entries start with `l`.
|
||||
''
|
||||
line
|
||||
break
|
||||
''
|
||||
''
|
||||
line
|
||||
break
|
||||
''
|
||||
"zebra"
|
||||
];
|
||||
};
|
||||
|
||||
duplicatesWithSpacesAndLineBreaksInPlace = checkInPlace {
|
||||
name = "duplicatesWithSpacesAndLineBreaksInPlace";
|
||||
valuesArray = [
|
||||
"dog"
|
||||
"bee"
|
||||
''
|
||||
line
|
||||
break
|
||||
''
|
||||
"cat"
|
||||
"zebra"
|
||||
"bee"
|
||||
"cat"
|
||||
"elephant"
|
||||
"dog with spaces"
|
||||
''
|
||||
line
|
||||
break
|
||||
''
|
||||
];
|
||||
expectedArray = [
|
||||
"bee"
|
||||
"bee"
|
||||
"cat"
|
||||
"cat"
|
||||
"dog"
|
||||
"dog with spaces"
|
||||
"elephant"
|
||||
# NOTE: lead whitespace is removed, so the following entries start with `l`.
|
||||
''
|
||||
line
|
||||
break
|
||||
''
|
||||
''
|
||||
line
|
||||
break
|
||||
''
|
||||
"zebra"
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -1,11 +1,5 @@
|
||||
# shellcheck shell=bash
|
||||
|
||||
# Tests if an array is declared.
|
||||
isDeclaredArray() {
|
||||
# shellcheck disable=SC2034
|
||||
local -nr arrayRef="$1" && [[ ${!arrayRef@a} =~ a ]]
|
||||
}
|
||||
|
||||
# Asserts that two arrays are equal, printing out differences if they are not.
|
||||
# Does not short circuit on the first difference.
|
||||
assertEqualArray() {
|
||||
@@ -19,12 +13,12 @@ assertEqualArray() {
|
||||
local -nr actualArrayRef="$2"
|
||||
|
||||
if ! isDeclaredArray "${!expectedArrayRef}"; then
|
||||
nixErrorLog "first arugment expectedArrayRef must be an array reference to a declared array"
|
||||
nixErrorLog "first argument expectedArrayRef must be a reference to an indexed array"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! isDeclaredArray "${!actualArrayRef}"; then
|
||||
nixErrorLog "second arugment actualArrayRef must be an array reference to a declared array"
|
||||
nixErrorLog "second argument actualArrayRef must be a reference to an indexed array"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
# shellcheck shell=bash
|
||||
|
||||
# Tests if a map is declared.
|
||||
isDeclaredMap() {
|
||||
# shellcheck disable=SC2034
|
||||
local -nr mapRef="$1" && [[ ${!mapRef@a} =~ A ]]
|
||||
}
|
||||
|
||||
# Asserts that two maps are equal, printing out differences if they are not.
|
||||
# Does not short circuit on the first difference.
|
||||
assertEqualMap() {
|
||||
@@ -19,26 +13,15 @@ assertEqualMap() {
|
||||
local -nr actualMapRef="$2"
|
||||
|
||||
if ! isDeclaredMap "${!expectedMapRef}"; then
|
||||
nixErrorLog "first arugment expectedMapRef must be an associative array reference to a declared associative array"
|
||||
nixErrorLog "first argument expectedMapRef must be a reference to an associative array"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! isDeclaredMap "${!actualMapRef}"; then
|
||||
nixErrorLog "second arugment actualMapRef must be an associative array reference to a declared associative array"
|
||||
nixErrorLog "second argument actualMapRef must be a reference to an associative array"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# NOTE:
|
||||
# From the `sort` manpage: "The locale specified by the environment affects sort order. Set LC_ALL=C to get the
|
||||
# traditional sort order that uses native byte values."
|
||||
# We specify the environment variable in a subshell to avoid polluting the caller's environment.
|
||||
|
||||
local -a sortedExpectedKeys
|
||||
mapfile -d '' -t sortedExpectedKeys < <(printf '%s\0' "${!expectedMapRef[@]}" | LC_ALL=C sort --stable --zero-terminated)
|
||||
|
||||
local -a sortedActualKeys
|
||||
mapfile -d '' -t sortedActualKeys < <(printf '%s\0' "${!actualMapRef[@]}" | LC_ALL=C sort --stable --zero-terminated)
|
||||
|
||||
local -ir expectedLength=${#expectedMapRef[@]}
|
||||
local -ir actualLength=${#actualMapRef[@]}
|
||||
|
||||
@@ -49,6 +32,12 @@ assertEqualMap() {
|
||||
hasDiff=1
|
||||
fi
|
||||
|
||||
local -a sortedExpectedKeys=()
|
||||
getSortedMapKeys "${!expectedMapRef}" sortedExpectedKeys
|
||||
|
||||
local -a sortedActualKeys=()
|
||||
getSortedMapKeys "${!actualMapRef}" sortedActualKeys
|
||||
|
||||
local -i expectedKeyIdx=0
|
||||
local expectedKey
|
||||
local expectedValue
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
arrayUtilities,
|
||||
lib,
|
||||
stdenvNoCC,
|
||||
}:
|
||||
@@ -21,7 +22,10 @@ lib.makeOverridable (
|
||||
inherit name;
|
||||
|
||||
nativeBuildInputs = [
|
||||
arrayUtilities.isDeclaredArray
|
||||
./assert-equal-array.sh
|
||||
arrayUtilities.isDeclaredMap
|
||||
arrayUtilities.getSortedMapKeys
|
||||
./assert-equal-map.sh
|
||||
];
|
||||
|
||||
|
||||
@@ -201,6 +201,16 @@ with pkgs;
|
||||
|
||||
auto-patchelf-hook = callPackage ./auto-patchelf-hook { };
|
||||
|
||||
# Accumulate all passthru.tests from arrayUtilities into a single attribute set.
|
||||
arrayUtilities = recurseIntoAttrs (
|
||||
lib.concatMapAttrs (
|
||||
name: value:
|
||||
lib.optionalAttrs (value ? passthru.tests) {
|
||||
${name} = value.passthru.tests;
|
||||
}
|
||||
) arrayUtilities
|
||||
);
|
||||
|
||||
srcOnly = callPackage ../build-support/src-only/tests.nix { };
|
||||
|
||||
systemd = callPackage ./systemd { };
|
||||
|
||||
@@ -177,6 +177,23 @@ with pkgs;
|
||||
|
||||
__flattenIncludeHackHook = callPackage ../build-support/setup-hooks/flatten-include-hack { };
|
||||
|
||||
arrayUtilities =
|
||||
let
|
||||
arrayUtilitiesPackages = makeScopeWithSplicing' {
|
||||
otherSplices = generateSplicesForMkScope "arrayUtilities";
|
||||
f =
|
||||
finalArrayUtilities:
|
||||
{
|
||||
callPackages = lib.callPackagesWith (pkgs // finalArrayUtilities);
|
||||
}
|
||||
// lib.packagesFromDirectoryRecursive {
|
||||
inherit (finalArrayUtilities) callPackage;
|
||||
directory = ../build-support/setup-hooks/arrayUtilities;
|
||||
};
|
||||
};
|
||||
in
|
||||
recurseIntoAttrs arrayUtilitiesPackages;
|
||||
|
||||
addBinToPathHook = callPackage (
|
||||
{ makeSetupHook }:
|
||||
makeSetupHook {
|
||||
|
||||
Reference in New Issue
Block a user