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}