3 # Property tests for lib/path/default.nix
4 # It generates random path-like strings and runs the functions on
5 # them, checking that the expected laws of the functions hold
7 # [nixpkgs]$ lib/path/tests/prop.sh
9 # [nixpkgs]$ nix-build lib/tests/release.nix
12 shopt -s inherit_errexit
14 # https://stackoverflow.com/a/246128
15 SCRIPT_DIR
=$
( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev
/null
&& pwd )
17 if test -z "${TEST_LIB:-}"; then
18 TEST_LIB
=$SCRIPT_DIR/..
/..
29 # Defaulting to a random seed but the first argument can override this
31 echo >&2 "Using seed $seed, use \`lib/path/tests/prop.sh $seed\` to reproduce this result"
33 # The number of random paths to generate. This specific number was chosen to
34 # be fast enough while still generating enough variety to detect bugs.
38 # debug=1 # print some extra info
39 # debug=2 # print generated values
41 # Fine tuning parameters to balance the number of generated invalid paths
42 # to the variance in generated paths.
43 extradotweight
=64 # Larger value: more dots
44 extraslashweight
=64 # Larger value: more slashes
45 extranullweight
=16 # Larger value: shorter strings
48 echo >&2 "test case failed: " "$@"
52 if [[ "$debug" -ge 1 ]]; then
53 echo >&2 "Generating $count random path-like strings"
56 # Read stream of null-terminated strings entry-by-entry into bash,
57 # write it to a file and the `strings` array.
59 mkdir
-p "$tmp/strings"
60 while IFS
= read -r -d $
'\0' str
; do
61 printf "%s" "$str" > "$tmp/strings/${#strings[@]}"
64 -f "$SCRIPT_DIR"/generate.
awk \
67 -v extradotweight
="$extradotweight" \
68 -v extraslashweight
="$extraslashweight" \
69 -v extranullweight
="$extranullweight")
71 if [[ "$debug" -ge 1 ]]; then
72 echo >&2 "Trying to normalise the generated path-like strings with Nix"
75 # Precalculate all normalisations with a single Nix call. Calling Nix for each
76 # string individually would take way too long
77 nix-instantiate
--eval --strict --json --show-trace \
78 --argstr libpath
"$TEST_LIB" \
79 --argstr dir
"$tmp/strings" \
80 "$SCRIPT_DIR"/prop.nix \
83 # Uses some jq magic to turn the resulting attribute set into an associative
84 # bash array assignment
85 declare -A normalised_result
="($(jq '
87 | map("[\
(.key | @sh
)]=\
(.value | @sh
)")
88 | join(" \n")' -r < "$tmp/result.json
"))"
90 # Looks up a normalisation result for a string
91 # Checks that the normalisation is only failing iff it's an invalid subpath
92 # For valid subpaths, returns 0 and prints the normalisation result
93 # For invalid subpaths, returns 1
96 # Uses the same check for validity as in the library implementation
97 if [[ "$str" == "" ||
"$str" == /* ||
"$str" =~ ^
(.
*/)?\.\.
(/.
*)?$
]]; then
103 normalised
=${normalised_result[$str]}
104 # An empty string indicates failure, this is encoded in ./prop.nix
105 if [[ -n "$normalised" ]]; then
106 if [[ -n "$valid" ]]; then
109 die
"For invalid subpath \"$str\", lib.path.subpath.normalise returned this result: \"$normalised\""
112 if [[ -n "$valid" ]]; then
113 die
"For valid subpath \"$str\", lib.path.subpath.normalise failed"
115 if [[ "$debug" -ge 2 ]]; then
116 echo >&2 "String \"$str\" is not a valid subpath"
118 # Invalid and it correctly failed, we let the caller continue if they catch the exit code
124 # Intermediate result populated by test_idempotency_realpath
125 # and used in test_normalise_uniqueness
127 # Contains a mapping from a normalised subpath to the realpath result it represents
128 declare -A norm_to_real
130 test_idempotency_realpath
() {
131 if [[ "$debug" -ge 1 ]]; then
132 echo >&2 "Checking idempotency of each result and making sure the realpath result isn't changed"
135 # Count invalid subpaths to display stats
137 for str
in "${strings[@]}"; do
138 if ! result
=$
(normalise
"$str"); then
139 ((invalid
++)) || true
143 # Check the law that it doesn't change the result of a realpath
144 mkdir
-p -- "$str" "$result"
145 real_orig
=$
(realpath
-- "$str")
146 real_norm
=$
(realpath
-- "$result")
148 if [[ "$real_orig" != "$real_norm" ]]; then
149 die
"realpath of the original string \"$str\" (\"$real_orig\") is not the same as realpath of the normalisation \"$result\" (\"$real_norm\")"
152 if [[ "$debug" -ge 2 ]]; then
153 echo >&2 "String \"$str\" gets normalised to \"$result\" and file path \"$real_orig\""
155 norm_to_real
["$result"]="$real_orig"
157 if [[ "$debug" -ge 1 ]]; then
158 echo >&2 "$(bc <<< "scale
=1; 100 / $count * $invalid")% of the total $count generated strings were invalid subpath strings, and were therefore ignored"
162 test_normalise_uniqueness
() {
163 if [[ "$debug" -ge 1 ]]; then
164 echo >&2 "Checking for the uniqueness law"
167 for norm_p
in "${!norm_to_real[@]}"; do
168 real_p
=${norm_to_real["$norm_p"]}
169 for norm_q
in "${!norm_to_real[@]}"; do
170 real_q
=${norm_to_real["$norm_q"]}
171 # Checks normalisation uniqueness law for each pair of values
172 if [[ "$norm_p" != "$norm_q" && "$real_p" == "$real_q" ]]; then
173 die
"Normalisations \"$norm_p\" and \"$norm_q\" are different, but the realpath of them is the same: \"$real_p\""
179 test_idempotency_realpath
180 test_normalise_uniqueness