Files
cozystack/hack/select-e2e.sh
T
Andrey KolkovandClaude Opus 4.8 82aa967674 test(e2e): correct the round-trip budget and its stale comments
Second review pass on this branch.

1. The op timeout equalled the inner sum instead of exceeding it: the comment
   enumerated 3900s of inner waits and then set 65m, and 3900s IS 65m — so there
   was zero headroom for the applies and SQL calls between those waits, and the
   claim of "room" was false. Sized the inner budgets against what CI actually
   takes instead (the bucket suite completes in ~37s and a backup-enabled
   ClickHouse plus Keeper reaches Ready in ~98s, measured on run 30353894332):
   bucket claim/access 300->180 and the three job waits 600->480, summing to
   3300s, under a 60m op timeout. Shrinking the inner budgets rather than
   raising the ceiling also keeps this suite closer to its postgres and mariadb
   siblings on a 180-minute job cap.

2. The strategy Pod's securityContext comment still justified root with "Pod
   runs apk add at startup", which the previous commit made conditional — the
   default path now runs the platform's pre-baked image and installs nothing.
   Scoped the note to the alpine fallback and said explicitly that the field set
   matches cozy-default-altinity, runAsNonRoot deliberately unset in both.

3. clickhouse-backup.yaml still referenced clickhouse-backup-{1-contracts,
   2-roundtrip}: the file moved unchanged, so the Test rename never reached its
   own header and a reader following either name found nothing.

