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 the `lib.path` library
4#
5# It generates random path-like strings and runs the functions on
6# them, checking that the expected laws of the functions hold
7
8set -euo pipefail
9shopt -s inherit_errexit
10
11# https://stackoverflow.com/a/246128
12SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
13
14if test -z "${TEST_LIB:-}"; then
15 TEST_LIB=$SCRIPT_DIR/../..
16fi
17
18tmp="$(mktemp -d)"
19clean_up() {
20 rm -rf "$tmp"
21}
22trap clean_up EXIT
23mkdir -p "$tmp/work"
24cd "$tmp/work"
25
26# Defaulting to a random seed but the first argument can override this
27seed=${1:-$RANDOM}
28echo >&2 "Using seed $seed, use \`lib/path/tests/prop.sh $seed\` to reproduce this result"
29
30# The number of random paths to generate. This specific number was chosen to
31# be fast enough while still generating enough variety to detect bugs.
32count=500
33
34debug=0
35# debug=1 # print some extra info
36# debug=2 # print generated values
37
38# Fine tuning parameters to balance the number of generated invalid paths
39# to the variance in generated paths.
40extradotweight=64 # Larger value: more dots
41extraslashweight=64 # Larger value: more slashes
42extranullweight=16 # Larger value: shorter strings
43
44die() {
45 echo >&2 "test case failed: " "$@"
46 exit 1
47}
48
49if [[ "$debug" -ge 1 ]]; then
50 echo >&2 "Generating $count random path-like strings"
51fi
52
53# Read stream of null-terminated strings entry-by-entry into bash,
54# write it to a file and the `strings` array.
55declare -a strings=()
56mkdir -p "$tmp/strings"
57while IFS= read -r -d $'\0' str; do
58 printf "%s" "$str" > "$tmp/strings/${#strings[@]}"
59 strings+=("$str")
60done < <(awk \
61 -f "$SCRIPT_DIR"/generate.awk \
62 -v seed="$seed" \
63 -v count="$count" \
64 -v extradotweight="$extradotweight" \
65 -v extraslashweight="$extraslashweight" \
66 -v extranullweight="$extranullweight")
67
68if [[ "$debug" -ge 1 ]]; then
69 echo >&2 "Trying to normalise the generated path-like strings with Nix"
70fi
71
72# Precalculate all normalisations with a single Nix call. Calling Nix for each
73# string individually would take way too long
74nix-instantiate --eval --strict --json \
75 --argstr libpath "$TEST_LIB" \
76 --argstr dir "$tmp/strings" \
77 "$SCRIPT_DIR"/prop.nix \
78 >"$tmp/result.json"
79
80# Uses some jq magic to turn the resulting attribute set into an associative
81# bash array assignment
82declare -A normalised_result="($(jq '
83 to_entries
84 | map("[\(.key | @sh)]=\(.value | @sh)")
85 | join(" \n")' -r < "$tmp/result.json"))"
86
87# Looks up a normalisation result for a string
88# Checks that the normalisation is only failing iff it's an invalid subpath
89# For valid subpaths, returns 0 and prints the normalisation result
90# For invalid subpaths, returns 1
91normalise() {
92 local str=$1
93 # Uses the same check for validity as in the library implementation
94 if [[ "$str" == "" || "$str" == /* || "$str" =~ ^(.*/)?\.\.(/.*)?$ ]]; then
95 valid=
96 else
97 valid=1
98 fi
99
100 normalised=${normalised_result[$str]}
101 # An empty string indicates failure, this is encoded in ./prop.nix
102 if [[ -n "$normalised" ]]; then
103 if [[ -n "$valid" ]]; then
104 echo "$normalised"
105 else
106 die "For invalid subpath \"$str\", lib.path.subpath.normalise returned this result: \"$normalised\""
107 fi
108 else
109 if [[ -n "$valid" ]]; then
110 die "For valid subpath \"$str\", lib.path.subpath.normalise failed"
111 else
112 if [[ "$debug" -ge 2 ]]; then
113 echo >&2 "String \"$str\" is not a valid subpath"
114 fi
115 # Invalid and it correctly failed, we let the caller continue if they catch the exit code
116 return 1
117 fi
118 fi
119}
120
121# Intermediate result populated by test_idempotency_realpath
122# and used in test_normalise_uniqueness
123#
124# Contains a mapping from a normalised subpath to the realpath result it represents
125declare -A norm_to_real
126
127test_idempotency_realpath() {
128 if [[ "$debug" -ge 1 ]]; then
129 echo >&2 "Checking idempotency of each result and making sure the realpath result isn't changed"
130 fi
131
132 # Count invalid subpaths to display stats
133 invalid=0
134 for str in "${strings[@]}"; do
135 if ! result=$(normalise "$str"); then
136 ((invalid++)) || true
137 continue
138 fi
139
140 # Check the law that it doesn't change the result of a realpath
141 mkdir -p -- "$str" "$result"
142 real_orig=$(realpath -- "$str")
143 real_norm=$(realpath -- "$result")
144
145 if [[ "$real_orig" != "$real_norm" ]]; then
146 die "realpath of the original string \"$str\" (\"$real_orig\") is not the same as realpath of the normalisation \"$result\" (\"$real_norm\")"
147 fi
148
149 if [[ "$debug" -ge 2 ]]; then
150 echo >&2 "String \"$str\" gets normalised to \"$result\" and file path \"$real_orig\""
151 fi
152 norm_to_real["$result"]="$real_orig"
153 done
154 if [[ "$debug" -ge 1 ]]; then
155 echo >&2 "$(bc <<< "scale=1; 100 / $count * $invalid")% of the total $count generated strings were invalid subpath strings, and were therefore ignored"
156 fi
157}
158
159test_normalise_uniqueness() {
160 if [[ "$debug" -ge 1 ]]; then
161 echo >&2 "Checking for the uniqueness law"
162 fi
163
164 for norm_p in "${!norm_to_real[@]}"; do
165 real_p=${norm_to_real["$norm_p"]}
166 for norm_q in "${!norm_to_real[@]}"; do
167 real_q=${norm_to_real["$norm_q"]}
168 # Checks normalisation uniqueness law for each pair of values
169 if [[ "$norm_p" != "$norm_q" && "$real_p" == "$real_q" ]]; then
170 die "Normalisations \"$norm_p\" and \"$norm_q\" are different, but the realpath of them is the same: \"$real_p\""
171 fi
172 done
173 done
174}
175
176test_idempotency_realpath
177test_normalise_uniqueness
178
179echo >&2 tests ok