The focused path tested every impacted package in a single job with a plain `go test -race`, so internal/app ran inside one long-lived process alongside all of its reverse dependencies. That is exactly the shape scripts/ci/run-app-race-tests.sh exists to avoid: a single app test process retains every constructed command tree in framework registries, so the run grows to 900s and the job stays alive long enough to be reclaimed by the runner. Recent focused runs failed with SIGTERM after 9-10 minutes without a single test failure, and one earlier run failed at `internal/app 902.651s`, 2.65s past the package timeout. Fan the same package plan across the shard matrix test-race already uses, and run each shard the way test-race runs it: internal/app through the process-isolating helper, cli/smoke with their wider package budget, release-scripts without race and with archive tooling. changed-test-packages.sh gains `list-shard`, which intersects the impacted set with scripts/ci/test-packages.sh shard membership so shard definitions stay single-sourced — and so an unknown shard name aborts there rather than reporting an empty selection, which would let a mistyped shard skip every test while reporting success. release-scripts is in the matrix on purpose: its dedicated job only runs at full-suite or release-sensitive scope, so omitting it here would stop testing test/scripts changes altogether. A test pins that the shard selections partition the impacted set exactly, so shard-plan drift cannot silently shrink focused coverage.
174 lines
5.5 KiB
Bash
Executable File
174 lines
5.5 KiB
Bash
Executable File
#!/bin/sh
|
|
# Copyright 2026 Alibaba Group
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
set -eu
|
|
|
|
ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)"
|
|
|
|
usage() {
|
|
printf '%s\n' \
|
|
"usage: $0 <changed|list> BASE_REF HEAD_REF" \
|
|
" $0 list-shard SHARD BASE_REF HEAD_REF" >&2
|
|
exit 2
|
|
}
|
|
|
|
# list-shard narrows the impacted set to one test shard so focused CI runs can
|
|
# fan the same package plan across the shard matrix instead of testing every
|
|
# impacted package in a single long-lived job.
|
|
SHARD=""
|
|
case "${1:-}" in
|
|
changed|list)
|
|
[ "$#" -eq 3 ] || usage
|
|
MODE="$1"
|
|
BASE_REF="$2"
|
|
HEAD_REF="$3"
|
|
;;
|
|
list-shard)
|
|
[ "$#" -eq 4 ] || usage
|
|
MODE="$1"
|
|
SHARD="$2"
|
|
BASE_REF="$3"
|
|
HEAD_REF="$4"
|
|
;;
|
|
*) usage ;;
|
|
esac
|
|
|
|
cd "$ROOT"
|
|
git rev-parse --verify --quiet "${BASE_REF}^{commit}" >/dev/null || {
|
|
printf 'changed test package base is not a commit: %s\n' "$BASE_REF" >&2
|
|
exit 2
|
|
}
|
|
git rev-parse --verify --quiet "${HEAD_REF}^{commit}" >/dev/null || {
|
|
printf 'changed test package head is not a commit: %s\n' "$HEAD_REF" >&2
|
|
exit 2
|
|
}
|
|
resolved_head="$(git rev-parse "${HEAD_REF}^{commit}")"
|
|
current_head="$(git rev-parse HEAD)"
|
|
[ "$current_head" = "$resolved_head" ] || {
|
|
printf 'changed test package head does not match the checked-out revision: expected %s, got %s\n' \
|
|
"$resolved_head" "$current_head" >&2
|
|
exit 2
|
|
}
|
|
git diff --quiet HEAD -- && git diff --cached --quiet HEAD -- || {
|
|
printf '%s\n' 'changed test package planning requires a clean tracked worktree' >&2
|
|
exit 2
|
|
}
|
|
|
|
workdir="$(mktemp -d "${TMPDIR:-/tmp}/dws-changed-packages.XXXXXX")"
|
|
trap 'rm -rf "$workdir"' EXIT HUP INT TERM
|
|
files="$workdir/files"
|
|
changed_packages="$workdir/changed-packages"
|
|
impacted_packages="$workdir/impacted-packages"
|
|
all_packages="$workdir/all-packages"
|
|
package_dependencies="$workdir/package-dependencies"
|
|
embed_owners="$workdir/embed-owners"
|
|
: > "$changed_packages"
|
|
: > "$impacted_packages"
|
|
|
|
git diff --no-ext-diff --find-renames --name-only \
|
|
--diff-filter=ACMRD "$BASE_REF" "$HEAD_REF" > "$files"
|
|
|
|
if ! go list ./... > "$all_packages"; then
|
|
printf '%s\n' 'failed to resolve the module package graph' >&2
|
|
exit 1
|
|
fi
|
|
if ! go list -f \
|
|
'{{- $import := .ImportPath -}}{{- $dir := .Dir -}}{{- range .EmbedFiles }}{{$import}}|{{$dir}}|{{.}}{{"\n"}}{{- end -}}{{- range .TestEmbedFiles }}{{$import}}|{{$dir}}|{{.}}{{"\n"}}{{- end -}}{{- range .XTestEmbedFiles }}{{$import}}|{{$dir}}|{{.}}{{"\n"}}{{- end -}}' \
|
|
./... > "$embed_owners"; then
|
|
printf '%s\n' 'failed to resolve embedded file ownership' >&2
|
|
exit 1
|
|
fi
|
|
|
|
while IFS= read -r file; do
|
|
[ -n "$file" ] || continue
|
|
case "$file" in
|
|
CHANGELOG.md|README.md|README_zh.md|CONTRIBUTING.md|SECURITY.md|\
|
|
CODE_OF_CONDUCT.md|LICENSE|NOTICE|docs/*|\
|
|
.github/PULL_REQUEST_TEMPLATE.md|.github/ISSUE_TEMPLATE/*)
|
|
continue
|
|
;;
|
|
esac
|
|
|
|
directory="$(dirname "$file")"
|
|
if [ "$directory" = . ]; then
|
|
pattern=.
|
|
else
|
|
pattern="./$directory"
|
|
fi
|
|
if [ -d "$directory" ]; then
|
|
has_go_files=false
|
|
for go_file in "$directory"/*.go; do
|
|
if [ -f "$go_file" ]; then
|
|
has_go_files=true
|
|
break
|
|
fi
|
|
done
|
|
if [ "$has_go_files" = true ]; then
|
|
if ! package="$(go list -f '{{.ImportPath}}' "$pattern")"; then
|
|
printf 'failed to resolve changed Go package: %s\n' "$directory" >&2
|
|
exit 1
|
|
fi
|
|
printf '%s\n' "$package" >> "$changed_packages"
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
embedded_owner_found=false
|
|
while IFS='|' read -r package package_dir embedded_file; do
|
|
[ -n "$package" ] || continue
|
|
case "$package_dir" in
|
|
"$ROOT") embedded_path="$embedded_file" ;;
|
|
"$ROOT"/*)
|
|
embedded_path="${package_dir#"$ROOT"/}/$embedded_file"
|
|
;;
|
|
*) continue ;;
|
|
esac
|
|
if [ "$embedded_path" = "$file" ]; then
|
|
printf '%s\n' "$package" >> "$changed_packages"
|
|
embedded_owner_found=true
|
|
fi
|
|
done < "$embed_owners"
|
|
[ "$embedded_owner_found" = true ] || continue
|
|
done < "$files"
|
|
|
|
# emit_selection prints the planned packages, restricted to SHARD when the
|
|
# caller asked for one. Restriction reuses scripts/ci/test-packages.sh as the
|
|
# single source of shard membership, which also means an unknown shard name
|
|
# aborts there instead of being reported as an empty selection: a silently empty
|
|
# shard would let a mistyped CI shard name skip every test and still succeed.
|
|
emit_selection() {
|
|
selection="$1"
|
|
if [ -z "$SHARD" ]; then
|
|
cat "$selection"
|
|
return 0
|
|
fi
|
|
shard_packages="$workdir/shard-packages"
|
|
"$ROOT/scripts/ci/test-packages.sh" list "$SHARD" > "$shard_packages"
|
|
LC_ALL=C sort -u "$shard_packages" -o "$shard_packages"
|
|
LC_ALL=C sort -u "$selection" -o "$selection"
|
|
# An empty intersection is a normal outcome for a shard no change touched, so
|
|
# comm's silence must not be treated as an error.
|
|
LC_ALL=C comm -12 "$selection" "$shard_packages"
|
|
}
|
|
|
|
LC_ALL=C sort -u "$changed_packages" -o "$changed_packages"
|
|
if [ "$MODE" = changed ] || [ ! -s "$changed_packages" ]; then
|
|
emit_selection "$changed_packages"
|
|
exit 0
|
|
fi
|
|
|
|
while IFS= read -r package; do
|
|
if ! go list -deps -test "$package" > "$package_dependencies"; then
|
|
printf 'failed to resolve dependencies for Go package: %s\n' "$package" >&2
|
|
exit 1
|
|
fi
|
|
if grep -Fxf "$changed_packages" "$package_dependencies" >/dev/null; then
|
|
printf '%s\n' "$package"
|
|
fi
|
|
done < "$all_packages" > "$impacted_packages"
|
|
|
|
cat "$changed_packages" >> "$impacted_packages"
|
|
LC_ALL=C sort -u "$impacted_packages" -o "$impacted_packages"
|
|
emit_selection "$impacted_packages"
|