4. wait_sts_ready's timeout diagnostic could not print anything. It selected
   clickhouse.altinity.com/chi=${name#chi-}, which yields
   clickhouse-<app>-clickhouse-0-0 while the label carries the CHI name
   (clickhouse-<app>), and 2>/dev/null then swallowed kubectl's "No resources
   found". Ask for the StatefulSet's own first replica by name instead and let
   both streams through; verified it now surfaces the Pod events that explain
   why the replica never came up.

Also dropped the clickhouse bats case added in the previous commit: it asserted
what the generic examples/backups/<app> -> <app> mapping already guarantees, so
it did not lock the invariant its comment claimed. That invariant is now
recorded where it belongs — in the mapping's own comment in select-e2e.sh.

The dedicated ch-backup-client image this example now inherits is tracked in
#3420; noted there that the command: override is load-bearing in two files.

Assisted-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Andrey Kolkov <androndo@gmail.com>
2026-07-30 16:04:03 +04:00

186 lines
7.1 KiB
Bash
Executable File

#!/bin/sh
# Read a list of changed files (one per line) and emit space-separated suite
# names whose Chainsaw suites under hack/e2e-chainsaw/ should run.
#
# Usage: hack/select-e2e.sh <changed-files> [<sources-dir>]
# Defaults: sources-dir = packages/core/platform/sources
#
# Output:
# - empty no E2E impact (docs / dashboards / *.md only)
# - <suite names> selected per the PackageSource dependency graph
# - full list any path that affects all tests, OR an unrecognised
# packages/* path, OR a per-app source whose graph has
# no *-application descendants (conservative fallback)
set -eu
CHANGED="${1:?missing changed-files arg}"
SOURCES_DIR="${2:-packages/core/platform/sources}"
# Anything matching this pattern triggers the full Chainsaw suite. Per-suite
# edits (hack/e2e-chainsaw/<name>/...) are matched BEFORE this so editing one
# suite doesn't escalate to the full suite; the shared _lib helpers and the
# .chainsaw.yaml config affect every suite and DO escalate (handled inline).
full_suite_pattern='^(packages/library/|packages/core/|api/|cmd/|internal/|hack/[^/]+\.sh$|hack/[^/]+\.bats$|Makefile$|\.github/workflows/pull-requests\.yaml$)'
# All known Chainsaw suites: every dir under hack/e2e-chainsaw/ holding a
# chainsaw-test.yaml (this excludes _lib/ and the top-level config files).
all_apps=$(find hack/e2e-chainsaw -mindepth 2 -maxdepth 2 -name chainsaw-test.yaml 2>/dev/null \
| sed -e 's,^hack/e2e-chainsaw/,,' -e 's,/chainsaw-test\.yaml$,,' | sort)
# PackageSource name -> Chainsaw suite name(s). Most *-application sources map
# by stripping the suffix; explicit overrides for the few that don't.
src_to_suites() {
case "$1" in
postgres-application) echo postgres ;;
vm-instance-application) echo vminstance ;;
kubernetes-application) echo "kubernetes-latest kubernetes-previous kubernetes-oidc-system kubernetes-oidc-customconfig" ;;
external-dns) echo external-dns ;;
*-application) echo "${1%-application}" ;;
*) echo "$1" ;;
esac
}
# yq: path -> PackageSource name
build_owners_index() {
yq -rN '.metadata.name as $n | .spec.variants[]?.components[]?.path | select(. != null and . != "") | . + "\t" + $n' "$SOURCES_DIR"/*.yaml
}
# yq: PackageSource name -> sources that depend on it (reverse of dependsOn)
#
# Exclude cozystack.cozystack-engine as a propagation hub. Every *-application
# source declares `dependsOn: cozystack.cozystack-engine` purely as an INSTALL
# ORDERING edge — the app's *-rd HelmRelease must wait for the engine to
# register the ApplicationDefinition CRD before it can reconcile. That is a
# universal lifecycle dependency, NOT a behavioral one: a change to an engine
# dependency (postgres-operator, keycloak, cert-manager, ...) does not alter any
# app's runtime behavior, so it must not fan test selection out to every app.
# Without this filter, postgres-operator -> keycloak -> cozystack-engine -> EVERY
# app, defeating test-impact analysis. Dropping the engine reverse edges keeps
# the engine reachable but stops it propagating selection downstream. A change to
# the engine itself still triggers the full suite via the no-app-descendants
# safety-net at the bottom of this script.
build_reverse_deps() {
yq -rN '.metadata.name as $n | .spec.variants[]?.dependsOn[]? | select(. != null and . != "" and . != "cozystack.cozystack-engine") | . + "\t" + $n' "$SOURCES_DIR"/*.yaml
}
OWNERS=$(build_owners_index | sort -u)
REVERSE=$(build_reverse_deps | sort -u)
trigger_full=0
trigger_any=0
selected_sources=""
selected_apps=""
while IFS= read -r file; do
[ -z "$file" ] && continue
# 1. Skip: docs, dashboards, *.md
if echo "$file" | grep -qE '^(docs/|dashboards/)' || echo "$file" | grep -qE '\.md$'; then
continue
fi
# 2. Chainsaw edits. A per-suite file selects only that suite; the shared
# _lib helpers and the .chainsaw.yaml config affect every suite, so they
# escalate to the full run.
case "$file" in
hack/e2e-chainsaw/_lib/*|hack/e2e-chainsaw/.chainsaw.yaml)
trigger_full=1
continue ;;
hack/e2e-chainsaw/*/*)
app=$(echo "$file" | sed -nE 's,^hack/e2e-chainsaw/([^/]+)/.*,\1,p')
selected_apps="$selected_apps $app"
trigger_any=1
continue ;;
examples/backups/*/*)
# The etcd, postgres, mariadb and clickhouse backup round-trip tests
# execute the example scripts under examples/backups/<app>/ as their
# harness, so an edit there must run that app's suite. This mapping is
# also why a round-trip Test belongs in its app's own suite dir: put it
# in a dir whose name does not match examples/backups/<app>/ and an edit
# to the harness selects a suite that cannot exercise it. A dir with no
# matching suite is a docs-only demo and stays ignored (adding it to
# selected_apps would empty the final intersection and trip the
# full-suite safety net).
app=$(echo "$file" | sed -nE 's,^examples/backups/([^/]+)/.*,\1,p')
if echo "$all_apps" | grep -Fxq "$app"; then
selected_apps="$selected_apps $app"
trigger_any=1
fi
continue ;;
esac
# 3. Full-suite trigger
if echo "$file" | grep -qE "$full_suite_pattern"; then
trigger_full=1
continue
fi
# 4. Component change: lookup in PackageSource graph
rel=$(echo "$file" | sed -nE 's,^packages/(apps|system|extra)/([^/]+)/.*,\1/\2,p')
if [ -n "$rel" ]; then
src=$(echo "$OWNERS" | awk -v p="$rel" -F'\t' '$1==p {print $2}')
if [ -n "$src" ]; then
selected_sources="$selected_sources $src"
trigger_any=1
else
# Inside packages/ but no graph entry — be conservative.
trigger_full=1
fi
fi
# Anything else (e.g. unrelated workflow files, top-level configs) is
# silently ignored.
done < "$CHANGED"
if [ "$trigger_full" = 1 ]; then
echo "$all_apps" | paste -sd ' ' -
exit 0
fi
if [ "$trigger_any" = 0 ]; then
exit 0 # nothing to run
fi
# Transitive closure: walk reverse-deps from each selected source.
all_sources="$selected_sources"
while :; do
new=""
for s in $all_sources; do
deps=$(echo "$REVERSE" | awk -v src="$s" -F'\t' '$1==src {print $2}')
for d in $deps; do
case " $all_sources " in *" $d "*) ;; *) new="$new $d";; esac
done
done
[ -z "$new" ] && break
all_sources="$all_sources $new"
done
# Filter to *-application sources, then map to Chainsaw suite names.
final=""
for s in $all_sources; do
app=${s#cozystack.}
case "$app" in
*-application) final="$final $(src_to_suites "$app")" ;;
external-dns) final="$final external-dns" ;;
esac
done
# Add directly-selected suites from per-suite edits.
final="$final $selected_apps"
# Deduplicate; intersect with available Chainsaw suites.
final_apps=$(echo "$final" | tr ' ' '\n' | sort -u | grep -v '^$' | while read -r app; do
if echo "$all_apps" | grep -Fxq "$app"; then
echo "$app"
fi
done | paste -sd ' ' -)
# Safety net: a system source with no *-application descendants would otherwise
# silently skip E2E. Fall back to full suite so a path inside the graph is
# never silently dropped.
if [ -z "$final_apps" ]; then
echo "$all_apps" | paste -sd ' ' -
exit 0
fi
echo "$final_apps"