From 37292e6ba1cba0a7d1c156ff601704fd9cafb4b1 Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Fri, 10 Oct 2025 07:50:07 -0700 Subject: [PATCH 01/30] finds and runs relevant code change tests --- .github/workflows/affected-tests.yml | 92 ++++++++ scripts/affected-packages.go | 334 +++++++++++++++++++++++++++ 2 files changed, 426 insertions(+) create mode 100644 .github/workflows/affected-tests.yml create mode 100644 scripts/affected-packages.go diff --git a/.github/workflows/affected-tests.yml b/.github/workflows/affected-tests.yml new file mode 100644 index 000000000..90fed1809 --- /dev/null +++ b/.github/workflows/affected-tests.yml @@ -0,0 +1,92 @@ +name: Affected Go Tests + +on: + workflow_run: + workflows: ["build-test"] + types: [completed] + branches: [main] + +permissions: + contents: read + +jobs: + test-affected: + # Only run if the build-test workflow succeeded and it's not a draft PR + if: | + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.pull_requests[0].draft == false + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + # Check out the PR merge commit + ref: ${{ github.event.workflow_run.head_sha }} + fetch-depth: 0 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Go Mod Download + run: go mod download + + - name: Get PR Base SHA + id: pr-info + env: + GH_TOKEN: ${{ github.token }} + run: | + # Get the PR number from the workflow run + PR_NUMBER="${{ github.event.workflow_run.pull_requests[0].number }}" + echo "PR Number: ${PR_NUMBER}" + + # Get the base SHA for this PR + BASE_SHA=$(gh pr view ${PR_NUMBER} --json baseRefOid -q .baseRefOid) + echo "BASE_SHA=${BASE_SHA}" >> "$GITHUB_OUTPUT" + echo "Base SHA: ${BASE_SHA}" + + - name: Compute affected packages + id: affected + env: + BASE_SHA: ${{ steps.pr-info.outputs.BASE_SHA }} + run: | + set -euo pipefail + echo "Base SHA: ${BASE_SHA}" + # Generate affected package list to a file for reuse in subsequent steps + go run ./scripts/affected-packages.go -base "${BASE_SHA}" > /tmp/affected.txt + echo "Affected packages:" || true + if [ -s /tmp/affected.txt ]; then + cat /tmp/affected.txt + else + echo "(none)" + fi + # Expose whether we have any packages to test + if [ -s /tmp/affected.txt ]; then + echo "has_changes=true" >> "$GITHUB_OUTPUT" + else + echo "has_changes=false" >> "$GITHUB_OUTPUT" + fi + + - name: Run unit tests for affected packages + if: steps.affected.outputs.has_changes == 'true' + run: | + set -euo pipefail + # If the script output contains './...' then run all tests + if grep -qx "./..." /tmp/affected.txt; then + echo "Module files changed; running all tests" + go test -race -count=1 ./... + else + echo "Running tests for affected packages" + # xargs will pass the package list as arguments to go test + xargs -a /tmp/affected.txt go test -race -count=1 -v + fi + + - name: No affected packages — skip tests + if: steps.affected.outputs.has_changes != 'true' + run: echo "No Go packages affected by this PR; skipping tests." + + diff --git a/scripts/affected-packages.go b/scripts/affected-packages.go new file mode 100644 index 000000000..bf209c87b --- /dev/null +++ b/scripts/affected-packages.go @@ -0,0 +1,334 @@ +package main + +import ( + "bufio" + "bytes" + "encoding/json" + "errors" + "flag" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "sort" + "strings" +) + +// goListPackageJSON models the subset of fields we need from `go list -json` output. +// The JSON can be quite large; we intentionally only decode what we use to keep memory reasonable. +type goListPackageJSON struct { + ImportPath string `json:"ImportPath"` + Deps []string `json:"Deps"` +} + +// runCommand executes a command and returns stdout as bytes with trimmed trailing newline. +func runCommand(name string, args ...string) ([]byte, error) { + cmd := exec.Command(name, args...) + cmd.Stderr = os.Stderr + out, err := cmd.Output() + if err != nil { + return nil, err + } + return bytes.TrimRight(out, "\n"), nil +} + +// listPackageWithDeps returns the full transitive dependency set for a package import path, +// including the package itself. +func listPackageWithDeps(importPath string) (map[string]struct{}, error) { + cmd := exec.Command("go", "list", "-json", "-deps", importPath) + cmd.Stderr = os.Stderr + out, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("go list -json -deps %s failed: %w", importPath, err) + } + deps := make(map[string]struct{}) + dec := json.NewDecoder(bytes.NewReader(out)) + for { + var pkg goListPackageJSON + if err := dec.Decode(&pkg); err != nil { + if errors.Is(err, io.EOF) { + break + } + return nil, fmt.Errorf("decode go list json: %w", err) + } + if pkg.ImportPath != "" { + deps[pkg.ImportPath] = struct{}{} + } + for _, d := range pkg.Deps { + deps[d] = struct{}{} + } + } + return deps, nil +} + +// changedFiles returns a slice of file paths changed between baseRef and HEAD. +func changedFiles(baseRef string) ([]string, error) { + // Use triple-dot to include merge base with baseRef, typical for PR diffs. + out, err := runCommand("git", "diff", "--name-only", baseRef+"...HEAD") + if err != nil { + return nil, fmt.Errorf("git diff failed: %w", err) + } + var files []string + scanner := bufio.NewScanner(bytes.NewReader(out)) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" { + continue + } + files = append(files, line) + } + if err := scanner.Err(); err != nil { + return nil, err + } + return files, nil +} + +// mapFilesToPackages resolves a set of Go package import paths that directly contain the changed Go files. +func mapFilesToPackages(files []string) (map[string]struct{}, error) { + packages := make(map[string]struct{}) + // Collect unique directories that contain changed Go files. + dirSet := make(map[string]struct{}) + for _, f := range files { + if strings.HasPrefix(f, "vendor/") { + continue + } + if filepath.Ext(f) != ".go" { + continue + } + d := filepath.Dir(f) + if d == "." { + d = "." + } + dirSet[d] = struct{}{} + } + if len(dirSet) == 0 { + return packages, nil + } + + // Convert to a stable-ordered slice of directories to avoid nondeterminism. + var dirs []string + for d := range dirSet { + // Ensure relative paths are treated as packages; prepend ./ for clarity. + if strings.HasPrefix(d, "./") || d == "." { + dirs = append(dirs, d) + } else { + dirs = append(dirs, "./"+d) + } + } + sort.Strings(dirs) + + // `go list` accepts directories and returns their package import paths. + args := append([]string{"list", "-f", "{{.ImportPath}}"}, dirs...) + out, err := runCommand("go", args...) + if err != nil { + return nil, fmt.Errorf("go list for files failed: %w", err) + } + scanner := bufio.NewScanner(bytes.NewReader(out)) + for scanner.Scan() { + pkg := strings.TrimSpace(scanner.Text()) + if pkg != "" { + packages[pkg] = struct{}{} + } + } + if err := scanner.Err(); err != nil { + return nil, err + } + return packages, nil +} + +// computeAffectedPackages expands directly changed packages to include reverse dependencies across the module. +// We query all packages with test dependencies (-test) to ensure test-only imports are considered. +func computeAffectedPackages(directPkgs map[string]struct{}) (map[string]struct{}, error) { + affected := make(map[string]struct{}) + for p := range directPkgs { + affected[p] = struct{}{} + } + if len(directPkgs) == 0 { + return affected, nil + } + + // Enumerate all packages in the module with their deps. + // We stream decode concatenated JSON objects produced by `go list -json`. + cmd := exec.Command("go", "list", "-json", "-deps", "-test", "./...") + cmd.Stderr = os.Stderr + out, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("go list -json failed: %w", err) + } + + dec := json.NewDecoder(bytes.NewReader(out)) + for { + var pkg goListPackageJSON + if err := dec.Decode(&pkg); err != nil { + if errors.Is(err, io.EOF) { + break + } + return nil, fmt.Errorf("decode go list json: %w", err) + } + // If this package is directly changed, it's already included. + // If it depends (directly or transitively) on any changed package, include it. + for changed := range directPkgs { + if pkg.ImportPath == changed { + affected[pkg.ImportPath] = struct{}{} + break + } + // Linear scan over deps is acceptable given typical package counts. + for _, dep := range pkg.Deps { + if dep == changed { + affected[pkg.ImportPath] = struct{}{} + break + } + } + } + } + return affected, nil +} + +func main() { + baseRef := flag.String("base", "origin/main", "Git base ref to diff against (e.g., origin/main)") + printAllOnChanges := flag.Bool("all-on-mod-change", true, "Run all tests if go.mod or go.sum changed") + verbose := flag.Bool("v", false, "Enable verbose diagnostics to stderr") + mode := flag.String("mode", "packages", "Output mode: 'packages' to print import paths; 'suites' to print e2e suite names") + flag.Parse() + + files, err := changedFiles(*baseRef) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + if *verbose { + fmt.Fprintln(os.Stderr, "Changed files vs base:") + if len(files) == 0 { + fmt.Fprintln(os.Stderr, " (none)") + } else { + for _, f := range files { + fmt.Fprintln(os.Stderr, " ", f) + } + } + } + + // If module files changed, be conservative. + if *printAllOnChanges { + for _, f := range files { + if f == "go.mod" || f == "go.sum" { + if *mode == "packages" { + if *verbose { + fmt.Fprintln(os.Stderr, "Detected module file change (go.mod/go.sum); selecting all packages ./...") + } + fmt.Println("./...") + return + } + if *mode == "suites" { + if *verbose { + fmt.Fprintln(os.Stderr, "Detected module file change (go.mod/go.sum); selecting all e2e suites") + } + fmt.Println("preflight") + fmt.Println("support-bundle") + return + } + } + } + } + + directPkgs, err := mapFilesToPackages(files) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + if *verbose { + // Stable dump of direct packages + var dirs []string + for p := range directPkgs { + dirs = append(dirs, p) + } + sort.Strings(dirs) + fmt.Fprintln(os.Stderr, "Directly changed packages:") + if len(dirs) == 0 { + fmt.Fprintln(os.Stderr, " (none)") + } else { + for _, p := range dirs { + fmt.Fprintln(os.Stderr, " ", p) + } + } + } + + switch *mode { + case "packages": + affected, err := computeAffectedPackages(directPkgs) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + if *verbose { + var dbg []string + for p := range affected { + dbg = append(dbg, p) + } + sort.Strings(dbg) + fmt.Fprintln(os.Stderr, "Final affected packages:") + if len(dbg) == 0 { + fmt.Fprintln(os.Stderr, " (none)") + } else { + for _, p := range dbg { + fmt.Fprintln(os.Stderr, " ", p) + } + } + } + var list []string + for p := range affected { + list = append(list, p) + } + sort.Strings(list) + for _, p := range list { + fmt.Println(p) + } + case "suites": + // Determine if preflight and/or support-bundle regression suites should run + preflightRoot := "github.com/replicatedhq/troubleshoot/cmd/preflight" + supportRoot := "github.com/replicatedhq/troubleshoot/cmd/troubleshoot" + + preflightDeps, err := listPackageWithDeps(preflightRoot) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + supportDeps, err := listPackageWithDeps(supportRoot) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + + preflightHit := false + supportHit := false + for changed := range directPkgs { + if !preflightHit { + if _, ok := preflightDeps[changed]; ok { + preflightHit = true + } + } + if !supportHit { + if _, ok := supportDeps[changed]; ok { + supportHit = true + } + } + if preflightHit && supportHit { + break + } + } + if *verbose { + fmt.Fprintln(os.Stderr, "E2E suite impact:") + fmt.Fprintf(os.Stderr, " preflight: %v\n", preflightHit) + fmt.Fprintf(os.Stderr, " support-bundle: %v\n", supportHit) + } + if preflightHit { + fmt.Println("preflight") + } + if supportHit { + fmt.Println("support-bundle") + } + default: + fmt.Fprintln(os.Stderr, "unknown mode; use 'packages' or 'suites'") + os.Exit(2) + } +} From 3f069125a229d2e08426f3a1537dd4124d0fb2a5 Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Sat, 11 Oct 2025 10:49:54 -0700 Subject: [PATCH 02/30] added flag to specify files changed for testing --- scripts/affected-packages.go | 41 ++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/scripts/affected-packages.go b/scripts/affected-packages.go index bf209c87b..a23aa24e4 100644 --- a/scripts/affected-packages.go +++ b/scripts/affected-packages.go @@ -190,12 +190,45 @@ func main() { printAllOnChanges := flag.Bool("all-on-mod-change", true, "Run all tests if go.mod or go.sum changed") verbose := flag.Bool("v", false, "Enable verbose diagnostics to stderr") mode := flag.String("mode", "packages", "Output mode: 'packages' to print import paths; 'suites' to print e2e suite names") + changedFilesCSV := flag.String("changed-files", "", "Comma-separated paths to treat as changed (bypass git)") + changedFilesFile := flag.String("changed-files-file", "", "File with newline-separated paths to treat as changed") flag.Parse() - files, err := changedFiles(*baseRef) - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(2) + // Determine the set of changed files: explicit list if provided, otherwise via git diff. + var files []string + if *changedFilesCSV != "" || *changedFilesFile != "" { + if *changedFilesCSV != "" { + parts := strings.Split(*changedFilesCSV, ",") + for _, p := range parts { + if s := strings.TrimSpace(p); s != "" { + files = append(files, s) + } + } + } + if *changedFilesFile != "" { + b, err := os.ReadFile(*changedFilesFile) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + scanner := bufio.NewScanner(bytes.NewReader(b)) + for scanner.Scan() { + if s := strings.TrimSpace(scanner.Text()); s != "" { + files = append(files, s) + } + } + if err := scanner.Err(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + } + } else { + var err error + files, err = changedFiles(*baseRef) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } } if *verbose { fmt.Fprintln(os.Stderr, "Changed files vs base:") From a2a424b20143428cde99fa1786a7369cc9ceea4c Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Sat, 11 Oct 2025 11:17:26 -0700 Subject: [PATCH 03/30] added test and makes new relevant tests run tests --- .github/workflows/affected-tests.yml | 51 +++++++ .github/workflows/build-test-deploy.yaml | 12 +- .github/workflows/build-test.yaml | 4 +- .github/workflows/regression-test.yaml | 3 +- scripts/affected-packages.go | 116 ++++++++++++--- test-affected-detection.sh | 181 +++++++++++++++++++++++ 6 files changed, 338 insertions(+), 29 deletions(-) create mode 100755 test-affected-detection.sh diff --git a/.github/workflows/affected-tests.yml b/.github/workflows/affected-tests.yml index 90fed1809..300e60d1f 100644 --- a/.github/workflows/affected-tests.yml +++ b/.github/workflows/affected-tests.yml @@ -35,6 +35,18 @@ jobs: - name: Go Mod Download run: go mod download + # 1) Build and compile tests only (no execution) + - name: Build (compile-only) + run: go build ./... + + - name: Compile tests (no execution) + shell: bash + run: | + set -euo pipefail + while read -r pkg; do + go test -c -o /dev/null "$pkg" || exit 1 + done < <(go list ./...) + - name: Get PR Base SHA id: pr-info env: @@ -49,6 +61,7 @@ jobs: echo "BASE_SHA=${BASE_SHA}" >> "$GITHUB_OUTPUT" echo "Base SHA: ${BASE_SHA}" + # 2) Detect relevant unit packages and e2e tests - name: Compute affected packages id: affected env: @@ -71,6 +84,22 @@ jobs: echo "has_changes=false" >> "$GITHUB_OUTPUT" fi + - name: Compute affected e2e tests + id: affected_e2e + env: + BASE_SHA: ${{ steps.pr-info.outputs.BASE_SHA }} + run: | + set -euo pipefail + go run ./scripts/affected-packages.go -mode=suites -base "${BASE_SHA}" > /tmp/affected-e2e.txt + awk -F: '$1=="preflight"{print $2}' /tmp/affected-e2e.txt > /tmp/preflight-tests.txt + awk -F: '$1=="support-bundle"{print $2}' /tmp/affected-e2e.txt > /tmp/support-tests.txt + if [ -s /tmp/preflight-tests.txt ] || [ -s /tmp/support-tests.txt ]; then + echo "has_changes=true" >> "$GITHUB_OUTPUT" + else + echo "has_changes=false" >> "$GITHUB_OUTPUT" + fi + + # 3) Run filtered tests only - name: Run unit tests for affected packages if: steps.affected.outputs.has_changes == 'true' run: | @@ -85,6 +114,28 @@ jobs: xargs -a /tmp/affected.txt go test -race -count=1 -v fi + - name: Run preflight e2e (filtered) + if: steps.affected_e2e.outputs.has_changes == 'true' + run: | + set -euo pipefail + if [ -s /tmp/preflight-tests.txt ]; then + regex="$(tr '\n' '|' < /tmp/preflight-tests.txt | sed 's/|$//')" + go test -v -count=1 ./test/e2e/preflight -run "^((${regex}))$" + else + echo "No preflight e2e changes" + fi + + - name: Run support-bundle e2e (filtered) + if: steps.affected_e2e.outputs.has_changes == 'true' + run: | + set -euo pipefail + if [ -s /tmp/support-tests.txt ]; then + regex="$(tr '\n' '|' < /tmp/support-tests.txt | sed 's/|$//')" + go test -v -count=1 ./test/e2e/support-bundle -run "^((${regex}))$" + else + echo "No support-bundle e2e changes" + fi + - name: No affected packages — skip tests if: steps.affected.outputs.has_changes != 'true' run: echo "No Go packages affected by this PR; skipping tests." diff --git a/.github/workflows/build-test-deploy.yaml b/.github/workflows/build-test-deploy.yaml index d9ff051e3..c471886c8 100644 --- a/.github/workflows/build-test-deploy.yaml +++ b/.github/workflows/build-test-deploy.yaml @@ -37,7 +37,8 @@ jobs: - run: make tidy-diff test-integration: - runs-on: ubuntu-latest + if: github.event_name == 'push' + runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - uses: actions/setup-go@v6 @@ -65,7 +66,8 @@ jobs: path: bin/preflight validate-preflight-e2e: - runs-on: ubuntu-latest + if: github.event_name == 'push' + runs-on: ubuntu-latest needs: compile-preflight steps: - uses: actions/checkout@v5 @@ -95,7 +97,8 @@ jobs: path: bin/support-bundle validate-supportbundle-e2e: - runs-on: ubuntu-latest + if: github.event_name == 'push' + runs-on: ubuntu-latest needs: compile-supportbundle steps: - uses: actions/checkout@v5 @@ -113,7 +116,8 @@ jobs: # Additional e2e tests for support bundle that run in Go, these create a Kind cluster validate-supportbundle-e2e-go: - runs-on: ubuntu-latest + if: github.event_name == 'push' + runs-on: ubuntu-latest needs: compile-supportbundle steps: - uses: actions/checkout@v5 diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index 7c3f8bf3a..b4a3db749 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -66,7 +66,7 @@ jobs: # Unit and integration tests test: - if: needs.changes.outputs.go-files == 'true' + if: github.event_name == 'push' && needs.changes.outputs.go-files == 'true' needs: [changes, lint] runs-on: ubuntu-latest timeout-minutes: 20 @@ -100,7 +100,7 @@ jobs: # E2E tests e2e: - if: needs.changes.outputs.go-files == 'true' || github.event_name == 'push' + if: github.event_name == 'push' needs: [changes, build] runs-on: ubuntu-latest timeout-minutes: 15 diff --git a/.github/workflows/regression-test.yaml b/.github/workflows/regression-test.yaml index a19e197f1..5f7f328bd 100644 --- a/.github/workflows/regression-test.yaml +++ b/.github/workflows/regression-test.yaml @@ -3,8 +3,6 @@ name: Regression Test Suite on: push: branches: [main, v1beta3] - pull_request: - types: [opened, synchronize, reopened] workflow_dispatch: inputs: update_baselines: @@ -14,6 +12,7 @@ on: jobs: regression-test: + if: github.event_name != 'pull_request' runs-on: ubuntu-22.04 timeout-minutes: 25 diff --git a/scripts/affected-packages.go b/scripts/affected-packages.go index a23aa24e4..100dfe855 100644 --- a/scripts/affected-packages.go +++ b/scripts/affected-packages.go @@ -8,9 +8,11 @@ import ( "flag" "fmt" "io" + "io/fs" "os" "os/exec" "path/filepath" + "regexp" "sort" "strings" ) @@ -185,6 +187,44 @@ func computeAffectedPackages(directPkgs map[string]struct{}) (map[string]struct{ return affected, nil } +// listTestFunctions scans a directory for Go test files and returns names of functions +// that match the pattern `func TestXxx(t *testing.T)`. +func listTestFunctions(dir string) ([]string, error) { + var tests []string + // Regex to capture test function names. This is a simple heuristic suitable for our codebase. + testFuncRe := regexp.MustCompile(`^func\s+(Test[\w\d_]+)\s*\(`) + + walkFn := func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + if !strings.HasSuffix(d.Name(), "_test.go") { + return nil + } + b, err := os.ReadFile(path) + if err != nil { + return err + } + scanner := bufio.NewScanner(bytes.NewReader(b)) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if m := testFuncRe.FindStringSubmatch(line); m != nil { + tests = append(tests, m[1]) + } + } + return scanner.Err() + } + + if err := filepath.WalkDir(dir, walkFn); err != nil { + return nil, err + } + sort.Strings(tests) + return tests, nil +} + func main() { baseRef := flag.String("base", "origin/main", "Git base ref to diff against (e.g., origin/main)") printAllOnChanges := flag.Bool("all-on-mod-change", true, "Run all tests if go.mod or go.sum changed") @@ -241,26 +281,21 @@ func main() { } } - // If module files changed, be conservative. + // Track module change to drive conservative behavior. + moduleChanged := false if *printAllOnChanges { for _, f := range files { if f == "go.mod" || f == "go.sum" { - if *mode == "packages" { - if *verbose { - fmt.Fprintln(os.Stderr, "Detected module file change (go.mod/go.sum); selecting all packages ./...") - } - fmt.Println("./...") - return - } - if *mode == "suites" { - if *verbose { - fmt.Fprintln(os.Stderr, "Detected module file change (go.mod/go.sum); selecting all e2e suites") - } - fmt.Println("preflight") - fmt.Println("support-bundle") - return - } + moduleChanged = true + break + } + } + if moduleChanged && *mode == "packages" { + if *verbose { + fmt.Fprintln(os.Stderr, "Detected module file change (go.mod/go.sum); selecting all packages ./...") } + fmt.Println("./...") + return } } @@ -317,7 +352,7 @@ func main() { fmt.Println(p) } case "suites": - // Determine if preflight and/or support-bundle regression suites should run + // Determine impacted suites by dependency mapping, then print exact test names for those suites. preflightRoot := "github.com/replicatedhq/troubleshoot/cmd/preflight" supportRoot := "github.com/replicatedhq/troubleshoot/cmd/troubleshoot" @@ -354,11 +389,50 @@ func main() { fmt.Fprintf(os.Stderr, " preflight: %v\n", preflightHit) fmt.Fprintf(os.Stderr, " support-bundle: %v\n", supportHit) } - if preflightHit { - fmt.Println("preflight") + + // If module files changed, conservatively select all tests for both suites. + if moduleChanged { + preTests, err := listTestFunctions("test/e2e/preflight") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + for _, tname := range preTests { + fmt.Printf("preflight:%s\n", tname) + } + sbTests, err := listTestFunctions("test/e2e/support-bundle") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + for _, tname := range sbTests { + fmt.Printf("support-bundle:%s\n", tname) + } + return } - if supportHit { - fmt.Println("support-bundle") + + // Collect tests for impacted suites and print as `:` + if preflightHit || supportHit { + if preflightHit { + preTests, err := listTestFunctions("test/e2e/preflight") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + for _, tname := range preTests { + fmt.Printf("preflight:%s\n", tname) + } + } + if supportHit { + sbTests, err := listTestFunctions("test/e2e/support-bundle") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + for _, tname := range sbTests { + fmt.Printf("support-bundle:%s\n", tname) + } + } } default: fmt.Fprintln(os.Stderr, "unknown mode; use 'packages' or 'suites'") diff --git a/test-affected-detection.sh b/test-affected-detection.sh new file mode 100755 index 000000000..4cf600caf --- /dev/null +++ b/test-affected-detection.sh @@ -0,0 +1,181 @@ +#!/bin/bash +# Comprehensive test for affected test detection +# Tests various code change scenarios to ensure correct suite detection + +set -e + +# Colors +GREEN='\033[0;32m' +RED='\033[0;31m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +TESTS_PASSED=0 +TESTS_FAILED=0 + +echo "========================================" +echo "Affected Test Detection Validation" +echo "========================================" +echo "" + +# Helper function to run test +run_test() { + local test_name="$1" + local test_file="$2" + local expected_suites="$3" + + echo -e "${BLUE}Test: $test_name${NC}" + echo "File: $test_file" + echo "Expected: $expected_suites" + + # Get affected tests from explicit changed files (no git required); detector prints : + local detector_output=$(go run ./scripts/affected-packages.go -mode=suites -changed-files "$test_file" 2>/dev/null) + # Derive suites from prefixes for comparison + local actual_suites=$(echo "$detector_output" | cut -d':' -f1 | grep -v '^$' | sort | uniq | tr '\n' ' ' | xargs) + + # Compare results + if [ "$actual_suites" = "$expected_suites" ]; then + echo -e "${GREEN}✓ PASS${NC} - Got: $actual_suites" + if [ -n "$detector_output" ]; then + echo "Tests:" && echo "$detector_output" | sed 's/^/ - /' + fi + TESTS_PASSED=$((TESTS_PASSED + 1)) + else + echo -e "${RED}✗ FAIL${NC} - Got: '$actual_suites', Expected: '$expected_suites'" + if [ -n "$detector_output" ]; then + echo "Tests:" && echo "$detector_output" | sed 's/^/ - /' + fi + TESTS_FAILED=$((TESTS_FAILED + 1)) + fi + echo "" +} + +# Test 1: Preflight-only package (should only trigger preflight) +run_test "Preflight-only package change" \ + "pkg/preflight/run.go" \ + "preflight" + +# Test 2: Support-bundle-only package +run_test "Support-bundle-only package change" \ + "pkg/supportbundle/supportbundle.go" \ + "support-bundle" + +# Test 3: Shared package - collect +run_test "Shared package (collect) change" \ + "pkg/collect/run.go" \ + "preflight support-bundle" + +# Test 4: Shared package - analyze +run_test "Shared package (analyze) change" \ + "pkg/analyze/analyzer.go" \ + "preflight support-bundle" + +# Test 5: Shared package - k8sutil +run_test "Shared package (k8sutil) change" \ + "pkg/k8sutil/config.go" \ + "preflight support-bundle" + +# Test 6: Shared package - convert +run_test "Shared package (convert) change" \ + "pkg/convert/output.go" \ + "preflight support-bundle" + +# Test 7: Shared package - redact (another shared one) +run_test "Shared package (redact) change" \ + "pkg/redact/redact.go" \ + "preflight support-bundle" + +# Test 8: Preflight command (should only trigger preflight) +run_test "Preflight command change" \ + "cmd/preflight/main.go" \ + "preflight" + +# Test 9: Support-bundle types (support-bundle only package) +run_test "Support-bundle types change" \ + "pkg/supportbundle/types/types.go" \ + "support-bundle" + +# Test 10: Workflow file (should not trigger e2e) +echo -e "${BLUE}Test: Workflow file change (should trigger nothing)${NC}" +echo "File: .github/workflows/affected-tests.yml" +echo "Expected: (no suites)" + +detector_output=$(go run ./scripts/affected-packages.go -mode=suites -changed-files ".github/workflows/affected-tests.yml" 2>/dev/null) +actual_suites=$(echo "$detector_output" | cut -d':' -f1 | grep -v '^$' | sort | uniq | tr '\n' ' ' | xargs) + +if [ -z "$actual_suites" ]; then + echo -e "${GREEN}✓ PASS${NC} - No suites affected (as expected)" + TESTS_PASSED=$((TESTS_PASSED + 1)) +else + echo -e "${RED}✗ FAIL${NC} - Got: '$actual_suites', Expected: (empty)" + TESTS_FAILED=$((TESTS_FAILED + 1)) +fi +echo "" + +# Test 11: go.mod change (should trigger all) +echo -e "${BLUE}Test: go.mod change (should trigger all suites)${NC}" +echo "File: go.mod" +echo "Expected: preflight support-bundle" + +detector_output=$(go run ./scripts/affected-packages.go -mode=suites -changed-files "go.mod" 2>/dev/null) +actual_suites=$(echo "$detector_output" | cut -d':' -f1 | grep -v '^$' | sort | uniq | tr '\n' ' ' | xargs) + +if [ "$actual_suites" = "preflight support-bundle" ]; then + echo -e "${GREEN}✓ PASS${NC} - Got: $actual_suites" + TESTS_PASSED=$((TESTS_PASSED + 1)) +else + echo -e "${RED}✗ FAIL${NC} - Got: '$actual_suites', Expected: 'preflight support-bundle'" + TESTS_FAILED=$((TESTS_FAILED + 1)) +fi +echo "" + +# Test 12: Multiple files across different areas +echo -e "${BLUE}Test: Multiple file changes (support-bundle + shared)${NC}" +echo "Files: pkg/supportbundle/supportbundle.go + pkg/collect/run.go" +echo "Expected: preflight support-bundle" + +detector_output=$(go run ./scripts/affected-packages.go -mode=suites -changed-files "pkg/supportbundle/supportbundle.go,pkg/collect/run.go" 2>/dev/null) +actual_suites=$(echo "$detector_output" | cut -d':' -f1 | grep -v '^$' | sort | uniq | tr '\n' ' ' | xargs) + +if [ "$actual_suites" = "preflight support-bundle" ]; then + echo -e "${GREEN}✓ PASS${NC} - Got: $actual_suites" + TESTS_PASSED=$((TESTS_PASSED + 1)) +else + echo -e "${RED}✗ FAIL${NC} - Got: '$actual_suites', Expected: 'preflight support-bundle'" + TESTS_FAILED=$((TESTS_FAILED + 1)) +fi +echo "" + +# Test 13: README change (should not trigger e2e) +echo -e "${BLUE}Test: Documentation change (should trigger nothing)${NC}" +echo "File: README.md" +echo "Expected: (no suites)" + +detector_output=$(go run ./scripts/affected-packages.go -mode=suites -changed-files "README.md" 2>/dev/null) +actual_suites=$(echo "$detector_output" | cut -d':' -f1 | grep -v '^$' | sort | uniq | tr '\n' ' ' | xargs) + +if [ -z "$actual_suites" ]; then + echo -e "${GREEN}✓ PASS${NC} - No suites affected (as expected)" + TESTS_PASSED=$((TESTS_PASSED + 1)) +else + echo -e "${RED}✗ FAIL${NC} - Got: '$actual_suites', Expected: (empty)" + TESTS_FAILED=$((TESTS_FAILED + 1)) +fi +echo "" + +# Summary +echo "========================================" +echo -e "${GREEN}Tests Passed: $TESTS_PASSED${NC}" +echo -e "${RED}Tests Failed: $TESTS_FAILED${NC}" +echo "========================================" + +if [ $TESTS_FAILED -eq 0 ]; then + echo -e "${GREEN}✓ All tests passed!${NC}" + exit 0 +else + echo -e "${RED}✗ Some tests failed${NC}" + exit 1 +fi + + From 8762bd151574a7e2fe365f90caa581e2738d3c93 Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Sat, 11 Oct 2025 11:31:43 -0700 Subject: [PATCH 04/30] changed tests always are flagged as affected tests --- .github/workflows/build-test.yaml | 26 +++++++ .github/workflows/regression-test.yaml | 2 +- scripts/affected-packages.go | 102 ++++++++++++++++++++++--- 3 files changed, 118 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index b4a3db749..9e5af070c 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -47,6 +47,19 @@ jobs: - uses: actions/checkout@v5 - uses: ./.github/actions/setup-go + - name: Cache Go build and modules + uses: actions/cache@v4 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Go mod download + run: go mod download + - name: Check go mod tidy run: | go mod tidy @@ -91,6 +104,19 @@ jobs: steps: - uses: actions/checkout@v5 - uses: ./.github/actions/setup-go + + - name: Cache Go build and modules + uses: actions/cache@v4 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Go mod download + run: go mod download - run: make build - uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/regression-test.yaml b/.github/workflows/regression-test.yaml index 5f7f328bd..d1f195075 100644 --- a/.github/workflows/regression-test.yaml +++ b/.github/workflows/regression-test.yaml @@ -2,7 +2,7 @@ name: Regression Test Suite on: push: - branches: [main, v1beta3] + branches: [main] workflow_dispatch: inputs: update_baselines: diff --git a/scripts/affected-packages.go b/scripts/affected-packages.go index 100dfe855..394b9a99f 100644 --- a/scripts/affected-packages.go +++ b/scripts/affected-packages.go @@ -352,7 +352,8 @@ func main() { fmt.Println(p) } case "suites": - // Determine impacted suites by dependency mapping, then print exact test names for those suites. + // Determine impacted suites by dependency mapping and direct e2e test changes, + // then print exact test names for those suites. preflightRoot := "github.com/replicatedhq/troubleshoot/cmd/preflight" supportRoot := "github.com/replicatedhq/troubleshoot/cmd/troubleshoot" @@ -369,6 +370,54 @@ func main() { preflightHit := false supportHit := false + + // Track whether e2e test files were directly changed per suite and collect specific test names + changedPreflightTests := make(map[string]struct{}) + changedSupportTests := make(map[string]struct{}) + preflightE2EChangedNonGo := false + supportE2EChangedNonGo := false + for _, f := range files { + if strings.HasPrefix(f, "test/e2e/preflight/") { + if strings.HasSuffix(f, "_test.go") { + // Extract test names from just this file + b, err := os.ReadFile(f) + if err == nil { // ignore read errors; they will be caught later if needed + scanner := bufio.NewScanner(bytes.NewReader(b)) + re := regexp.MustCompile(`^func\s+(Test[\w\d_]+)\s*\(`) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if m := re.FindStringSubmatch(line); m != nil { + changedPreflightTests[m[1]] = struct{}{} + } + } + } + preflightHit = true + } else { + // Non-go change under preflight e2e; run whole suite + preflightE2EChangedNonGo = true + preflightHit = true + } + } + if strings.HasPrefix(f, "test/e2e/support-bundle/") { + if strings.HasSuffix(f, "_test.go") { + b, err := os.ReadFile(f) + if err == nil { + scanner := bufio.NewScanner(bytes.NewReader(b)) + re := regexp.MustCompile(`^func\s+(Test[\w\d_]+)\s*\(`) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if m := re.FindStringSubmatch(line); m != nil { + changedSupportTests[m[1]] = struct{}{} + } + } + } + supportHit = true + } else { + supportE2EChangedNonGo = true + supportHit = true + } + } + } for changed := range directPkgs { if !preflightHit { if _, ok := preflightDeps[changed]; ok { @@ -414,22 +463,53 @@ func main() { // Collect tests for impacted suites and print as `:` if preflightHit || supportHit { if preflightHit { - preTests, err := listTestFunctions("test/e2e/preflight") - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(2) + toPrint := make(map[string]struct{}) + if preflightE2EChangedNonGo || len(changedPreflightTests) == 0 { + // Run full suite if e2e non-go assets changed or no specific test names collected + preTests, err := listTestFunctions("test/e2e/preflight") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + for _, t := range preTests { + toPrint[t] = struct{}{} + } + } else { + for t := range changedPreflightTests { + toPrint[t] = struct{}{} + } + } + var list []string + for t := range toPrint { + list = append(list, t) } - for _, tname := range preTests { + sort.Strings(list) + for _, tname := range list { fmt.Printf("preflight:%s\n", tname) } } if supportHit { - sbTests, err := listTestFunctions("test/e2e/support-bundle") - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(2) + toPrint := make(map[string]struct{}) + if supportE2EChangedNonGo || len(changedSupportTests) == 0 { + sbTests, err := listTestFunctions("test/e2e/support-bundle") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + for _, t := range sbTests { + toPrint[t] = struct{}{} + } + } else { + for t := range changedSupportTests { + toPrint[t] = struct{}{} + } + } + var list []string + for t := range toPrint { + list = append(list, t) } - for _, tname := range sbTests { + sort.Strings(list) + for _, tname := range list { fmt.Printf("support-bundle:%s\n", tname) } } From 2d664b8736d4448ba5617366406ef1dd0597d64b Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 13 Oct 2025 09:44:39 -0500 Subject: [PATCH 05/30] separated build and test actions for relevant tests --- .../workflows/{build-test.yaml => build.yaml} | 65 ++----------------- .github/workflows/push-full-tests.yml | 47 ++++++++++++++ 2 files changed, 53 insertions(+), 59 deletions(-) rename .github/workflows/{build-test.yaml => build.yaml} (65%) create mode 100644 .github/workflows/push-full-tests.yml diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build.yaml similarity index 65% rename from .github/workflows/build-test.yaml rename to .github/workflows/build.yaml index 9e5af070c..2ad6570d1 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build.yaml @@ -1,4 +1,4 @@ -name: build-test +name: build on: pull_request: @@ -78,22 +78,7 @@ jobs: make vet # Unit and integration tests - test: - if: github.event_name == 'push' && needs.changes.outputs.go-files == 'true' - needs: [changes, lint] - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/checkout@v5 - - uses: ./.github/actions/setup-go - - - name: Setup K3s - uses: replicatedhq/action-k3s@main - with: - version: v1.31.2-k3s1 - - - name: Run tests - run: make test-integration + # (moved to push-full-tests.yml) # Build binaries build: @@ -124,64 +109,26 @@ jobs: path: bin/ retention-days: 1 - # E2E tests - e2e: - if: github.event_name == 'push' - needs: [changes, build] - runs-on: ubuntu-latest - timeout-minutes: 15 - strategy: - fail-fast: false - matrix: - include: - - name: preflight - target: preflight-e2e-test - needs-k3s: true - - name: support-bundle-shell - target: support-bundle-e2e-test - needs-k3s: true - - name: support-bundle-go - target: support-bundle-e2e-go-test - needs-k3s: false - steps: - - uses: actions/checkout@v5 - - - name: Setup K3s - if: matrix.needs-k3s - uses: replicatedhq/action-k3s@main - with: - version: v1.31.2-k3s1 - - - uses: actions/download-artifact@v4 - with: - name: binaries - path: bin/ - - - run: chmod +x bin/* - - run: make ${{ matrix.target }} + # (moved to push-full-tests.yml) # Success summary success: if: always() - needs: [lint, test, build, e2e] + needs: [lint, build] runs-on: ubuntu-latest steps: - name: Check results run: | # Check if any required jobs failed if [[ "${{ needs.lint.result }}" == "failure" ]] || \ - [[ "${{ needs.test.result }}" == "failure" ]] || \ - [[ "${{ needs.build.result }}" == "failure" ]] || \ - [[ "${{ needs.e2e.result }}" == "failure" ]]; then + [[ "${{ needs.build.result }}" == "failure" ]]; then echo "::error::Some jobs failed or were cancelled" exit 1 fi # Check if any required jobs were cancelled if [[ "${{ needs.lint.result }}" == "cancelled" ]] || \ - [[ "${{ needs.test.result }}" == "cancelled" ]] || \ - [[ "${{ needs.build.result }}" == "cancelled" ]] || \ - [[ "${{ needs.e2e.result }}" == "cancelled" ]]; then + [[ "${{ needs.build.result }}" == "cancelled" ]]; then echo "::error::Some jobs failed or were cancelled" exit 1 fi diff --git a/.github/workflows/push-full-tests.yml b/.github/workflows/push-full-tests.yml new file mode 100644 index 000000000..3228be765 --- /dev/null +++ b/.github/workflows/push-full-tests.yml @@ -0,0 +1,47 @@ +name: push-full-tests + +on: + push: + branches: [main] + +jobs: + unit-integration: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v5 + - uses: ./.github/actions/setup-go + - name: Setup K3s + uses: replicatedhq/action-k3s@main + with: + version: v1.31.2-k3s1 + - name: Run tests + run: make test-integration + + e2e: + needs: unit-integration + runs-on: ubuntu-latest + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + include: + - name: preflight + target: preflight-e2e-test + needs-k3s: true + - name: support-bundle-shell + target: support-bundle-e2e-test + needs-k3s: true + - name: support-bundle-go + target: support-bundle-e2e-go-test + needs-k3s: false + steps: + - uses: actions/checkout@v5 + - name: Setup K3s + if: matrix.needs-k3s + uses: replicatedhq/action-k3s@main + with: + version: v1.31.2-k3s1 + - run: make ${{ matrix.target }} + + From d325804359204fc24f74b1a0937258c6ea5cf2ba Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 13 Oct 2025 09:52:47 -0500 Subject: [PATCH 06/30] readded build binary action for tests --- .github/workflows/affected-tests.yml | 4 ++-- .github/workflows/push-full-tests.yml | 32 ++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/.github/workflows/affected-tests.yml b/.github/workflows/affected-tests.yml index 300e60d1f..08a9e31cd 100644 --- a/.github/workflows/affected-tests.yml +++ b/.github/workflows/affected-tests.yml @@ -2,7 +2,7 @@ name: Affected Go Tests on: workflow_run: - workflows: ["build-test"] + workflows: ["build"] types: [completed] branches: [main] @@ -11,7 +11,7 @@ permissions: jobs: test-affected: - # Only run if the build-test workflow succeeded and it's not a draft PR + # Only run if the build workflow succeeded and it's not a draft PR if: | github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' && diff --git a/.github/workflows/push-full-tests.yml b/.github/workflows/push-full-tests.yml index 3228be765..e8396d9eb 100644 --- a/.github/workflows/push-full-tests.yml +++ b/.github/workflows/push-full-tests.yml @@ -18,8 +18,33 @@ jobs: - name: Run tests run: make test-integration + build-binaries: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v5 + - uses: ./.github/actions/setup-go + - name: Cache Go build and modules + uses: actions/cache@v4 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: Go mod download + run: go mod download + - name: Build binaries + run: make build + - uses: actions/upload-artifact@v4 + with: + name: binaries + path: bin/ + retention-days: 1 + e2e: - needs: unit-integration + needs: [unit-integration, build-binaries] runs-on: ubuntu-latest timeout-minutes: 15 strategy: @@ -37,6 +62,11 @@ jobs: needs-k3s: false steps: - uses: actions/checkout@v5 + - uses: actions/download-artifact@v4 + with: + name: binaries + path: bin/ + - run: chmod +x bin/* - name: Setup K3s if: matrix.needs-k3s uses: replicatedhq/action-k3s@main From 1c234e432c93dc41d600aa0bbf2c7d1c129222ea Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 13 Oct 2025 10:00:49 -0500 Subject: [PATCH 07/30] shows affected test summary --- .github/workflows/affected-tests.yml | 20 +++++++++++++++++++- .github/workflows/build-test-deploy.yaml | 20 ++++++++++---------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/.github/workflows/affected-tests.yml b/.github/workflows/affected-tests.yml index 08a9e31cd..8088fce74 100644 --- a/.github/workflows/affected-tests.yml +++ b/.github/workflows/affected-tests.yml @@ -11,7 +11,7 @@ permissions: jobs: test-affected: - # Only run if the build workflow succeeded and it's not a draft PR + # Only run if the build-test workflow succeeded and it's not a draft PR if: | github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' && @@ -99,6 +99,24 @@ jobs: echo "has_changes=false" >> "$GITHUB_OUTPUT" fi + - name: Publish affected summary + if: always() + run: | + { + echo "### Affected unit packages"; + if [ -s /tmp/affected.txt ]; then + sed 's/^/- /' /tmp/affected.txt; + else + echo "- (none)"; + fi; + echo "\n### Affected e2e tests"; + if [ -s /tmp/affected-e2e.txt ]; then + sed 's/^/- /' /tmp/affected-e2e.txt; + else + echo "- (none)"; + fi; + } >> "$GITHUB_STEP_SUMMARY" + # 3) Run filtered tests only - name: Run unit tests for affected packages if: steps.affected.outputs.has_changes == 'true' diff --git a/.github/workflows/build-test-deploy.yaml b/.github/workflows/build-test-deploy.yaml index c471886c8..e77c73087 100644 --- a/.github/workflows/build-test-deploy.yaml +++ b/.github/workflows/build-test-deploy.yaml @@ -37,8 +37,8 @@ jobs: - run: make tidy-diff test-integration: - if: github.event_name == 'push' - runs-on: ubuntu-latest + if: github.event_name == 'push' + runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - uses: actions/setup-go@v6 @@ -66,8 +66,8 @@ jobs: path: bin/preflight validate-preflight-e2e: - if: github.event_name == 'push' - runs-on: ubuntu-latest + if: github.event_name == 'push' + runs-on: ubuntu-latest needs: compile-preflight steps: - uses: actions/checkout@v5 @@ -97,8 +97,8 @@ jobs: path: bin/support-bundle validate-supportbundle-e2e: - if: github.event_name == 'push' - runs-on: ubuntu-latest + if: github.event_name == 'push' + runs-on: ubuntu-latest needs: compile-supportbundle steps: - uses: actions/checkout@v5 @@ -116,10 +116,10 @@ jobs: # Additional e2e tests for support bundle that run in Go, these create a Kind cluster validate-supportbundle-e2e-go: - if: github.event_name == 'push' - runs-on: ubuntu-latest - needs: compile-supportbundle - steps: + if: github.event_name == 'push' + runs-on: ubuntu-latest + needs: compile-supportbundle + steps: - uses: actions/checkout@v5 - name: Download support bundle binary uses: actions/download-artifact@v5 From 0f747f55dc3b0c5c505bf534cae222f7eb9b5645 Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 13 Oct 2025 10:07:26 -0500 Subject: [PATCH 08/30] removed main requirement on affected tests --- .github/workflows/affected-tests.yml | 1 - .github/workflows/build-test-deploy.yaml | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/affected-tests.yml b/.github/workflows/affected-tests.yml index 8088fce74..a34be9878 100644 --- a/.github/workflows/affected-tests.yml +++ b/.github/workflows/affected-tests.yml @@ -4,7 +4,6 @@ on: workflow_run: workflows: ["build"] types: [completed] - branches: [main] permissions: contents: read diff --git a/.github/workflows/build-test-deploy.yaml b/.github/workflows/build-test-deploy.yaml index e77c73087..7d2a78849 100644 --- a/.github/workflows/build-test-deploy.yaml +++ b/.github/workflows/build-test-deploy.yaml @@ -137,6 +137,7 @@ jobs: # summary jobs, these jobs will only run if all the other jobs have succeeded validate-pr-tests: + if: github.event_name == 'push' runs-on: ubuntu-latest needs: - tidy-check @@ -151,6 +152,7 @@ jobs: # this job will validate that the validation did not fail and that all pr-tests succeed # it is used for the github branch protection rule validate-success: + if: github.event_name == 'push' runs-on: ubuntu-latest needs: - validate-pr-tests From e05962bcf3d21cf4df3230ffdc2cd685f7836da9 Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 13 Oct 2025 10:11:46 -0500 Subject: [PATCH 09/30] affected test workflow runs on PR --- .github/workflows/affected-tests.yml | 37 ++++++------------------ .github/workflows/build-test-deploy.yaml | 3 +- 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/.github/workflows/affected-tests.yml b/.github/workflows/affected-tests.yml index a34be9878..9220b1d73 100644 --- a/.github/workflows/affected-tests.yml +++ b/.github/workflows/affected-tests.yml @@ -1,28 +1,21 @@ name: Affected Go Tests on: - workflow_run: - workflows: ["build"] - types: [completed] + pull_request: + types: [opened, reopened, synchronize, ready_for_review] permissions: contents: read jobs: test-affected: - # Only run if the build-test workflow succeeded and it's not a draft PR - if: | - github.event.workflow_run.conclusion == 'success' && - github.event.workflow_run.event == 'pull_request' && - github.event.workflow_run.pull_requests[0].draft == false + if: github.event.pull_request.draft == false runs-on: ubuntu-latest timeout-minutes: 30 steps: - name: Checkout uses: actions/checkout@v4 with: - # Check out the PR merge commit - ref: ${{ github.event.workflow_run.head_sha }} fetch-depth: 0 - name: Setup Go @@ -46,30 +39,20 @@ jobs: go test -c -o /dev/null "$pkg" || exit 1 done < <(go list ./...) - - name: Get PR Base SHA + - name: Compute base ref id: pr-info - env: - GH_TOKEN: ${{ github.token }} run: | - # Get the PR number from the workflow run - PR_NUMBER="${{ github.event.workflow_run.pull_requests[0].number }}" - echo "PR Number: ${PR_NUMBER}" - - # Get the base SHA for this PR - BASE_SHA=$(gh pr view ${PR_NUMBER} --json baseRefOid -q .baseRefOid) - echo "BASE_SHA=${BASE_SHA}" >> "$GITHUB_OUTPUT" - echo "Base SHA: ${BASE_SHA}" + echo "BASE_REF=origin/${{ github.base_ref }}" >> "$GITHUB_OUTPUT" + echo "Base ref: origin/${{ github.base_ref }}" # 2) Detect relevant unit packages and e2e tests - name: Compute affected packages id: affected - env: - BASE_SHA: ${{ steps.pr-info.outputs.BASE_SHA }} run: | set -euo pipefail - echo "Base SHA: ${BASE_SHA}" + echo "Base: ${{ steps.pr-info.outputs.BASE_REF }}" # Generate affected package list to a file for reuse in subsequent steps - go run ./scripts/affected-packages.go -base "${BASE_SHA}" > /tmp/affected.txt + go run ./scripts/affected-packages.go -base "${{ steps.pr-info.outputs.BASE_REF }}" > /tmp/affected.txt echo "Affected packages:" || true if [ -s /tmp/affected.txt ]; then cat /tmp/affected.txt @@ -85,11 +68,9 @@ jobs: - name: Compute affected e2e tests id: affected_e2e - env: - BASE_SHA: ${{ steps.pr-info.outputs.BASE_SHA }} run: | set -euo pipefail - go run ./scripts/affected-packages.go -mode=suites -base "${BASE_SHA}" > /tmp/affected-e2e.txt + go run ./scripts/affected-packages.go -mode=suites -base "${{ steps.pr-info.outputs.BASE_REF }}" > /tmp/affected-e2e.txt awk -F: '$1=="preflight"{print $2}' /tmp/affected-e2e.txt > /tmp/preflight-tests.txt awk -F: '$1=="support-bundle"{print $2}' /tmp/affected-e2e.txt > /tmp/support-tests.txt if [ -s /tmp/preflight-tests.txt ] || [ -s /tmp/support-tests.txt ]; then diff --git a/.github/workflows/build-test-deploy.yaml b/.github/workflows/build-test-deploy.yaml index 7d2a78849..7ae690a37 100644 --- a/.github/workflows/build-test-deploy.yaml +++ b/.github/workflows/build-test-deploy.yaml @@ -152,11 +152,10 @@ jobs: # this job will validate that the validation did not fail and that all pr-tests succeed # it is used for the github branch protection rule validate-success: - if: github.event_name == 'push' + if: ${{ always() && github.event_name == 'push' }} runs-on: ubuntu-latest needs: - validate-pr-tests - if: always() steps: # https://docs.github.com/en/actions/learn-github-actions/contexts#needs-context # if the validate-pr-tests job was not successful, this job will fail From 2b82454ef8783a949096a9d18f9a8fdde52508f7 Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 13 Oct 2025 10:18:43 -0500 Subject: [PATCH 10/30] Update affected-tests.yml --- .github/workflows/affected-tests.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.github/workflows/affected-tests.yml b/.github/workflows/affected-tests.yml index 9220b1d73..d32ba3622 100644 --- a/.github/workflows/affected-tests.yml +++ b/.github/workflows/affected-tests.yml @@ -27,18 +27,6 @@ jobs: - name: Go Mod Download run: go mod download - # 1) Build and compile tests only (no execution) - - name: Build (compile-only) - run: go build ./... - - - name: Compile tests (no execution) - shell: bash - run: | - set -euo pipefail - while read -r pkg; do - go test -c -o /dev/null "$pkg" || exit 1 - done < <(go list ./...) - - name: Compute base ref id: pr-info run: | From cb32216df8414e797c146bdb0ddb707c94f256b0 Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 13 Oct 2025 10:54:59 -0500 Subject: [PATCH 11/30] runs all tests when test change is detected --- scripts/affected-packages.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/scripts/affected-packages.go b/scripts/affected-packages.go index 394b9a99f..04267f7d8 100644 --- a/scripts/affected-packages.go +++ b/scripts/affected-packages.go @@ -281,18 +281,26 @@ func main() { } } - // Track module change to drive conservative behavior. + // Track module change and CI configuration changes to drive conservative behavior. moduleChanged := false + ciChanged := false if *printAllOnChanges { for _, f := range files { if f == "go.mod" || f == "go.sum" { moduleChanged = true - break + } + if strings.HasPrefix(f, "scripts/") || strings.HasPrefix(f, ".github/workflows/") { + ciChanged = true } } - if moduleChanged && *mode == "packages" { + if (moduleChanged || ciChanged) && *mode == "packages" { if *verbose { - fmt.Fprintln(os.Stderr, "Detected module file change (go.mod/go.sum); selecting all packages ./...") + if moduleChanged { + fmt.Fprintln(os.Stderr, "Detected module file change (go.mod/go.sum); selecting all packages ./...") + } + if ciChanged { + fmt.Fprintln(os.Stderr, "Detected CI/detector change (scripts/ or .github/workflows/); selecting all packages ./...") + } } fmt.Println("./...") return @@ -439,8 +447,8 @@ func main() { fmt.Fprintf(os.Stderr, " support-bundle: %v\n", supportHit) } - // If module files changed, conservatively select all tests for both suites. - if moduleChanged { + // If module files or CI/detector changed, conservatively select all tests for both suites. + if moduleChanged || ciChanged { preTests, err := listTestFunctions("test/e2e/preflight") if err != nil { fmt.Fprintln(os.Stderr, err) From e3458ad2978c5432444d44e51ce6e6caa6fdbedd Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 13 Oct 2025 11:29:58 -0500 Subject: [PATCH 12/30] can run unit tests via makefile --- .github/workflows/affected-tests.yml | 15 +++--- Makefile | 11 ++++ scripts/run-affected.sh | 51 +++++++++++++++++++ .../test-affected-detection.sh | 0 4 files changed, 70 insertions(+), 7 deletions(-) create mode 100755 scripts/run-affected.sh rename test-affected-detection.sh => scripts/test-affected-detection.sh (100%) diff --git a/.github/workflows/affected-tests.yml b/.github/workflows/affected-tests.yml index d32ba3622..89926c631 100644 --- a/.github/workflows/affected-tests.yml +++ b/.github/workflows/affected-tests.yml @@ -77,13 +77,14 @@ jobs: else echo "- (none)"; fi; - echo "\n### Affected e2e tests"; + echo; + echo "### Affected e2e tests"; if [ -s /tmp/affected-e2e.txt ]; then sed 's/^/- /' /tmp/affected-e2e.txt; else echo "- (none)"; fi; - } >> "$GITHUB_STEP_SUMMARY" + } | tee -a "$GITHUB_STEP_SUMMARY" # 3) Run filtered tests only - name: Run unit tests for affected packages @@ -93,11 +94,11 @@ jobs: # If the script output contains './...' then run all tests if grep -qx "./..." /tmp/affected.txt; then echo "Module files changed; running all tests" - go test -race -count=1 ./... + make test else echo "Running tests for affected packages" - # xargs will pass the package list as arguments to go test - xargs -a /tmp/affected.txt go test -race -count=1 -v + pkgs=$(tr '\n' ' ' < /tmp/affected.txt) + PACKAGES="$pkgs" make test-packages fi - name: Run preflight e2e (filtered) @@ -106,7 +107,7 @@ jobs: set -euo pipefail if [ -s /tmp/preflight-tests.txt ]; then regex="$(tr '\n' '|' < /tmp/preflight-tests.txt | sed 's/|$//')" - go test -v -count=1 ./test/e2e/preflight -run "^((${regex}))$" + RUN="^((${regex}))$" make support-bundle-e2e-go-test else echo "No preflight e2e changes" fi @@ -117,7 +118,7 @@ jobs: set -euo pipefail if [ -s /tmp/support-tests.txt ]; then regex="$(tr '\n' '|' < /tmp/support-tests.txt | sed 's/|$//')" - go test -v -count=1 ./test/e2e/support-bundle -run "^((${regex}))$" + RUN="^((${regex}))$" make support-bundle-e2e-go-test else echo "No support-bundle e2e changes" fi diff --git a/Makefile b/Makefile index 2588f3bfa..b13b8672f 100644 --- a/Makefile +++ b/Makefile @@ -55,6 +55,17 @@ test: generate fmt vet go test ${BUILDFLAGS} ${BUILDPATHS} ${TESTFLAGS}; \ fi +# Run unit tests only for a provided list of packages. +# Usage: make test-packages PACKAGES="pkg/a pkg/b cmd/foo" +.PHONY: test-packages +test-packages: + @if [ -z "$(PACKAGES)" ]; then \ + echo "No PACKAGES provided; nothing to test."; \ + exit 0; \ + fi + @echo "Running unit tests for packages: $(PACKAGES)" + go test ${BUILDFLAGS} $(PACKAGES) ${TESTFLAGS} + # Go tests that require a K8s instance # TODOLATER: merge with test, so we get unified coverage reports? it'll add 21~sec to the test job though... .PHONY: test-integration diff --git a/scripts/run-affected.sh b/scripts/run-affected.sh new file mode 100755 index 000000000..0cfa92b80 --- /dev/null +++ b/scripts/run-affected.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +set -euo pipefail + +# 0) Preconditions (one-time) +export PATH="$(go env GOPATH)/bin:$PATH" +go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.19.0 >/dev/null +go install k8s.io/code-generator/cmd/client-gen@v0.34.0 >/dev/null +git fetch origin main --depth=1 || true + +# 1) Compute base (robust to unrelated histories) +BASE="$(git merge-base HEAD origin/main 2>/dev/null || true)" +if [ -z "${BASE}" ]; then + echo "No merge-base with origin/main → running full set" + PKGS="./..." + E2E_OUT="$(go run ./scripts/affected-packages.go -mode=suites -changed-files go.mod || true)" +else + PKGS="$(go run ./scripts/affected-packages.go -base "${BASE}")" + E2E_OUT="$(go run ./scripts/affected-packages.go -mode=suites -base "${BASE}")" +fi + +# 2) Print what will run +echo "=== Affected unit packages ===" +if [ -n "${PKGS}" ]; then echo "${PKGS}"; else echo "(none)"; fi +echo +echo "=== Affected e2e tests ===" +if [ -n "${E2E_OUT}" ]; then echo "${E2E_OUT}"; else echo "(none)"; fi +echo + +# 3) Unit tests via Makefile (inherits required build tags) +if [ "${PKGS}" = "./..." ]; then + echo "Running: make test (all)" + make test +elif [ -n "${PKGS}" ]; then + echo "Running: make test-packages for affected pkgs" + PACKAGES="$(echo "${PKGS}" | xargs)" make test-packages +else + echo "No affected unit packages" +fi + +# 4) E2E tests via Makefile (filtered by regex) +PRE="$(echo "${E2E_OUT}" | awk -F: '$1=="preflight"{print $2}' | paste -sd'|' -)" +SB="$( echo "${E2E_OUT}" | awk -F: '$1=="support-bundle"{print $2}' | paste -sd'|' -)" + +if [ -n "${PRE}" ]; then + echo "Running preflight e2e: ${PRE}" + RUN="^((${PRE}))$" make support-bundle-e2e-go-test +fi +if [ -n "${SB}" ]; then + echo "Running support-bundle e2e: ${SB}" + RUN="^((${SB}))$" make support-bundle-e2e-go-test +fi \ No newline at end of file diff --git a/test-affected-detection.sh b/scripts/test-affected-detection.sh similarity index 100% rename from test-affected-detection.sh rename to scripts/test-affected-detection.sh From 45ee61aa4d2f2af9bd8dc0a3e272c314af5fddda Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 13 Oct 2025 12:01:58 -0500 Subject: [PATCH 13/30] creates cluster for tests and removed non testable files from being flagged --- .github/workflows/affected-tests.yml | 23 ++++++++++++++++---- scripts/affected-packages.go | 22 ++++++++++++++++++- scripts/run-affected.sh | 32 +++++++++++++++++++--------- 3 files changed, 62 insertions(+), 15 deletions(-) diff --git a/.github/workflows/affected-tests.yml b/.github/workflows/affected-tests.yml index 89926c631..4683cc9b5 100644 --- a/.github/workflows/affected-tests.yml +++ b/.github/workflows/affected-tests.yml @@ -86,6 +86,13 @@ jobs: fi; } | tee -a "$GITHUB_STEP_SUMMARY" + # Provision a Kubernetes cluster for e2e that depend on it (safe no-op for kind-based Go e2e) + - name: Setup K3s + if: steps.affected_e2e.outputs.has_changes == 'true' + uses: replicatedhq/action-k3s@main + with: + version: v1.31.2-k3s1 + # 3) Run filtered tests only - name: Run unit tests for affected packages if: steps.affected.outputs.has_changes == 'true' @@ -106,8 +113,12 @@ jobs: run: | set -euo pipefail if [ -s /tmp/preflight-tests.txt ]; then - regex="$(tr '\n' '|' < /tmp/preflight-tests.txt | sed 's/|$//')" - RUN="^((${regex}))$" make support-bundle-e2e-go-test + regex="$(grep -v '^$' /tmp/preflight-tests.txt | tr '\n' '|' | sed 's/|$//')" + if [ -n "$regex" ]; then + RUN="^(${regex})$" make preflight-e2e-go-only-test + else + echo "No valid preflight tests matched after filtering" + fi else echo "No preflight e2e changes" fi @@ -117,8 +128,12 @@ jobs: run: | set -euo pipefail if [ -s /tmp/support-tests.txt ]; then - regex="$(tr '\n' '|' < /tmp/support-tests.txt | sed 's/|$//')" - RUN="^((${regex}))$" make support-bundle-e2e-go-test + regex="$(grep -v '^$' /tmp/support-tests.txt | tr '\n' '|' | sed 's/|$//')" + if [ -n "$regex" ]; then + RUN="^(${regex})$" make support-bundle-e2e-go-only-test + else + echo "No valid support-bundle tests matched after filtering" + fi else echo "No support-bundle e2e changes" fi diff --git a/scripts/affected-packages.go b/scripts/affected-packages.go index 04267f7d8..e3fe752dd 100644 --- a/scripts/affected-packages.go +++ b/scripts/affected-packages.go @@ -351,8 +351,28 @@ func main() { } } } - var list []string + // Normalize and filter import paths: + // - Strip test variant suffixes like "pkg [pkg.test]" + // - Exclude e2e test packages (./test/e2e/...) + normalized := make(map[string]struct{}) for p := range affected { + // Trim Go test variant decorations that appear in `go list -test` + if idx := strings.Index(p, " ["); idx != -1 { + p = p[:idx] + } + // Exclude synthetic test packages like github.com/org/repo/pkg.name.test + if strings.HasSuffix(p, ".test") { + continue + } + if strings.Contains(p, "/test/e2e/") { + continue + } + if p != "" { + normalized[p] = struct{}{} + } + } + var list []string + for p := range normalized { list = append(list, p) } sort.Strings(list) diff --git a/scripts/run-affected.sh b/scripts/run-affected.sh index 0cfa92b80..be4631b3d 100755 --- a/scripts/run-affected.sh +++ b/scripts/run-affected.sh @@ -7,15 +7,24 @@ go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.19.0 >/dev/null go install k8s.io/code-generator/cmd/client-gen@v0.34.0 >/dev/null git fetch origin main --depth=1 || true -# 1) Compute base (robust to unrelated histories) -BASE="$(git merge-base HEAD origin/main 2>/dev/null || true)" -if [ -z "${BASE}" ]; then - echo "No merge-base with origin/main → running full set" - PKGS="./..." - E2E_OUT="$(go run ./scripts/affected-packages.go -mode=suites -changed-files go.mod || true)" +# 1) Determine changed files source: explicit args or git base diff +if [ "$#" -gt 0 ]; then + # Treat provided paths as changed files + CHANGED_CSV=$(printf "%s," "$@" | sed 's/,$//') + echo "Simulating changes in: $CHANGED_CSV" + PKGS="$(go run ./scripts/affected-packages.go -changed-files "${CHANGED_CSV}")" + E2E_OUT="$(go run ./scripts/affected-packages.go -mode=suites -changed-files "${CHANGED_CSV}")" else - PKGS="$(go run ./scripts/affected-packages.go -base "${BASE}")" - E2E_OUT="$(go run ./scripts/affected-packages.go -mode=suites -base "${BASE}")" + # Compute base (robust to unrelated histories) + BASE="$(git merge-base HEAD origin/main 2>/dev/null || true)" + if [ -z "${BASE}" ]; then + echo "No merge-base with origin/main → running full set" + PKGS="./..." + E2E_OUT="$(go run ./scripts/affected-packages.go -mode=suites -changed-files go.mod || true)" + else + PKGS="$(go run ./scripts/affected-packages.go -base "${BASE}")" + E2E_OUT="$(go run ./scripts/affected-packages.go -mode=suites -base "${BASE}")" + fi fi # 2) Print what will run @@ -41,11 +50,14 @@ fi PRE="$(echo "${E2E_OUT}" | awk -F: '$1=="preflight"{print $2}' | paste -sd'|' -)" SB="$( echo "${E2E_OUT}" | awk -F: '$1=="support-bundle"{print $2}' | paste -sd'|' -)" +# Use direct go test with the same build tags as the Makefile to avoid RUN quoting issues locally +BUILD_TAGS='netgo containers_image_ostree_stub exclude_graphdriver_devicemapper exclude_graphdriver_btrfs containers_image_openpgp' + if [ -n "${PRE}" ]; then echo "Running preflight e2e: ${PRE}" - RUN="^((${PRE}))$" make support-bundle-e2e-go-test + go test -tags "${BUILD_TAGS}" -installsuffix netgo -v -count=1 ./test/e2e/preflight -run "^(("${PRE}")$)" || true fi if [ -n "${SB}" ]; then echo "Running support-bundle e2e: ${SB}" - RUN="^((${SB}))$" make support-bundle-e2e-go-test + go test -tags "${BUILD_TAGS}" -installsuffix netgo -v -count=1 ./test/e2e/support-bundle -run "^(("${SB}")$)" || true fi \ No newline at end of file From bf1dfa0318cd976709f1adcb9cd8cdebc807f5d1 Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 13 Oct 2025 12:45:59 -0500 Subject: [PATCH 14/30] removed references to nonexistent tests --- .github/workflows/affected-tests.yml | 4 ++-- Makefile | 8 ++++---- scripts/run-affected.sh | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/affected-tests.yml b/.github/workflows/affected-tests.yml index 4683cc9b5..3cdc969e4 100644 --- a/.github/workflows/affected-tests.yml +++ b/.github/workflows/affected-tests.yml @@ -115,7 +115,7 @@ jobs: if [ -s /tmp/preflight-tests.txt ]; then regex="$(grep -v '^$' /tmp/preflight-tests.txt | tr '\n' '|' | sed 's/|$//')" if [ -n "$regex" ]; then - RUN="^(${regex})$" make preflight-e2e-go-only-test + RUN="^(${regex})$" make support-bundle-e2e-go-test else echo "No valid preflight tests matched after filtering" fi @@ -130,7 +130,7 @@ jobs: if [ -s /tmp/support-tests.txt ]; then regex="$(grep -v '^$' /tmp/support-tests.txt | tr '\n' '|' | sed 's/|$//')" if [ -n "$regex" ]; then - RUN="^(${regex})$" make support-bundle-e2e-go-only-test + RUN="^(${regex})$" make support-bundle-e2e-go-test else echo "No valid support-bundle tests matched after filtering" fi diff --git a/Makefile b/Makefile index b13b8672f..6668861cb 100644 --- a/Makefile +++ b/Makefile @@ -49,8 +49,8 @@ ffi: fmt vet .PHONY: test test: generate fmt vet - if [ -n $(RUN) ]; then \ - go test ${BUILDFLAGS} ${BUILDPATHS} ${TESTFLAGS} -run $(RUN); \ + if [ -n "$(RUN)" ]; then \ + go test ${BUILDFLAGS} ${BUILDPATHS} ${TESTFLAGS} -run "$(RUN)"; \ else \ go test ${BUILDFLAGS} ${BUILDPATHS} ${TESTFLAGS}; \ fi @@ -86,8 +86,8 @@ support-bundle-e2e-test: .PHONY: support-bundle-e2e-go-test support-bundle-e2e-go-test: - if [ -n $(RUN) ]; then \ - go test ${BUILDFLAGS} ${E2EPATHS} -v -run $(RUN); \ + if [ -n "$(RUN)" ]; then \ + go test ${BUILDFLAGS} ${E2EPATHS} -v -run "$(RUN)"; \ else \ go test ${BUILDFLAGS} ${E2EPATHS} -v; \ fi diff --git a/scripts/run-affected.sh b/scripts/run-affected.sh index be4631b3d..f3c1b450e 100755 --- a/scripts/run-affected.sh +++ b/scripts/run-affected.sh @@ -55,9 +55,9 @@ BUILD_TAGS='netgo containers_image_ostree_stub exclude_graphdriver_devicemapper if [ -n "${PRE}" ]; then echo "Running preflight e2e: ${PRE}" - go test -tags "${BUILD_TAGS}" -installsuffix netgo -v -count=1 ./test/e2e/preflight -run "^(("${PRE}")$)" || true + go test -tags "${BUILD_TAGS}" -installsuffix netgo -v -count=1 ./test/e2e/preflight -run "^(${PRE})$" || true fi if [ -n "${SB}" ]; then echo "Running support-bundle e2e: ${SB}" - go test -tags "${BUILD_TAGS}" -installsuffix netgo -v -count=1 ./test/e2e/support-bundle -run "^(("${SB}")$)" || true + go test -tags "${BUILD_TAGS}" -installsuffix netgo -v -count=1 ./test/e2e/support-bundle -run "^(${SB})$" || true fi \ No newline at end of file From 6ea8cbcb09feafeda56b20fd183b3f11312f70ce Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 13 Oct 2025 13:00:59 -0500 Subject: [PATCH 15/30] creates clusters for tests via replicated actions --- .github/workflows/affected-tests.yml | 28 ++++++++++++++++++++++++---- scripts/run-affected.sh | 10 +++++++--- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/.github/workflows/affected-tests.yml b/.github/workflows/affected-tests.yml index 3cdc969e4..ad19206ed 100644 --- a/.github/workflows/affected-tests.yml +++ b/.github/workflows/affected-tests.yml @@ -86,12 +86,23 @@ jobs: fi; } | tee -a "$GITHUB_STEP_SUMMARY" - # Provision a Kubernetes cluster for e2e that depend on it (safe no-op for kind-based Go e2e) - - name: Setup K3s + # Provision a Kubernetes cluster for e2e using Replicated compatibility actions + - name: Create k3s cluster + id: create-cluster if: steps.affected_e2e.outputs.has_changes == 'true' - uses: replicatedhq/action-k3s@main + uses: replicatedhq/compatibility-actions/create-cluster@v1 with: - version: v1.31.2-k3s1 + api-token: ${{ secrets.REPLICATED_API_TOKEN }} + kubernetes-distribution: k3s + cluster-name: pr-${{ github.run_id }}-${{ github.run_attempt }} + ttl: 25m + timeout-minutes: 5 + + - name: Configure kubeconfig + if: steps.affected_e2e.outputs.has_changes == 'true' + run: | + echo "${{ steps.create-cluster.outputs.cluster-kubeconfig }}" > $GITHUB_WORKSPACE/kubeconfig.yaml + echo "KUBECONFIG=$GITHUB_WORKSPACE/kubeconfig.yaml" >> $GITHUB_ENV # 3) Run filtered tests only - name: Run unit tests for affected packages @@ -142,4 +153,13 @@ jobs: if: steps.affected.outputs.has_changes != 'true' run: echo "No Go packages affected by this PR; skipping tests." + # Cleanup cluster + - name: Remove cluster + if: always() && steps.affected_e2e.outputs.has_changes == 'true' + uses: replicatedhq/compatibility-actions/remove-cluster@v1 + continue-on-error: true + with: + api-token: ${{ secrets.REPLICATED_API_TOKEN }} + cluster-id: ${{ steps.create-cluster.outputs.cluster-id }} + diff --git a/scripts/run-affected.sh b/scripts/run-affected.sh index f3c1b450e..5ad762d47 100755 --- a/scripts/run-affected.sh +++ b/scripts/run-affected.sh @@ -53,11 +53,15 @@ SB="$( echo "${E2E_OUT}" | awk -F: '$1=="support-bundle"{print $2}' | paste -sd' # Use direct go test with the same build tags as the Makefile to avoid RUN quoting issues locally BUILD_TAGS='netgo containers_image_ostree_stub exclude_graphdriver_devicemapper exclude_graphdriver_btrfs containers_image_openpgp' +overall=0 + if [ -n "${PRE}" ]; then echo "Running preflight e2e: ${PRE}" - go test -tags "${BUILD_TAGS}" -installsuffix netgo -v -count=1 ./test/e2e/preflight -run "^(${PRE})$" || true + go test -tags "${BUILD_TAGS}" -installsuffix netgo -v -count=1 ./test/e2e/preflight -run "^(${PRE})$" || overall=1 fi if [ -n "${SB}" ]; then echo "Running support-bundle e2e: ${SB}" - go test -tags "${BUILD_TAGS}" -installsuffix netgo -v -count=1 ./test/e2e/support-bundle -run "^(${SB})$" || true -fi \ No newline at end of file + go test -tags "${BUILD_TAGS}" -installsuffix netgo -v -count=1 ./test/e2e/support-bundle -run "^(${SB})$" || overall=1 +fi + +exit $overall \ No newline at end of file From a6d3e3e423fcd0a4583b1c3497bee9812d5d47e4 Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 13 Oct 2025 13:02:45 -0500 Subject: [PATCH 16/30] Update affected-tests.yml --- .github/workflows/affected-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/affected-tests.yml b/.github/workflows/affected-tests.yml index ad19206ed..7c5c68661 100644 --- a/.github/workflows/affected-tests.yml +++ b/.github/workflows/affected-tests.yml @@ -126,7 +126,7 @@ jobs: if [ -s /tmp/preflight-tests.txt ]; then regex="$(grep -v '^$' /tmp/preflight-tests.txt | tr '\n' '|' | sed 's/|$//')" if [ -n "$regex" ]; then - RUN="^(${regex})$" make support-bundle-e2e-go-test + RUN="^(${regex})$" make preflight-e2e-go-test else echo "No valid preflight tests matched after filtering" fi From 8f7cf26cc935ecac658d37986d46b0fc1938187b Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 13 Oct 2025 13:13:19 -0500 Subject: [PATCH 17/30] Update affected-tests.yml --- .github/workflows/affected-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/affected-tests.yml b/.github/workflows/affected-tests.yml index 7c5c68661..1b1e28cec 100644 --- a/.github/workflows/affected-tests.yml +++ b/.github/workflows/affected-tests.yml @@ -126,7 +126,7 @@ jobs: if [ -s /tmp/preflight-tests.txt ]; then regex="$(grep -v '^$' /tmp/preflight-tests.txt | tr '\n' '|' | sed 's/|$//')" if [ -n "$regex" ]; then - RUN="^(${regex})$" make preflight-e2e-go-test + RUN="^(${regex})$" make preflight-e2e-test else echo "No valid preflight tests matched after filtering" fi From 1aa74db81d091e1fc21a04360dd43e365d5e8409 Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 13 Oct 2025 13:24:35 -0500 Subject: [PATCH 18/30] Update affected-tests.yml --- .github/workflows/affected-tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/affected-tests.yml b/.github/workflows/affected-tests.yml index 1b1e28cec..6694aba03 100644 --- a/.github/workflows/affected-tests.yml +++ b/.github/workflows/affected-tests.yml @@ -104,6 +104,11 @@ jobs: echo "${{ steps.create-cluster.outputs.cluster-kubeconfig }}" > $GITHUB_WORKSPACE/kubeconfig.yaml echo "KUBECONFIG=$GITHUB_WORKSPACE/kubeconfig.yaml" >> $GITHUB_ENV + - name: Build binaries for script-based e2e + if: steps.affected_e2e.outputs.has_changes == 'true' + run: | + make bin/preflight bin/support-bundle + # 3) Run filtered tests only - name: Run unit tests for affected packages if: steps.affected.outputs.has_changes == 'true' From fb0983480c260be3ad1ab1f61896ecb41efb5173 Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 13 Oct 2025 13:40:06 -0500 Subject: [PATCH 19/30] Update affected-tests.yml --- .github/workflows/affected-tests.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/affected-tests.yml b/.github/workflows/affected-tests.yml index 6694aba03..c4ce1e9f2 100644 --- a/.github/workflows/affected-tests.yml +++ b/.github/workflows/affected-tests.yml @@ -146,7 +146,10 @@ jobs: if [ -s /tmp/support-tests.txt ]; then regex="$(grep -v '^$' /tmp/support-tests.txt | tr '\n' '|' | sed 's/|$//')" if [ -n "$regex" ]; then - RUN="^(${regex})$" make support-bundle-e2e-go-test + # Ensure no stale kind cluster from previous steps + docker rm -f kind-cluster-control-plane 2>/dev/null || true + # Scope to support-bundle suite only to avoid preflight kind interactions + E2EPATHS=./test/e2e/support-bundle RUN="^(${regex})$" make support-bundle-e2e-go-test else echo "No valid support-bundle tests matched after filtering" fi From 78af9208cf09cd7a9b8d51fd52ecea5ca8ffcab8 Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 13 Oct 2025 13:53:09 -0500 Subject: [PATCH 20/30] runs e2e preflight and support bundle tests as matrix --- .github/workflows/affected-tests.yml | 110 ++++++++++++++------------- 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/.github/workflows/affected-tests.yml b/.github/workflows/affected-tests.yml index c4ce1e9f2..5f0e70ed3 100644 --- a/.github/workflows/affected-tests.yml +++ b/.github/workflows/affected-tests.yml @@ -86,28 +86,6 @@ jobs: fi; } | tee -a "$GITHUB_STEP_SUMMARY" - # Provision a Kubernetes cluster for e2e using Replicated compatibility actions - - name: Create k3s cluster - id: create-cluster - if: steps.affected_e2e.outputs.has_changes == 'true' - uses: replicatedhq/compatibility-actions/create-cluster@v1 - with: - api-token: ${{ secrets.REPLICATED_API_TOKEN }} - kubernetes-distribution: k3s - cluster-name: pr-${{ github.run_id }}-${{ github.run_attempt }} - ttl: 25m - timeout-minutes: 5 - - - name: Configure kubeconfig - if: steps.affected_e2e.outputs.has_changes == 'true' - run: | - echo "${{ steps.create-cluster.outputs.cluster-kubeconfig }}" > $GITHUB_WORKSPACE/kubeconfig.yaml - echo "KUBECONFIG=$GITHUB_WORKSPACE/kubeconfig.yaml" >> $GITHUB_ENV - - - name: Build binaries for script-based e2e - if: steps.affected_e2e.outputs.has_changes == 'true' - run: | - make bin/preflight bin/support-bundle # 3) Run filtered tests only - name: Run unit tests for affected packages @@ -124,50 +102,74 @@ jobs: PACKAGES="$pkgs" make test-packages fi - - name: Run preflight e2e (filtered) - if: steps.affected_e2e.outputs.has_changes == 'true' + - name: No affected packages — skip tests + if: steps.affected.outputs.has_changes != 'true' + run: echo "No Go packages affected by this PR; skipping tests." + + e2e-affected: + needs: test-affected + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + suite: [preflight, support-bundle] + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Go Mod Download + run: go mod download + + - name: Compute base ref + id: pr-info + run: | + echo "BASE_REF=origin/${{ github.base_ref }}" >> "$GITHUB_OUTPUT" + echo "Base ref: origin/${{ github.base_ref }}" + + - name: Compute affected e2e tests + id: affected_e2e run: | set -euo pipefail - if [ -s /tmp/preflight-tests.txt ]; then - regex="$(grep -v '^$' /tmp/preflight-tests.txt | tr '\n' '|' | sed 's/|$//')" - if [ -n "$regex" ]; then - RUN="^(${regex})$" make preflight-e2e-test - else - echo "No valid preflight tests matched after filtering" - fi + go run ./scripts/affected-packages.go -mode=suites -base "${{ steps.pr-info.outputs.BASE_REF }}" > /tmp/affected-e2e.txt + awk -F: '$1=="preflight"{print $2}' /tmp/affected-e2e.txt > /tmp/preflight-tests.txt + awk -F: '$1=="support-bundle"{print $2}' /tmp/affected-e2e.txt > /tmp/support-tests.txt + if [ -s /tmp/preflight-tests.txt ] || [ -s /tmp/support-tests.txt ]; then + echo "has_changes=true" >> "$GITHUB_OUTPUT" else - echo "No preflight e2e changes" + echo "has_changes=false" >> "$GITHUB_OUTPUT" fi - - name: Run support-bundle e2e (filtered) + - name: Run e2e (filtered) - ${{ matrix.suite }} if: steps.affected_e2e.outputs.has_changes == 'true' run: | set -euo pipefail - if [ -s /tmp/support-tests.txt ]; then - regex="$(grep -v '^$' /tmp/support-tests.txt | tr '\n' '|' | sed 's/|$//')" + docker rm -f kind-cluster-control-plane 2>/dev/null || true + if [ "${{ matrix.suite }}" = "preflight" ]; then + file=/tmp/preflight-tests.txt + path=./test/e2e/preflight + else + file=/tmp/support-tests.txt + path=./test/e2e/support-bundle + fi + if [ -s "$file" ]; then + regex="$(grep -v '^$' "$file" | tr '\n' '|' | sed 's/|$//')" if [ -n "$regex" ]; then - # Ensure no stale kind cluster from previous steps - docker rm -f kind-cluster-control-plane 2>/dev/null || true - # Scope to support-bundle suite only to avoid preflight kind interactions - E2EPATHS=./test/e2e/support-bundle RUN="^(${regex})$" make support-bundle-e2e-go-test + E2EPATHS="$path" RUN="^(${regex})$" make support-bundle-e2e-go-test else - echo "No valid support-bundle tests matched after filtering" + echo "No valid ${{ matrix.suite }} tests matched after filtering" fi else - echo "No support-bundle e2e changes" + echo "No ${{ matrix.suite }} e2e changes" fi - - name: No affected packages — skip tests - if: steps.affected.outputs.has_changes != 'true' - run: echo "No Go packages affected by this PR; skipping tests." - - # Cleanup cluster - - name: Remove cluster - if: always() && steps.affected_e2e.outputs.has_changes == 'true' - uses: replicatedhq/compatibility-actions/remove-cluster@v1 - continue-on-error: true - with: - api-token: ${{ secrets.REPLICATED_API_TOKEN }} - cluster-id: ${{ steps.create-cluster.outputs.cluster-id }} - From 409a02a125972d94949af00f8a488ec87bc28413 Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 13 Oct 2025 14:02:20 -0500 Subject: [PATCH 21/30] Update affected-tests.yml --- .github/workflows/affected-tests.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/affected-tests.yml b/.github/workflows/affected-tests.yml index 5f0e70ed3..8024baba6 100644 --- a/.github/workflows/affected-tests.yml +++ b/.github/workflows/affected-tests.yml @@ -27,6 +27,10 @@ jobs: - name: Go Mod Download run: go mod download + - name: Build e2e binaries + run: | + make bin/preflight bin/support-bundle + - name: Compute base ref id: pr-info run: | From c84ea20b884f15dcb16be78d9632a6faad2ccb83 Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 13 Oct 2025 14:19:27 -0500 Subject: [PATCH 22/30] e2e test make target build the binaries first --- .github/workflows/affected-tests.yml | 45 +++++++++++++++++----------- Makefile | 4 +-- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/.github/workflows/affected-tests.yml b/.github/workflows/affected-tests.yml index 8024baba6..00cddfef7 100644 --- a/.github/workflows/affected-tests.yml +++ b/.github/workflows/affected-tests.yml @@ -91,21 +91,6 @@ jobs: } | tee -a "$GITHUB_STEP_SUMMARY" - # 3) Run filtered tests only - - name: Run unit tests for affected packages - if: steps.affected.outputs.has_changes == 'true' - run: | - set -euo pipefail - # If the script output contains './...' then run all tests - if grep -qx "./..." /tmp/affected.txt; then - echo "Module files changed; running all tests" - make test - else - echo "Running tests for affected packages" - pkgs=$(tr '\n' ' ' < /tmp/affected.txt) - PACKAGES="$pkgs" make test-packages - fi - - name: No affected packages — skip tests if: steps.affected.outputs.has_changes != 'true' run: echo "No Go packages affected by this PR; skipping tests." @@ -118,7 +103,7 @@ jobs: strategy: fail-fast: false matrix: - suite: [preflight, support-bundle] + suite: [unit, preflight, support-bundle] steps: - name: Checkout uses: actions/checkout@v4 @@ -140,6 +125,14 @@ jobs: echo "BASE_REF=origin/${{ github.base_ref }}" >> "$GITHUB_OUTPUT" echo "Base ref: origin/${{ github.base_ref }}" + - name: Compute affected packages + id: affected + run: | + set -euo pipefail + echo "Base: ${{ steps.pr-info.outputs.BASE_REF }}" + go run ./scripts/affected-packages.go -base "${{ steps.pr-info.outputs.BASE_REF }}" > /tmp/affected.txt + if [ -s /tmp/affected.txt ]; then echo "has_changes=true" >> "$GITHUB_OUTPUT"; else echo "has_changes=false" >> "$GITHUB_OUTPUT"; fi + - name: Compute affected e2e tests id: affected_e2e run: | @@ -153,8 +146,26 @@ jobs: echo "has_changes=false" >> "$GITHUB_OUTPUT" fi + - name: Run unit tests (filtered) + if: matrix.suite == 'unit' && steps.affected.outputs.has_changes == 'true' + run: | + set -euo pipefail + if grep -qx "./..." /tmp/affected.txt; then + echo "Module files changed; running all unit tests" + make test + else + echo "Running unit tests for affected packages" + pkgs=$(tr '\n' ' ' < /tmp/affected.txt) + PACKAGES="$pkgs" make test-packages + fi + + - name: Build e2e binaries + if: matrix.suite != 'unit' + run: | + make bin/preflight bin/support-bundle + - name: Run e2e (filtered) - ${{ matrix.suite }} - if: steps.affected_e2e.outputs.has_changes == 'true' + if: matrix.suite != 'unit' && steps.affected_e2e.outputs.has_changes == 'true' run: | set -euo pipefail docker rm -f kind-cluster-control-plane 2>/dev/null || true diff --git a/Makefile b/Makefile index 6668861cb..bb901a84e 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,7 @@ endef BUILDTAGS = "netgo containers_image_ostree_stub exclude_graphdriver_devicemapper exclude_graphdriver_btrfs containers_image_openpgp" BUILDFLAGS = -tags ${BUILDTAGS} -installsuffix netgo BUILDPATHS = ./pkg/... ./cmd/... ./internal/... -E2EPATHS = ./test/e2e/... +E2EPATHS ?= ./test/e2e/... TESTFLAGS ?= -v -coverprofile cover.out .DEFAULT_GOAL := all @@ -85,7 +85,7 @@ support-bundle-e2e-test: ./test/validate-support-bundle-e2e.sh .PHONY: support-bundle-e2e-go-test -support-bundle-e2e-go-test: +support-bundle-e2e-go-test: bin/preflight bin/support-bundle if [ -n "$(RUN)" ]; then \ go test ${BUILDFLAGS} ${E2EPATHS} -v -run "$(RUN)"; \ else \ From a677646b170605e52e8ab2b2a7793f7fe47d1869 Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 13 Oct 2025 14:31:14 -0500 Subject: [PATCH 23/30] reduced redundant building --- .github/workflows/affected-tests.yml | 13 +++++-------- Makefile | 10 +++++++++- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.github/workflows/affected-tests.yml b/.github/workflows/affected-tests.yml index 00cddfef7..cb673c7cb 100644 --- a/.github/workflows/affected-tests.yml +++ b/.github/workflows/affected-tests.yml @@ -27,9 +27,6 @@ jobs: - name: Go Mod Download run: go mod download - - name: Build e2e binaries - run: | - make bin/preflight bin/support-bundle - name: Compute base ref id: pr-info @@ -159,10 +156,6 @@ jobs: PACKAGES="$pkgs" make test-packages fi - - name: Build e2e binaries - if: matrix.suite != 'unit' - run: | - make bin/preflight bin/support-bundle - name: Run e2e (filtered) - ${{ matrix.suite }} if: matrix.suite != 'unit' && steps.affected_e2e.outputs.has_changes == 'true' @@ -179,7 +172,11 @@ jobs: if [ -s "$file" ]; then regex="$(grep -v '^$' "$file" | tr '\n' '|' | sed 's/|$//')" if [ -n "$regex" ]; then - E2EPATHS="$path" RUN="^(${regex})$" make support-bundle-e2e-go-test + if [ "${{ matrix.suite }}" = "preflight" ]; then + E2EPATHS="$path" RUN="^(${regex})$" make preflight-e2e-go-test + else + E2EPATHS="$path" RUN="^(${regex})$" make support-bundle-e2e-go-test + fi else echo "No valid ${{ matrix.suite }} tests matched after filtering" fi diff --git a/Makefile b/Makefile index bb901a84e..d4a0f7177 100644 --- a/Makefile +++ b/Makefile @@ -84,8 +84,16 @@ run-examples: support-bundle-e2e-test: ./test/validate-support-bundle-e2e.sh +.PHONY: preflight-e2e-go-test +preflight-e2e-go-test: bin/preflight + if [ -n "$(RUN)" ]; then \ + go test ${BUILDFLAGS} ${E2EPATHS} -v -run "$(RUN)"; \ + else \ + go test ${BUILDFLAGS} ${E2EPATHS} -v; \ + fi + .PHONY: support-bundle-e2e-go-test -support-bundle-e2e-go-test: bin/preflight bin/support-bundle +support-bundle-e2e-go-test: bin/support-bundle if [ -n "$(RUN)" ]; then \ go test ${BUILDFLAGS} ${E2EPATHS} -v -run "$(RUN)"; \ else \ From 8920a9d09aaeb8867146f1f7839323f8ef41c0f1 Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 13 Oct 2025 14:35:25 -0500 Subject: [PATCH 24/30] Update Makefile --- Makefile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index d4a0f7177..db7d556f8 100644 --- a/Makefile +++ b/Makefile @@ -86,11 +86,11 @@ support-bundle-e2e-test: .PHONY: preflight-e2e-go-test preflight-e2e-go-test: bin/preflight - if [ -n "$(RUN)" ]; then \ - go test ${BUILDFLAGS} ${E2EPATHS} -v -run "$(RUN)"; \ - else \ - go test ${BUILDFLAGS} ${E2EPATHS} -v; \ - fi + if [ -n "$(RUN)" ]; then \ + go test ${BUILDFLAGS} ${E2EPATHS} -v -run "$(RUN)"; \ + else \ + go test ${BUILDFLAGS} ${E2EPATHS} -v; \ + fi .PHONY: support-bundle-e2e-go-test support-bundle-e2e-go-test: bin/support-bundle From f7a941c220b9c196d51cefe0af2eb138cd6ef012 Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Tue, 14 Oct 2025 08:11:07 -0500 Subject: [PATCH 25/30] regression tests run separate from relevant test detection --- .github/workflows/regression-test.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/regression-test.yaml b/.github/workflows/regression-test.yaml index d1f195075..18dee27d2 100644 --- a/.github/workflows/regression-test.yaml +++ b/.github/workflows/regression-test.yaml @@ -3,6 +3,8 @@ name: Regression Test Suite on: push: branches: [main] + pull_request: + types: [opened, reopened, synchronize, ready_for_review] workflow_dispatch: inputs: update_baselines: @@ -12,7 +14,8 @@ on: jobs: regression-test: - if: github.event_name != 'pull_request' + # Run on push, manual dispatch, and internal PRs (skip fork PRs due to secrets) + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false runs-on: ubuntu-22.04 timeout-minutes: 25 From e084c0a46ad52bad93ba2321c0163be9fa308ba8 Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Tue, 14 Oct 2025 09:53:58 -0500 Subject: [PATCH 26/30] regression tests run in matrix --- .github/workflows/regression-test.yaml | 353 +++++++++---------------- 1 file changed, 122 insertions(+), 231 deletions(-) diff --git a/.github/workflows/regression-test.yaml b/.github/workflows/regression-test.yaml index 18dee27d2..723d571f7 100644 --- a/.github/workflows/regression-test.yaml +++ b/.github/workflows/regression-test.yaml @@ -13,24 +13,16 @@ on: default: false jobs: - regression-test: - # Run on push, manual dispatch, and internal PRs (skip fork PRs due to secrets) + create-cluster: + # Create a shared cluster for the matrix; skip on fork PRs due to secrets if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false runs-on: ubuntu-22.04 - timeout-minutes: 25 - + outputs: + kubeconfig: ${{ steps.create.outputs.cluster-kubeconfig }} + cluster-id: ${{ steps.create.outputs.cluster-id }} steps: - # 1. SETUP - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Fetch all history for git describe to work - - - name: Create output directory - run: mkdir -p test/output - - name: Create k3s cluster - id: create-cluster + id: create uses: replicatedhq/compatibility-actions/create-cluster@v1 with: api-token: ${{ secrets.REPLICATED_API_TOKEN }} @@ -39,13 +31,21 @@ jobs: ttl: 25m timeout-minutes: 5 - - name: Configure kubeconfig - run: | - echo "${{ steps.create-cluster.outputs.cluster-kubeconfig }}" > $GITHUB_WORKSPACE/kubeconfig.yaml - echo "KUBECONFIG=$GITHUB_WORKSPACE/kubeconfig.yaml" >> $GITHUB_ENV - - - name: Verify cluster access - run: kubectl get nodes -o wide + regression: + # Run on push, manual dispatch, and internal PRs (skip fork PRs) + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false + needs: create-cluster + runs-on: ubuntu-22.04 + timeout-minutes: 25 + strategy: + fail-fast: false + matrix: + suite: [preflight-v1beta3, preflight-v1beta2, support-bundle] + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Setup Go uses: actions/setup-go@v5 @@ -54,13 +54,6 @@ jobs: cache: true cache-dependency-path: go.sum - - name: Build binaries - run: | - echo "Building preflight and support-bundle binaries..." - make bin/preflight bin/support-bundle - ./bin/preflight version - ./bin/support-bundle version - - name: Setup Python for comparison uses: actions/setup-python@v5 with: @@ -70,225 +63,123 @@ jobs: run: | pip install pyyaml deepdiff - # 2. EXECUTE SPECS (in parallel) - - name: Run all specs in parallel - continue-on-error: true + - name: Configure kubeconfig (support-bundle only) + if: matrix.suite == 'support-bundle' run: | - echo "Running all 3 specs in parallel..." - - # Run v1beta3 in background - ( - echo "Starting preflight v1beta3..." - ./bin/preflight \ - examples/preflight/complex-v1beta3.yaml \ - --values examples/preflight/values-complex-full.yaml \ - --interactive=false \ - --format=json \ - --output=test/output/preflight-results-v1beta3.json 2>&1 | tee test/output/v1beta3.log || true - - BUNDLE=$(ls -t preflightbundle-*.tar.gz 2>/dev/null | head -1) - if [ -n "$BUNDLE" ]; then - mv "$BUNDLE" test/output/preflight-v1beta3-bundle.tar.gz - echo "✓ v1beta3 bundle saved" - fi - ) & - PID_V1BETA3=$! - - # Run v1beta2 in background - ( - echo "Starting preflight v1beta2..." - ./bin/preflight \ - examples/preflight/all-analyzers-v1beta2.yaml \ - --interactive=false \ - --format=json \ - --output=test/output/preflight-results-v1beta2.json 2>&1 | tee test/output/v1beta2.log || true - - BUNDLE=$(ls -t preflightbundle-*.tar.gz 2>/dev/null | head -1) - if [ -n "$BUNDLE" ]; then - mv "$BUNDLE" test/output/preflight-v1beta2-bundle.tar.gz - echo "✓ v1beta2 bundle saved" - fi - ) & - PID_V1BETA2=$! - - # Run support bundle in background - ( - echo "Starting support bundle..." - ./bin/support-bundle \ - examples/collect/host/all-kubernetes-collectors.yaml \ - --interactive=false \ - --output=test/output/supportbundle.tar.gz 2>&1 | tee test/output/supportbundle.log || true - - if [ -f test/output/supportbundle.tar.gz ]; then - echo "✓ Support bundle saved" - fi - ) & - PID_SUPPORTBUNDLE=$! - - # Wait for all to complete - echo "Waiting for all specs to complete..." - wait $PID_V1BETA3 - wait $PID_V1BETA2 - wait $PID_SUPPORTBUNDLE - - echo "All specs completed!" - - # Verify bundles exist - ls -lh test/output/*.tar.gz || echo "Warning: Some bundles may be missing" - - # 3. COMPARE BUNDLES - - name: Compare preflight v1beta3 bundle - id: compare-v1beta3 - continue-on-error: true - run: | - echo "Comparing v1beta3 preflight bundle against baseline..." - if [ ! -f test/baselines/preflight-v1beta3/baseline.tar.gz ]; then - echo "⚠ No baseline found for v1beta3 - skipping comparison" - echo "baseline_missing=true" >> $GITHUB_OUTPUT - exit 0 - fi + echo "${{ needs.create-cluster.outputs.kubeconfig }}" > $GITHUB_WORKSPACE/kubeconfig.yaml + echo "KUBECONFIG=$GITHUB_WORKSPACE/kubeconfig.yaml" >> $GITHUB_ENV - python3 scripts/compare_bundles.py \ - --baseline test/baselines/preflight-v1beta3/baseline.tar.gz \ - --current test/output/preflight-v1beta3-bundle.tar.gz \ - --rules scripts/compare_rules.yaml \ - --report test/output/diff-report-v1beta3.json \ - --spec-type preflight + - name: Verify cluster access (support-bundle only) + if: matrix.suite == 'support-bundle' + run: kubectl get nodes -o wide - - name: Compare preflight v1beta2 bundle - id: compare-v1beta2 - continue-on-error: true + - name: Build binary for suite run: | - echo "Comparing v1beta2 preflight bundle against baseline..." - if [ ! -f test/baselines/preflight-v1beta2/baseline.tar.gz ]; then - echo "⚠ No baseline found for v1beta2 - skipping comparison" - echo "baseline_missing=true" >> $GITHUB_OUTPUT - exit 0 - fi - - python3 scripts/compare_bundles.py \ - --baseline test/baselines/preflight-v1beta2/baseline.tar.gz \ - --current test/output/preflight-v1beta2-bundle.tar.gz \ - --rules scripts/compare_rules.yaml \ - --report test/output/diff-report-v1beta2.json \ - --spec-type preflight - - - name: Compare support bundle - id: compare-supportbundle - continue-on-error: true + case "${{ matrix.suite }}" in + preflight-*) + make bin/preflight + ./bin/preflight version + ;; + support-bundle) + make bin/support-bundle + ./bin/support-bundle version + ;; + esac + + - name: Run suite run: | - echo "Comparing support bundle against baseline..." - if [ ! -f test/baselines/supportbundle/baseline.tar.gz ]; then - echo "⚠ No baseline found for support bundle - skipping comparison" - echo "baseline_missing=true" >> $GITHUB_OUTPUT - exit 0 - fi - - python3 scripts/compare_bundles.py \ - --baseline test/baselines/supportbundle/baseline.tar.gz \ - --current test/output/supportbundle.tar.gz \ - --rules scripts/compare_rules.yaml \ - --report test/output/diff-report-supportbundle.json \ - --spec-type supportbundle - - # 4. REPORT RESULTS - - name: Generate summary report - if: always() + mkdir -p test/output + case "${{ matrix.suite }}" in + preflight-v1beta3) + ./bin/preflight \ + examples/preflight/complex-v1beta3.yaml \ + --values examples/preflight/values-complex-full.yaml \ + --interactive=false \ + --format=json \ + --output=test/output/preflight-results-v1beta3.json + BUNDLE=$(ls -t preflightbundle-*.tar.gz 2>/dev/null | head -1) + if [ -n "$BUNDLE" ]; then + mv "$BUNDLE" test/output/preflight-v1beta3-bundle.tar.gz + fi + ;; + preflight-v1beta2) + ./bin/preflight \ + examples/preflight/all-analyzers-v1beta2.yaml \ + --interactive=false \ + --format=json \ + --output=test/output/preflight-results-v1beta2.json + BUNDLE=$(ls -t preflightbundle-*.tar.gz 2>/dev/null | head -1) + if [ -n "$BUNDLE" ]; then + mv "$BUNDLE" test/output/preflight-v1beta2-bundle.tar.gz + fi + ;; + support-bundle) + ./bin/support-bundle \ + examples/collect/host/all-kubernetes-collectors.yaml \ + --interactive=false \ + --output=test/output/supportbundle.tar.gz + ;; + esac + + - name: Compare against baselines run: | - python3 scripts/generate_summary.py \ - --reports test/output/diff-report-*.json \ - --output-file $GITHUB_STEP_SUMMARY \ - --output-console - - - name: Upload test artifacts + case "${{ matrix.suite }}" in + preflight-v1beta3) + if [ -f test/baselines/preflight-v1beta3/baseline.tar.gz ]; then + python3 scripts/compare_bundles.py \ + --baseline test/baselines/preflight-v1beta3/baseline.tar.gz \ + --current test/output/preflight-v1beta3-bundle.tar.gz \ + --rules scripts/compare_rules.yaml \ + --report test/output/diff-report-v1beta3.json \ + --spec-type preflight + else + echo "No baseline for v1beta3; skipping comparison" + fi + ;; + preflight-v1beta2) + if [ -f test/baselines/preflight-v1beta2/baseline.tar.gz ]; then + python3 scripts/compare_bundles.py \ + --baseline test/baselines/preflight-v1beta2/baseline.tar.gz \ + --current test/output/preflight-v1beta2-bundle.tar.gz \ + --rules scripts/compare_rules.yaml \ + --report test/output/diff-report-v1beta2.json \ + --spec-type preflight + else + echo "No baseline for v1beta2; skipping comparison" + fi + ;; + support-bundle) + if [ -f test/baselines/supportbundle/baseline.tar.gz ]; then + python3 scripts/compare_bundles.py \ + --baseline test/baselines/supportbundle/baseline.tar.gz \ + --current test/output/supportbundle.tar.gz \ + --rules scripts/compare_rules.yaml \ + --report test/output/diff-report-supportbundle.json \ + --spec-type supportbundle + else + echo "No baseline for support bundle; skipping comparison" + fi + ;; + esac + + - name: Upload artifacts if: always() uses: actions/upload-artifact@v4 with: - name: regression-test-results-${{ github.run_id }}-${{ github.run_attempt }} + name: regression-${{ matrix.suite }}-${{ github.run_id }}-${{ github.run_attempt }} path: | test/output/*.tar.gz test/output/*.json retention-days: 30 - - name: Check for regressions - if: always() - run: | - echo "Checking comparison results..." - - # Check if any comparisons failed - FAILURES=0 - - if [ "${{ steps.compare-v1beta3.outcome }}" == "failure" ] && [ "${{ steps.compare-v1beta3.outputs.baseline_missing }}" != "true" ]; then - echo "❌ v1beta3 comparison failed" - FAILURES=$((FAILURES + 1)) - fi - - if [ "${{ steps.compare-v1beta2.outcome }}" == "failure" ] && [ "${{ steps.compare-v1beta2.outputs.baseline_missing }}" != "true" ]; then - echo "❌ v1beta2 comparison failed" - FAILURES=$((FAILURES + 1)) - fi - - if [ "${{ steps.compare-supportbundle.outcome }}" == "failure" ] && [ "${{ steps.compare-supportbundle.outputs.baseline_missing }}" != "true" ]; then - echo "❌ Support bundle comparison failed" - FAILURES=$((FAILURES + 1)) - fi - - if [ $FAILURES -gt 0 ]; then - echo "" - echo "❌ $FAILURES regression(s) detected!" - echo "Review the comparison reports in the artifacts." - exit 1 - else - echo "✅ All comparisons passed or skipped (no baseline)" - fi - - # 5. UPDATE BASELINES (optional, manual trigger only) - - name: Update baselines - if: github.event.inputs.update_baselines == 'true' && github.event_name == 'workflow_dispatch' - run: | - echo "Updating baselines with current bundles..." - - # Copy new bundles as baselines - if [ -f test/output/preflight-v1beta3-bundle.tar.gz ]; then - mkdir -p test/baselines/preflight-v1beta3 - cp test/output/preflight-v1beta3-bundle.tar.gz test/baselines/preflight-v1beta3/baseline.tar.gz - echo "✓ Updated v1beta3 baseline" - fi - - if [ -f test/output/preflight-v1beta2-bundle.tar.gz ]; then - mkdir -p test/baselines/preflight-v1beta2 - cp test/output/preflight-v1beta2-bundle.tar.gz test/baselines/preflight-v1beta2/baseline.tar.gz - echo "✓ Updated v1beta2 baseline" - fi - - if [ -f test/output/supportbundle.tar.gz ]; then - mkdir -p test/baselines/supportbundle - cp test/output/supportbundle.tar.gz test/baselines/supportbundle/baseline.tar.gz - echo "✓ Updated support bundle baseline" - fi - - # Create metadata file - cat > test/baselines/metadata.json < Date: Tue, 14 Oct 2025 10:00:35 -0500 Subject: [PATCH 27/30] use kubeconfig to properly set up preflight regression tests --- .github/workflows/regression-test.yaml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/regression-test.yaml b/.github/workflows/regression-test.yaml index 723d571f7..e0e7ca2d8 100644 --- a/.github/workflows/regression-test.yaml +++ b/.github/workflows/regression-test.yaml @@ -63,14 +63,12 @@ jobs: run: | pip install pyyaml deepdiff - - name: Configure kubeconfig (support-bundle only) - if: matrix.suite == 'support-bundle' + - name: Configure kubeconfig run: | echo "${{ needs.create-cluster.outputs.kubeconfig }}" > $GITHUB_WORKSPACE/kubeconfig.yaml echo "KUBECONFIG=$GITHUB_WORKSPACE/kubeconfig.yaml" >> $GITHUB_ENV - - name: Verify cluster access (support-bundle only) - if: matrix.suite == 'support-bundle' + - name: Verify cluster access run: kubectl get nodes -o wide - name: Build binary for suite @@ -96,6 +94,7 @@ jobs: --values examples/preflight/values-complex-full.yaml \ --interactive=false \ --format=json \ + --kubeconfig=$GITHUB_WORKSPACE/kubeconfig.yaml \ --output=test/output/preflight-results-v1beta3.json BUNDLE=$(ls -t preflightbundle-*.tar.gz 2>/dev/null | head -1) if [ -n "$BUNDLE" ]; then @@ -107,6 +106,7 @@ jobs: examples/preflight/all-analyzers-v1beta2.yaml \ --interactive=false \ --format=json \ + --kubeconfig=$GITHUB_WORKSPACE/kubeconfig.yaml \ --output=test/output/preflight-results-v1beta2.json BUNDLE=$(ls -t preflightbundle-*.tar.gz 2>/dev/null | head -1) if [ -n "$BUNDLE" ]; then @@ -117,6 +117,7 @@ jobs: ./bin/support-bundle \ examples/collect/host/all-kubernetes-collectors.yaml \ --interactive=false \ + --kubeconfig=$GITHUB_WORKSPACE/kubeconfig.yaml \ --output=test/output/supportbundle.tar.gz ;; esac From e73e1375ccfb366f500295861fb2056cedac1a5b Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Tue, 14 Oct 2025 10:14:33 -0500 Subject: [PATCH 28/30] uses unique namespaces for different preflight regression tests --- .github/workflows/regression-test.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/regression-test.yaml b/.github/workflows/regression-test.yaml index e0e7ca2d8..893559e8e 100644 --- a/.github/workflows/regression-test.yaml +++ b/.github/workflows/regression-test.yaml @@ -71,6 +71,13 @@ jobs: - name: Verify cluster access run: kubectl get nodes -o wide + - name: Prepare namespace (preflight only) + if: startsWith(matrix.suite, 'preflight-') + run: | + ns="ts-${{ matrix.suite }}-${{ github.run_id }}-${{ github.run_attempt }}" + echo "E2E_NS=$ns" >> $GITHUB_ENV + kubectl create namespace "$ns" + - name: Build binary for suite run: | case "${{ matrix.suite }}" in @@ -94,7 +101,9 @@ jobs: --values examples/preflight/values-complex-full.yaml \ --interactive=false \ --format=json \ + --auto-update=false \ --kubeconfig=$GITHUB_WORKSPACE/kubeconfig.yaml \ + -n "$E2E_NS" \ --output=test/output/preflight-results-v1beta3.json BUNDLE=$(ls -t preflightbundle-*.tar.gz 2>/dev/null | head -1) if [ -n "$BUNDLE" ]; then @@ -106,7 +115,9 @@ jobs: examples/preflight/all-analyzers-v1beta2.yaml \ --interactive=false \ --format=json \ + --auto-update=false \ --kubeconfig=$GITHUB_WORKSPACE/kubeconfig.yaml \ + -n "$E2E_NS" \ --output=test/output/preflight-results-v1beta2.json BUNDLE=$(ls -t preflightbundle-*.tar.gz 2>/dev/null | head -1) if [ -n "$BUNDLE" ]; then From 1013f2a160789b0ddd107db68e8bd3d4c4b17c65 Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Tue, 14 Oct 2025 10:24:27 -0500 Subject: [PATCH 29/30] does not recalculate affected e2e tests --- .github/workflows/affected-tests.yml | 59 +++++++++++++++------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/.github/workflows/affected-tests.yml b/.github/workflows/affected-tests.yml index cb673c7cb..138fc1887 100644 --- a/.github/workflows/affected-tests.yml +++ b/.github/workflows/affected-tests.yml @@ -12,6 +12,9 @@ jobs: if: github.event.pull_request.draft == false runs-on: ubuntu-latest timeout-minutes: 30 + outputs: + unit_has_changes: ${{ steps.affected.outputs.has_changes }} + e2e_has_changes: ${{ steps.affected_e2e.outputs.has_changes }} steps: - name: Checkout uses: actions/checkout@v4 @@ -87,6 +90,23 @@ jobs: fi; } | tee -a "$GITHUB_STEP_SUMMARY" + - name: Upload affected unit packages + uses: actions/upload-artifact@v4 + with: + name: affected-unit + path: /tmp/affected.txt + if-no-files-found: warn + + - name: Upload affected e2e artifacts + uses: actions/upload-artifact@v4 + with: + name: affected-e2e + path: | + /tmp/affected-e2e.txt + /tmp/preflight-tests.txt + /tmp/support-tests.txt + if-no-files-found: warn + - name: No affected packages — skip tests if: steps.affected.outputs.has_changes != 'true' @@ -116,35 +136,20 @@ jobs: - name: Go Mod Download run: go mod download - - name: Compute base ref - id: pr-info - run: | - echo "BASE_REF=origin/${{ github.base_ref }}" >> "$GITHUB_OUTPUT" - echo "Base ref: origin/${{ github.base_ref }}" - - - name: Compute affected packages - id: affected - run: | - set -euo pipefail - echo "Base: ${{ steps.pr-info.outputs.BASE_REF }}" - go run ./scripts/affected-packages.go -base "${{ steps.pr-info.outputs.BASE_REF }}" > /tmp/affected.txt - if [ -s /tmp/affected.txt ]; then echo "has_changes=true" >> "$GITHUB_OUTPUT"; else echo "has_changes=false" >> "$GITHUB_OUTPUT"; fi + - name: Download affected unit packages + uses: actions/download-artifact@v4 + with: + name: affected-unit + path: /tmp - - name: Compute affected e2e tests - id: affected_e2e - run: | - set -euo pipefail - go run ./scripts/affected-packages.go -mode=suites -base "${{ steps.pr-info.outputs.BASE_REF }}" > /tmp/affected-e2e.txt - awk -F: '$1=="preflight"{print $2}' /tmp/affected-e2e.txt > /tmp/preflight-tests.txt - awk -F: '$1=="support-bundle"{print $2}' /tmp/affected-e2e.txt > /tmp/support-tests.txt - if [ -s /tmp/preflight-tests.txt ] || [ -s /tmp/support-tests.txt ]; then - echo "has_changes=true" >> "$GITHUB_OUTPUT" - else - echo "has_changes=false" >> "$GITHUB_OUTPUT" - fi + - name: Download affected e2e artifacts + uses: actions/download-artifact@v4 + with: + name: affected-e2e + path: /tmp - name: Run unit tests (filtered) - if: matrix.suite == 'unit' && steps.affected.outputs.has_changes == 'true' + if: matrix.suite == 'unit' && needs.test-affected.outputs.unit_has_changes == 'true' run: | set -euo pipefail if grep -qx "./..." /tmp/affected.txt; then @@ -158,7 +163,7 @@ jobs: - name: Run e2e (filtered) - ${{ matrix.suite }} - if: matrix.suite != 'unit' && steps.affected_e2e.outputs.has_changes == 'true' + if: matrix.suite != 'unit' && needs.test-affected.outputs.e2e_has_changes == 'true' run: | set -euo pipefail docker rm -f kind-cluster-control-plane 2>/dev/null || true From 2a2b0e0f301465e9a9e2661e54e397e7720d60d3 Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Tue, 14 Oct 2025 11:30:05 -0500 Subject: [PATCH 30/30] reverted regression tests to how they were before This was turning into something worthy of its own PR so I put it back to how it was before to work on next/separately --- .github/workflows/regression-test.yaml | 363 ++++++++++++++++--------- 1 file changed, 229 insertions(+), 134 deletions(-) diff --git a/.github/workflows/regression-test.yaml b/.github/workflows/regression-test.yaml index 893559e8e..991c803dc 100644 --- a/.github/workflows/regression-test.yaml +++ b/.github/workflows/regression-test.yaml @@ -4,7 +4,7 @@ on: push: branches: [main] pull_request: - types: [opened, reopened, synchronize, ready_for_review] + types: [opened, synchronize, reopened] workflow_dispatch: inputs: update_baselines: @@ -13,16 +13,22 @@ on: default: false jobs: - create-cluster: - # Create a shared cluster for the matrix; skip on fork PRs due to secrets - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false + regression-test: runs-on: ubuntu-22.04 - outputs: - kubeconfig: ${{ steps.create.outputs.cluster-kubeconfig }} - cluster-id: ${{ steps.create.outputs.cluster-id }} + timeout-minutes: 25 + steps: + # 1. SETUP + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history for git describe to work + + - name: Create output directory + run: mkdir -p test/output + - name: Create k3s cluster - id: create + id: create-cluster uses: replicatedhq/compatibility-actions/create-cluster@v1 with: api-token: ${{ secrets.REPLICATED_API_TOKEN }} @@ -31,21 +37,13 @@ jobs: ttl: 25m timeout-minutes: 5 - regression: - # Run on push, manual dispatch, and internal PRs (skip fork PRs) - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false - needs: create-cluster - runs-on: ubuntu-22.04 - timeout-minutes: 25 - strategy: - fail-fast: false - matrix: - suite: [preflight-v1beta3, preflight-v1beta2, support-bundle] - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 + - name: Configure kubeconfig + run: | + echo "${{ steps.create-cluster.outputs.cluster-kubeconfig }}" > $GITHUB_WORKSPACE/kubeconfig.yaml + echo "KUBECONFIG=$GITHUB_WORKSPACE/kubeconfig.yaml" >> $GITHUB_ENV + + - name: Verify cluster access + run: kubectl get nodes -o wide - name: Setup Go uses: actions/setup-go@v5 @@ -54,6 +52,13 @@ jobs: cache: true cache-dependency-path: go.sum + - name: Build binaries + run: | + echo "Building preflight and support-bundle binaries..." + make bin/preflight bin/support-bundle + ./bin/preflight version + ./bin/support-bundle version + - name: Setup Python for comparison uses: actions/setup-python@v5 with: @@ -63,135 +68,225 @@ jobs: run: | pip install pyyaml deepdiff - - name: Configure kubeconfig + # 2. EXECUTE SPECS (in parallel) + - name: Run all specs in parallel + continue-on-error: true run: | - echo "${{ needs.create-cluster.outputs.kubeconfig }}" > $GITHUB_WORKSPACE/kubeconfig.yaml - echo "KUBECONFIG=$GITHUB_WORKSPACE/kubeconfig.yaml" >> $GITHUB_ENV + echo "Running all 3 specs in parallel..." - - name: Verify cluster access - run: kubectl get nodes -o wide + # Run v1beta3 in background + ( + echo "Starting preflight v1beta3..." + ./bin/preflight \ + examples/preflight/complex-v1beta3.yaml \ + --values examples/preflight/values-complex-full.yaml \ + --interactive=false \ + --format=json \ + --output=test/output/preflight-results-v1beta3.json 2>&1 | tee test/output/v1beta3.log || true + + BUNDLE=$(ls -t preflightbundle-*.tar.gz 2>/dev/null | head -1) + if [ -n "$BUNDLE" ]; then + mv "$BUNDLE" test/output/preflight-v1beta3-bundle.tar.gz + echo "✓ v1beta3 bundle saved" + fi + ) & + PID_V1BETA3=$! + + # Run v1beta2 in background + ( + echo "Starting preflight v1beta2..." + ./bin/preflight \ + examples/preflight/all-analyzers-v1beta2.yaml \ + --interactive=false \ + --format=json \ + --output=test/output/preflight-results-v1beta2.json 2>&1 | tee test/output/v1beta2.log || true + + BUNDLE=$(ls -t preflightbundle-*.tar.gz 2>/dev/null | head -1) + if [ -n "$BUNDLE" ]; then + mv "$BUNDLE" test/output/preflight-v1beta2-bundle.tar.gz + echo "✓ v1beta2 bundle saved" + fi + ) & + PID_V1BETA2=$! + + # Run support bundle in background + ( + echo "Starting support bundle..." + ./bin/support-bundle \ + examples/collect/host/all-kubernetes-collectors.yaml \ + --interactive=false \ + --output=test/output/supportbundle.tar.gz 2>&1 | tee test/output/supportbundle.log || true + + if [ -f test/output/supportbundle.tar.gz ]; then + echo "✓ Support bundle saved" + fi + ) & + PID_SUPPORTBUNDLE=$! - - name: Prepare namespace (preflight only) - if: startsWith(matrix.suite, 'preflight-') + # Wait for all to complete + echo "Waiting for all specs to complete..." + wait $PID_V1BETA3 + wait $PID_V1BETA2 + wait $PID_SUPPORTBUNDLE + + echo "All specs completed!" + + # Verify bundles exist + ls -lh test/output/*.tar.gz || echo "Warning: Some bundles may be missing" + + # 3. COMPARE BUNDLES + - name: Compare preflight v1beta3 bundle + id: compare-v1beta3 + continue-on-error: true run: | - ns="ts-${{ matrix.suite }}-${{ github.run_id }}-${{ github.run_attempt }}" - echo "E2E_NS=$ns" >> $GITHUB_ENV - kubectl create namespace "$ns" + echo "Comparing v1beta3 preflight bundle against baseline..." + if [ ! -f test/baselines/preflight-v1beta3/baseline.tar.gz ]; then + echo "⚠ No baseline found for v1beta3 - skipping comparison" + echo "baseline_missing=true" >> $GITHUB_OUTPUT + exit 0 + fi - - name: Build binary for suite + python3 scripts/compare_bundles.py \ + --baseline test/baselines/preflight-v1beta3/baseline.tar.gz \ + --current test/output/preflight-v1beta3-bundle.tar.gz \ + --rules scripts/compare_rules.yaml \ + --report test/output/diff-report-v1beta3.json \ + --spec-type preflight + + - name: Compare preflight v1beta2 bundle + id: compare-v1beta2 + continue-on-error: true run: | - case "${{ matrix.suite }}" in - preflight-*) - make bin/preflight - ./bin/preflight version - ;; - support-bundle) - make bin/support-bundle - ./bin/support-bundle version - ;; - esac - - - name: Run suite + echo "Comparing v1beta2 preflight bundle against baseline..." + if [ ! -f test/baselines/preflight-v1beta2/baseline.tar.gz ]; then + echo "⚠ No baseline found for v1beta2 - skipping comparison" + echo "baseline_missing=true" >> $GITHUB_OUTPUT + exit 0 + fi + + python3 scripts/compare_bundles.py \ + --baseline test/baselines/preflight-v1beta2/baseline.tar.gz \ + --current test/output/preflight-v1beta2-bundle.tar.gz \ + --rules scripts/compare_rules.yaml \ + --report test/output/diff-report-v1beta2.json \ + --spec-type preflight + + - name: Compare support bundle + id: compare-supportbundle + continue-on-error: true run: | - mkdir -p test/output - case "${{ matrix.suite }}" in - preflight-v1beta3) - ./bin/preflight \ - examples/preflight/complex-v1beta3.yaml \ - --values examples/preflight/values-complex-full.yaml \ - --interactive=false \ - --format=json \ - --auto-update=false \ - --kubeconfig=$GITHUB_WORKSPACE/kubeconfig.yaml \ - -n "$E2E_NS" \ - --output=test/output/preflight-results-v1beta3.json - BUNDLE=$(ls -t preflightbundle-*.tar.gz 2>/dev/null | head -1) - if [ -n "$BUNDLE" ]; then - mv "$BUNDLE" test/output/preflight-v1beta3-bundle.tar.gz - fi - ;; - preflight-v1beta2) - ./bin/preflight \ - examples/preflight/all-analyzers-v1beta2.yaml \ - --interactive=false \ - --format=json \ - --auto-update=false \ - --kubeconfig=$GITHUB_WORKSPACE/kubeconfig.yaml \ - -n "$E2E_NS" \ - --output=test/output/preflight-results-v1beta2.json - BUNDLE=$(ls -t preflightbundle-*.tar.gz 2>/dev/null | head -1) - if [ -n "$BUNDLE" ]; then - mv "$BUNDLE" test/output/preflight-v1beta2-bundle.tar.gz - fi - ;; - support-bundle) - ./bin/support-bundle \ - examples/collect/host/all-kubernetes-collectors.yaml \ - --interactive=false \ - --kubeconfig=$GITHUB_WORKSPACE/kubeconfig.yaml \ - --output=test/output/supportbundle.tar.gz - ;; - esac - - - name: Compare against baselines + echo "Comparing support bundle against baseline..." + if [ ! -f test/baselines/supportbundle/baseline.tar.gz ]; then + echo "⚠ No baseline found for support bundle - skipping comparison" + echo "baseline_missing=true" >> $GITHUB_OUTPUT + exit 0 + fi + + python3 scripts/compare_bundles.py \ + --baseline test/baselines/supportbundle/baseline.tar.gz \ + --current test/output/supportbundle.tar.gz \ + --rules scripts/compare_rules.yaml \ + --report test/output/diff-report-supportbundle.json \ + --spec-type supportbundle + + # 4. REPORT RESULTS + - name: Generate summary report + if: always() run: | - case "${{ matrix.suite }}" in - preflight-v1beta3) - if [ -f test/baselines/preflight-v1beta3/baseline.tar.gz ]; then - python3 scripts/compare_bundles.py \ - --baseline test/baselines/preflight-v1beta3/baseline.tar.gz \ - --current test/output/preflight-v1beta3-bundle.tar.gz \ - --rules scripts/compare_rules.yaml \ - --report test/output/diff-report-v1beta3.json \ - --spec-type preflight - else - echo "No baseline for v1beta3; skipping comparison" - fi - ;; - preflight-v1beta2) - if [ -f test/baselines/preflight-v1beta2/baseline.tar.gz ]; then - python3 scripts/compare_bundles.py \ - --baseline test/baselines/preflight-v1beta2/baseline.tar.gz \ - --current test/output/preflight-v1beta2-bundle.tar.gz \ - --rules scripts/compare_rules.yaml \ - --report test/output/diff-report-v1beta2.json \ - --spec-type preflight - else - echo "No baseline for v1beta2; skipping comparison" - fi - ;; - support-bundle) - if [ -f test/baselines/supportbundle/baseline.tar.gz ]; then - python3 scripts/compare_bundles.py \ - --baseline test/baselines/supportbundle/baseline.tar.gz \ - --current test/output/supportbundle.tar.gz \ - --rules scripts/compare_rules.yaml \ - --report test/output/diff-report-supportbundle.json \ - --spec-type supportbundle - else - echo "No baseline for support bundle; skipping comparison" - fi - ;; - esac - - - name: Upload artifacts + python3 scripts/generate_summary.py \ + --reports test/output/diff-report-*.json \ + --output-file $GITHUB_STEP_SUMMARY \ + --output-console + + - name: Upload test artifacts if: always() uses: actions/upload-artifact@v4 with: - name: regression-${{ matrix.suite }}-${{ github.run_id }}-${{ github.run_attempt }} + name: regression-test-results-${{ github.run_id }}-${{ github.run_attempt }} path: | test/output/*.tar.gz test/output/*.json retention-days: 30 - cleanup: - if: always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) - needs: [create-cluster, regression] - runs-on: ubuntu-22.04 - steps: + - name: Check for regressions + if: always() + run: | + echo "Checking comparison results..." + + # Check if any comparisons failed + FAILURES=0 + + if [ "${{ steps.compare-v1beta3.outcome }}" == "failure" ] && [ "${{ steps.compare-v1beta3.outputs.baseline_missing }}" != "true" ]; then + echo "❌ v1beta3 comparison failed" + FAILURES=$((FAILURES + 1)) + fi + + if [ "${{ steps.compare-v1beta2.outcome }}" == "failure" ] && [ "${{ steps.compare-v1beta2.outputs.baseline_missing }}" != "true" ]; then + echo "❌ v1beta2 comparison failed" + FAILURES=$((FAILURES + 1)) + fi + + if [ "${{ steps.compare-supportbundle.outcome }}" == "failure" ] && [ "${{ steps.compare-supportbundle.outputs.baseline_missing }}" != "true" ]; then + echo "❌ Support bundle comparison failed" + FAILURES=$((FAILURES + 1)) + fi + + if [ $FAILURES -gt 0 ]; then + echo "" + echo "❌ $FAILURES regression(s) detected!" + echo "Review the comparison reports in the artifacts." + exit 1 + else + echo "✅ All comparisons passed or skipped (no baseline)" + fi + + # 5. UPDATE BASELINES (optional, manual trigger only) + - name: Update baselines + if: github.event.inputs.update_baselines == 'true' && github.event_name == 'workflow_dispatch' + run: | + echo "Updating baselines with current bundles..." + + # Copy new bundles as baselines + if [ -f test/output/preflight-v1beta3-bundle.tar.gz ]; then + mkdir -p test/baselines/preflight-v1beta3 + cp test/output/preflight-v1beta3-bundle.tar.gz test/baselines/preflight-v1beta3/baseline.tar.gz + echo "✓ Updated v1beta3 baseline" + fi + + if [ -f test/output/preflight-v1beta2-bundle.tar.gz ]; then + mkdir -p test/baselines/preflight-v1beta2 + cp test/output/preflight-v1beta2-bundle.tar.gz test/baselines/preflight-v1beta2/baseline.tar.gz + echo "✓ Updated v1beta2 baseline" + fi + + if [ -f test/output/supportbundle.tar.gz ]; then + mkdir -p test/baselines/supportbundle + cp test/output/supportbundle.tar.gz test/baselines/supportbundle/baseline.tar.gz + echo "✓ Updated support bundle baseline" + fi + + # Create metadata file + cat > test/baselines/metadata.json <