|
| 1 | +#!/usr/bin/env bash |
| 2 | + |
| 3 | +set -euo pipefail |
| 4 | +export SWIFT_DETERMINISTIC_HASHING=1 |
| 5 | + |
| 6 | +script_dir="$(cd "$(dirname "$0")" && pwd)" |
| 7 | + |
| 8 | +# Canonicalize query strings by sorting key-value pairs so order differences |
| 9 | +# don't cause spurious diffs. This mirrors how we compare semantics, not order. |
| 10 | +canonicalize_query() { |
| 11 | + local qs="$1" |
| 12 | + local IFS='&' |
| 13 | + # Split on '&' into an array (bash 3-compatible). Predeclare to avoid |
| 14 | + # "unbound variable" under `set -u` when the input is empty. |
| 15 | + local -a parts=() |
| 16 | + # Only split when the string is non-empty; otherwise leave as empty array. |
| 17 | + if [[ -n "$qs" ]]; then |
| 18 | + read -r -a parts <<< "$qs" |
| 19 | + fi |
| 20 | + # If there are no parts, return an empty string so callers print `Encoded: ` |
| 21 | + # without tripping `set -u` on ${parts[@]}. |
| 22 | + if ((${#parts[@]} == 0)); then |
| 23 | + echo "" |
| 24 | + return |
| 25 | + fi |
| 26 | + # Sort pairs lexicographically in a C locale and join back with '&' |
| 27 | + LC_ALL=C printf '%s\n' "${parts[@]}" | sort | paste -sd '&' - |
| 28 | +} |
| 29 | + |
| 30 | +# For lines like `Decoded: { ... }`, recursively sort object keys so |
| 31 | +# JSON key ordering differences don't cause spurious diffs. |
| 32 | +# Uses Node (already a dependency for the comparison). |
| 33 | +canonicalize_json_with_node() { |
| 34 | + node - <<'NODE' |
| 35 | +let s = ""; |
| 36 | +process.stdin.on("data", c => (s += c)); |
| 37 | +process.stdin.on("end", () => { |
| 38 | + function sort(v) { |
| 39 | + if (Array.isArray(v)) return v.map(sort); |
| 40 | + if (v && typeof v === "object") { |
| 41 | + const keys = Object.keys(v).sort(); |
| 42 | + const o = {}; |
| 43 | + for (const k of keys) o[k] = sort(v[k]); |
| 44 | + return o; |
| 45 | + } |
| 46 | + return v; |
| 47 | + } |
| 48 | + try { |
| 49 | + const v = JSON.parse(s); |
| 50 | + process.stdout.write(JSON.stringify(sort(v))); |
| 51 | + } catch { |
| 52 | + process.stdout.write(s); |
| 53 | + } |
| 54 | +}); |
| 55 | +NODE |
| 56 | +} |
| 57 | + |
| 58 | +normalize_stream() { |
| 59 | + local line |
| 60 | + while IFS= read -r line; do |
| 61 | + if [[ "$line" == Encoded:* ]]; then |
| 62 | + local qs="${line#Encoded: }" |
| 63 | + echo "Encoded: $(canonicalize_query "$qs")" |
| 64 | + elif [[ "$line" == Decoded:* ]]; then |
| 65 | + local json="${line#Decoded: }" |
| 66 | + local canon |
| 67 | + canon="$(printf '%s' "$json" | canonicalize_json_with_node)" |
| 68 | + echo "Decoded: $canon" |
| 69 | + else |
| 70 | + echo "$line" |
| 71 | + fi |
| 72 | + done |
| 73 | +} |
| 74 | + |
| 75 | +node_output_raw="$(node "$script_dir/js/qs.js")" |
| 76 | +swift_output_raw="$(swift run -q -c release QsSwiftComparison)" |
| 77 | + |
| 78 | +# Normalize both outputs to remove ordering noise. |
| 79 | +node_output="$(printf '%s\n' "$node_output_raw" | normalize_stream)" |
| 80 | +swift_output="$(printf '%s\n' "$swift_output_raw" | normalize_stream)" |
| 81 | + |
| 82 | +if [ "$node_output" == "$swift_output" ]; then |
| 83 | + echo "The outputs are identical." |
| 84 | + exit 0 |
| 85 | +else |
| 86 | + echo "The outputs are different." |
| 87 | + diff -u <(echo "$node_output") <(echo "$swift_output") || true |
| 88 | + exit 1 |
| 89 | +fi |
0 commit comments