fork
Configure Feed
Select the types of activity you want to include in your feed.
lol
fork
Configure Feed
Select the types of activity you want to include in your feed.
1#!/usr/bin/env bash
2
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
6# Run:
7# [nixpkgs]$ lib/path/tests/prop.sh
8# or:
9# [nixpkgs]$ nix-build lib/tests/release.nix
10
11set -euo pipefail
12shopt -s inherit_errexit
13
14# https://stackoverflow.com/a/246128
15SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
16
17if test -z "${TEST_LIB:-}"; then
18 TEST_LIB=$SCRIPT_DIR/../..
19fi
20
21tmp="$(mktemp -d)"
22clean_up() {
23 rm -rf "$tmp"
24}
25trap clean_up EXIT
26mkdir -p "$tmp/work"
27cd "$tmp/work"
28
29# Defaulting to a random seed but the first argument can override this
30seed=${1:-$RANDOM}
31echo >&2 "Using seed $seed, use \`lib/path/tests/prop.sh $seed\` to reproduce this result"
32
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.
35count=500
36
37debug=0
38# debug=1 # print some extra info
39# debug=2 # print generated values
40
41# Fine tuning parameters to balance the number of generated invalid paths
42# to the variance in generated paths.
43extradotweight=64 # Larger value: more dots
44extraslashweight=64 # Larger value: more slashes
45extranullweight=16 # Larger value: shorter strings
46
47die() {
48 echo >&2 "test case failed: " "$@"
49 exit 1
50}
51
52if [[ "$debug" -ge 1 ]]; then
53 echo >&2 "Generating $count random path-like strings"
54fi
55
56# Read stream of null-terminated strings entry-by-entry into bash,
57# write it to a file and the `strings` array.
58declare -a strings=()
59mkdir -p "$tmp/strings"
60while IFS= read -r -d $'\0' str; do
61 printf "%s" "$str" > "$tmp/strings/${#strings[@]}"
62 strings+=("$str")
63done < <(awk \
64 -f "$SCRIPT_DIR"/generate.awk \
65 -v seed="$seed" \
66 -v count="$count" \
67 -v extradotweight="$extradotweight" \
68 -v extraslashweight="$extraslashweight" \
69 -v extranullweight="$extranullweight")
70
71if [[ "$debug" -ge 1 ]]; then
72 echo >&2 "Trying to normalise the generated path-like strings with Nix"
73fi
74
75# Precalculate all normalisations with a single Nix call. Calling Nix for each
76# string individually would take way too long
77nix-instantiate --eval --strict --json --show-trace \
78 --argstr libpath "$TEST_LIB" \
79 --argstr dir "$tmp/strings" \
80 "$SCRIPT_DIR"/prop.nix \
81 >"$tmp/result.json"
82
83# Uses some jq magic to turn the resulting attribute set into an associative
84# bash array assignment
85declare -A normalised_result="($(jq '
86 to_entries
87 | map("[\(.key | @sh)]=\(.value | @sh)")
88 | join(" \n")' -r < "$tmp/result.json"))"
89
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
94normalise() {
95 local str=$1
96 # Uses the same check for validity as in the library implementation
97 if [[ "$str" == "" || "$str" == /* || "$str" =~ ^(.*/)?\.\.(/.*)?$ ]]; then
98 valid=
99 else
100 valid=1
101 fi
102
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
107 echo "$normalised"
108 else
109 die "For invalid subpath \"$str\", lib.path.subpath.normalise returned this result: \"$normalised\""
110 fi
111 else
112 if [[ -n "$valid" ]]; then
113 die "For valid subpath \"$str\", lib.path.subpath.normalise failed"
114 else
115 if [[ "$debug" -ge 2 ]]; then
116 echo >&2 "String \"$str\" is not a valid subpath"
117 fi
118 # Invalid and it correctly failed, we let the caller continue if they catch the exit code
119 return 1
120 fi
121 fi
122}
123
124# Intermediate result populated by test_idempotency_realpath
125# and used in test_normalise_uniqueness
126#
127# Contains a mapping from a normalised subpath to the realpath result it represents
128declare -A norm_to_real
129
130test_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"
133 fi
134
135 # Count invalid subpaths to display stats
136 invalid=0
137 for str in "${strings[@]}"; do
138 if ! result=$(normalise "$str"); then
139 ((invalid++)) || true
140 continue
141 fi
142
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")
147
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\")"
150 fi
151
152 if [[ "$debug" -ge 2 ]]; then
153 echo >&2 "String \"$str\" gets normalised to \"$result\" and file path \"$real_orig\""
154 fi
155 norm_to_real["$result"]="$real_orig"
156 done
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"
159 fi
160}
161
162test_normalise_uniqueness() {
163 if [[ "$debug" -ge 1 ]]; then
164 echo >&2 "Checking for the uniqueness law"
165 fi
166
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\""
174 fi
175 done
176 done
177}
178
179test_idempotency_realpath
180test_normalise_uniqueness
181
182echo >&2 tests ok