1# shellcheck shell=bash
2
3# Asserts that two maps are equal, printing out differences if they are not.
4# Does not short circuit on the first difference.
5assertEqualMap() {
6 if (($# != 2)); then
7 nixErrorLog "expected two arguments!"
8 nixErrorLog "usage: assertEqualMap expectedMapRef actualMapRef"
9 exit 1
10 fi
11
12 local -nr expectedMapRef="$1"
13 local -nr actualMapRef="$2"
14
15 if ! isDeclaredMap "${!expectedMapRef}"; then
16 nixErrorLog "first argument expectedMapRef must be a reference to an associative array"
17 exit 1
18 fi
19
20 if ! isDeclaredMap "${!actualMapRef}"; then
21 nixErrorLog "second argument actualMapRef must be a reference to an associative array"
22 exit 1
23 fi
24
25 local -ir expectedLength=${#expectedMapRef[@]}
26 local -ir actualLength=${#actualMapRef[@]}
27
28 local -i hasDiff=0
29
30 if ((expectedLength != actualLength)); then
31 nixErrorLog "maps differ in length: expectedMap has length $expectedLength but actualMap has length $actualLength"
32 hasDiff=1
33 fi
34
35 local -a sortedExpectedKeys=()
36 getSortedMapKeys "${!expectedMapRef}" sortedExpectedKeys
37
38 local -a sortedActualKeys=()
39 getSortedMapKeys "${!actualMapRef}" sortedActualKeys
40
41 local -i expectedKeyIdx=0
42 local expectedKey
43 local expectedValue
44 local -i actualKeyIdx=0
45 local actualKey
46 local actualValue
47
48 # We iterate so long as at least one map has keys we've not considered.
49 while ((expectedKeyIdx < expectedLength || actualKeyIdx < actualLength)); do
50 # Update values for variables which are still in range/valid.
51 if ((expectedKeyIdx < expectedLength)); then
52 expectedKey="${sortedExpectedKeys["$expectedKeyIdx"]}"
53 expectedValue="${expectedMapRef["$expectedKey"]}"
54 fi
55
56 if ((actualKeyIdx < actualLength)); then
57 actualKey="${sortedActualKeys["$actualKeyIdx"]}"
58 actualValue="${actualMapRef["$actualKey"]}"
59 fi
60
61 # In the case actualKeyIdx is valid and expectedKey comes after actualKey or expectedKeyIdx is invalid, actualMap
62 # has an extra key relative to expectedMap.
63 # NOTE: In Bash, && and || have the same precedence, so use the fact they're left-associative to enforce groups.
64 if ((actualKeyIdx < actualLength)) && [[ $expectedKey > $actualKey ]] || ((expectedKeyIdx >= expectedLength)); then
65 nixErrorLog "maps differ at key ${actualKey@Q}: expectedMap has no such key but actualMap has value ${actualValue@Q}"
66 hasDiff=1
67 actualKeyIdx+=1
68
69 # In the case actualKeyIdx is invalid or expectedKey comes before actualKey, expectedMap has an extra key relative
70 # to actualMap.
71 # NOTE: By virtue of the previous condition being false, we know the negation is true. Namely, expectedKeyIdx is
72 # valid AND (actualKeyIdx is invalid OR expectedKey <= actualKey).
73 elif ((actualKeyIdx >= actualLength)) || [[ $expectedKey < $actualKey ]]; then
74 nixErrorLog "maps differ at key ${expectedKey@Q}: expectedMap has value ${expectedValue@Q} but actualMap has no such key"
75 hasDiff=1
76 expectedKeyIdx+=1
77
78 # In the case where both key indices are valid and the keys are equal.
79 else
80 if [[ $expectedValue != "$actualValue" ]]; then
81 nixErrorLog "maps differ at key ${expectedKey@Q}: expectedMap has value ${expectedValue@Q} but actualMap has value ${actualValue@Q}"
82 hasDiff=1
83 fi
84
85 expectedKeyIdx+=1
86 actualKeyIdx+=1
87 fi
88 done
89
90 ((hasDiff)) && exit 1 || return 0
91}