Files
coolify/scripts/parser-snapshot-diff
Andras Bacsai 4e55c58a28 fix(storage): keep long-form bind sources with variable or vault references
Docker Compose resolves these values from the .env file, so the parser no
longer rejects or misresolves them. Also copy the expected parser fixtures
when parser-snapshot-diff compares with a git ref.
2026-10-20 13:47:41 +02:00

120 lines
5.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# Show how a code change alters the service parser output for every template and the reference fixture.
#
# Usage:
# ./scripts/parser-snapshot-diff save # 1. before you change code: save the current output
# ./scripts/parser-snapshot-diff baseline # 2. after the change: compare with the saved output
# ./scripts/parser-snapshot-diff [git-ref] # or compare with a git ref (default: main)
# ./scripts/parser-snapshot-diff baseline -- --filter='"template appwrite"' # extra Pest arguments
#
# Each run parses everything with tests/Feature/ServiceParserReferenceTest.php (about 90 seconds).
# A git ref is exported with git archive to a temporary directory and parsed with the current test,
# fixtures and templates, so the diff shows only the effect of the code change.
# The terminal shows one line per changed case; the full diff is written to storage/framework/testing/.
set -euo pipefail
repo="$(git -C "$(dirname "$0")/.." rev-parse --show-toplevel)"
mode="${1:-main}"
shift || true
[[ "${1:-}" == "--" ]] && shift
pest_args=("$@")
testing="$repo/storage/framework/testing" # gitignored, same file system as the repository
saved="$testing/parser-snapshots-baseline"
report="$testing/parser-snapshot-diff.diff"
work="$(mktemp -d "$testing/parser-snapshot-diff.XXXXXX")"
trap 'rm -rf "$work"' EXIT
# Run the golden test in <dir> with UPDATE_PARSER_SNAPSHOTS=1 and copy its expected files to <target>.
parse_into() {
local dir="$1" target="$2" label="$3" expected="$1/tests/Fixtures/parser/expected" backup="$work/expected-backup" status=0
echo "==> Parsing on $label (about 90 seconds)"
# The run rewrites the expected files: put the previous ones back afterwards, so the repository is unchanged.
rm -rf "$backup"
cp -r "$expected" "$backup"
rm -rf "$expected/templates"
(cd "$dir" && UPDATE_PARSER_SNAPSHOTS=1 php -d memory_limit=2G artisan test --compact \
tests/Feature/ServiceParserReferenceTest.php "${pest_args[@]}" >"$work/run.log" 2>&1) || status=$?
rm -rf "$target"
cp -r "$expected" "$target"
rm -rf "$expected"
mv "$backup" "$expected"
if [[ $status -ne 0 ]]; then
echo "Test run on $label failed:" >&2
tail -30 "$work/run.log" >&2
exit 1
fi
}
case "$mode" in
save)
parse_into "$repo" "$saved" "working tree"
echo "Saved baseline: $saved"
echo "Change the code, then run: ./scripts/parser-snapshot-diff baseline"
exit 0
;;
baseline)
[[ -d "$saved" ]] || { echo "No saved baseline. Run ./scripts/parser-snapshot-diff save first." >&2; exit 1; }
base_label="saved baseline"
cp -r "$saved" "$work/base-expected"
;;
*)
base_label="$mode"
base="$work/base"
mkdir -p "$base"
echo "==> Exporting $mode ($(git -C "$repo" rev-parse --short "$mode"))"
git -C "$repo" archive "$mode" | tar -x -C "$base"
# Hard links: fast and no extra disk space. A symlink would make Composer load the classes of $repo.
cp -al "$repo/vendor" "$base/vendor"
[[ -f "$repo/.env" ]] && cp "$repo/.env" "$base/.env"
rm -rf "$base/templates/compose" "$base/tests/Fixtures/parser"
cp -r "$repo/templates/compose" "$base/templates/compose"
mkdir -p "$base/tests/Fixtures/parser"
cp "$repo"/tests/Fixtures/parser/*.yaml "$base/tests/Fixtures/parser/"
cp -r "$repo/tests/Fixtures/parser/expected" "$base/tests/Fixtures/parser/expected"
cp "$repo/tests/Feature/ServiceParserReferenceTest.php" "$base/tests/Feature/"
parse_into "$base" "$work/base-expected" "$mode"
;;
esac
parse_into "$repo" "$work/current-expected" "working tree"
echo
if diff -ru "$work/base-expected" "$work/current-expected" | sed -E "s#$work/(base|current)-expected/##g" >"$report"; then
echo "No differences from $base_label: the parser output of every case is the same."
exit 0
fi
# Line 2 is the first step (1_deploy): an error there means the case did not parse at all on that side.
first_step_error() { sed -n 2p "$1" | grep '^ error:' | cut -c10-130 || true; }
summary() {
local file="$1" base_file="$work/base-expected/$1" current_file="$work/current-expected/$1" base_error current_error
[[ -f "$base_file" ]] || { echo " new case $file"; return; }
[[ -f "$current_file" ]] || { echo " removed case $file"; return; }
base_error="$(first_step_error "$base_file")"
current_error="$(first_step_error "$current_file")"
if [[ -n "$base_error" && -z "$current_error" ]]; then
echo " base error $file: $base_error"
elif [[ -n "$current_error" && -z "$base_error" ]]; then
echo " CURRENT ERROR $file: $current_error"
else
echo " changed $file (+$(diff "$base_file" "$current_file" | grep -c '^>' || true) -$(diff "$base_file" "$current_file" | grep -c '^<' || true))"
fi
}
changed_cases=()
while IFS= read -r file; do
cmp -s "$work/base-expected/$file" "$work/current-expected/$file" || changed_cases+=("$file")
done < <( (cd "$work/base-expected" && find . -name '*.yaml'; cd "$work/current-expected" && find . -name '*.yaml') | sed 's|^\./||' | sort -u)
lines="$(for file in "${changed_cases[@]}"; do summary "$file"; done)"
echo "${#changed_cases[@]} case(s) differ from $base_label:"
echo "$lines"
echo
echo "Full diff: $report"
if grep -q '^ base error' <<<"$lines"; then
echo "Note: 'base error' cases did not parse on $base_label, so there is no base output to compare with."
fi
exit 1