package scripts_test import ( "errors" "os" "os/exec" "path/filepath" "runtime" "strings" "testing" ) type mutationRun struct { console string summary string } func runMutationTotalCheck(t *testing.T, reports map[string]string) (mutationRun, error) { t.Helper() if runtime.GOOS == "windows" { t.Skip("the mutation total checker runs in the Linux CI job") } dir := t.TempDir() for name, report := range reports { path := filepath.Join(dir, name) if err := os.WriteFile(path, []byte(report), 0o600); err != nil { t.Fatalf("write report: %v", err) } } summary := filepath.Join(t.TempDir(), "summary.md") console, err := runMutationTotalCheckWithSummary(dir, summary) written, readErr := os.ReadFile(summary) if readErr != nil && !errors.Is(readErr, os.ErrNotExist) { t.Fatalf("read summary: %v", readErr) } return mutationRun{console: console, summary: string(written)}, err } func runMutationTotalCheckWithSummary(dir, summary string) (string, error) { cmd := exec.Command("bash", "check-mutation-total.sh", dir) cmd.Env = append(os.Environ(), "GITHUB_STEP_SUMMARY="+summary) out, err := cmd.CombinedOutput() return string(out), err } func mutationTotalReports() map[string]string { return map[string]string{ "root-default-root.json": `{"mutants_killed":30,"mutants_lived":0,"mutants_not_covered":10,"files":[]}`, "root-desktop-root.json": `{"mutants_killed":20,"mutants_lived":0,"mutants_not_covered":8,"files":[]}`, "internal-a-d-auth.json": `{"mutants_killed":10,"mutants_lived":0,"mutants_not_covered":5,` + `"files":[{"file_name":"auth.go","mutations":[` + `{"status":"NOT COVERED","line":3,"type":"CONDITIONALS_NEGATION"},` + `{"status":"NOT COVERED","line":4,"type":"CONDITIONALS_BOUNDARY"}]}]}`, } } func TestMutationTotalCheckSeparatesTheTwoBuilds(t *testing.T) { run, err := runMutationTotalCheck(t, mutationTotalReports()) if err != nil { t.Fatalf("check mutation totals: %v", err) } if !strings.Contains(run.console, "60 killed, 0 survived; uncovered 15 default, 13 desktop") { t.Fatalf("console = %q, want the per-build totals", run.console) } } func TestMutationTotalCheckNamesTheFilesNoTestReaches(t *testing.T) { run, err := runMutationTotalCheck(t, mutationTotalReports()) if err != nil { t.Fatalf("check mutation totals: %v", err) } if !strings.Contains(run.summary, "## Mutation testing") { t.Fatalf("summary = %q, want the mutation heading", run.summary) } if !strings.Contains(run.summary, "| 2 | internal-a-d-auth | auth.go |") { t.Fatalf("summary = %q, want the uncovered file listed", run.summary) } } func TestMutationTotalCheckWarnsAboutASurvivingMutantWithoutFailing(t *testing.T) { reports := mutationTotalReports() reports["internal-a-d-auth.json"] = `{"mutants_killed":10,"mutants_lived":1,"mutants_not_covered":0,` + `"files":[{"file_name":"auth.go","mutations":[{"status":"LIVED","line":42,"type":"REMOVE_SELF_ASSIGNMENTS"}]}]}` run, err := runMutationTotalCheck(t, reports) if err != nil { t.Fatalf("check surviving mutant: %v", err) } if !strings.Contains(run.console, "::warning title=Mutant survived::REMOVE_SELF_ASSIGNMENTS changed auth.go:42") { t.Fatalf("console = %q, want a warning annotation", run.console) } if !strings.Contains(run.summary, "| internal-a-d-auth | auth.go | 42 | REMOVE_SELF_ASSIGNMENTS |") { t.Fatalf("summary = %q, want the survivor in the summary", run.summary) } } func TestMutationTotalCheckPrintsTheSummaryWhenActionsIsNotListening(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("the mutation total checker runs in the Linux CI job") } dir := t.TempDir() for name, report := range mutationTotalReports() { if err := os.WriteFile(filepath.Join(dir, name), []byte(report), 0o600); err != nil { t.Fatalf("write report: %v", err) } } console, err := runMutationTotalCheckWithSummary(dir, "") if err != nil { t.Fatalf("check mutation totals: %v", err) } if !strings.Contains(console, "## Mutation testing") { t.Fatalf("console = %q, want the summary on stdout without a step summary to write to", console) } } func TestMutationTotalCheckRejectsAMissingDesktopRootReport(t *testing.T) { reports := mutationTotalReports() delete(reports, "root-desktop-root.json") _, err := runMutationTotalCheck(t, reports) if code := exitCode(t, err); code != 11 { t.Fatalf("exit code = %d, want 11", code) } } func TestMutationTotalCheckRejectsAnEmptyReportDirectory(t *testing.T) { _, err := runMutationTotalCheck(t, map[string]string{}) if code := exitCode(t, err); code != 11 { t.Fatalf("exit code = %d, want 11", code) } } func TestMutationTotalCheckRejectsAReportItCannotRead(t *testing.T) { reports := mutationTotalReports() reports["internal-a-d-auth.json"] = "not json" _, err := runMutationTotalCheck(t, reports) if code := exitCode(t, err); code != 11 { t.Fatalf("exit code = %d, want 11", code) } }