diff --git a/cmd/internal-tools/confusion-matrix/main.go b/cmd/internal-tools/confusion-matrix/main.go index 471eb8e..b1e2c7e 100644 --- a/cmd/internal-tools/confusion-matrix/main.go +++ b/cmd/internal-tools/confusion-matrix/main.go @@ -54,16 +54,25 @@ func run(arguments []string, stdout, stderr io.Writer) error { if err := flagSet.Parse(arguments); err != nil { return err } - for name, value := range map[string]string{ - "--sweep": sweepDirectory, - "--reviews": reviewsDirectory, - "--assignment": assignmentPath, - "--property-clauses": mappingPath, + // Every missing flag is named together, in flag order: stopping at the + // first turns one rerun into one rerun per missing flag. + var missing []error + for _, required := range []struct { + name string + value string + }{ + {"--sweep", sweepDirectory}, + {"--reviews", reviewsDirectory}, + {"--assignment", assignmentPath}, + {"--property-clauses", mappingPath}, } { - if value == "" { - return fmt.Errorf("%s is required", name) + if required.value == "" { + missing = append(missing, fmt.Errorf("%s is required", required.name)) } } + if err := errors.Join(missing...); err != nil { + return err + } mapping, err := loadMapping(mappingPath) if err != nil { diff --git a/cmd/internal-tools/confusion-matrix/parse_test.go b/cmd/internal-tools/confusion-matrix/parse_test.go index acad7b6..bb8d905 100644 --- a/cmd/internal-tools/confusion-matrix/parse_test.go +++ b/cmd/internal-tools/confusion-matrix/parse_test.go @@ -31,6 +31,31 @@ func scoredFixture(name, model string) fixtureImplementation { } } +// Three flags missing is one rerun, not three: the operator is told about all +// of them at once, in flag order, whatever order the check happened to walk. +func TestRunNamesEveryMissingRequiredFlagInFlagOrder(t *testing.T) { + var stdout, stderr bytes.Buffer + err := run([]string{"--sweep", "s"}, &stdout, &stderr) + if err == nil { + t.Fatal("got no error, want every missing flag named") + } + message := err.Error() + previous := -1 + for _, name := range []string{"--reviews", "--assignment", "--property-clauses"} { + at := strings.Index(message, name) + if at < 0 { + t.Fatalf("got %q, want %s named", message, name) + } + if at < previous { + t.Errorf("got %q, want the flags named in flag order", message) + } + previous = at + } + if strings.Contains(message, "--sweep") { + t.Errorf("got %q, want the supplied --sweep left out", message) + } +} + func TestMappingRefusesInputTheMatrixCannotBeScoredFrom(t *testing.T) { tests := []struct { name string