diff --git a/modules/mesh-build-go/cmd/mesh-build-go/levels.go b/modules/mesh-build-go/cmd/mesh-build-go/levels.go index 455ca95f..6af18e3f 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/levels.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/levels.go @@ -70,6 +70,8 @@ type TestResult struct { Elapsed float64 `json:"elapsed"` // Setup is the level's setup time in seconds, when it has a setup. Setup float64 `json:"setup,omitempty"` + // Lint is the unit level's gofmt and go vet time in seconds, before its tests (ADR 0529 §1). + Lint float64 `json:"lint,omitempty"` // Bound and SetupBound are the bounds the level was judged by, in seconds. Bound float64 `json:"bound"` SetupBound float64 `json:"setup_bound,omitempty"` diff --git a/modules/mesh-build-go/cmd/mesh-build-go/lint_test.go b/modules/mesh-build-go/cmd/mesh-build-go/lint_test.go index abf91574..602d68b7 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/lint_test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/lint_test.go @@ -72,7 +72,7 @@ func TestTheUnitLevelRunsGofmtAndVetWithEveryTag(t *testing.T) { // 1887/D7-go: through the step, the unit level of a tree holding an unformatted file fails, naming the file. func TestAnUnformattedFileFailsTheUnitLevel(t *testing.T) { - m, states, ws, _ := workspaceFor(t, "empty") + m, states, ws, calls := workspaceFor(t, "empty") tree := filepath.Join(m.Work, ws, "tree") for name, body := range map[string]string{"go.mod": "module x\n", "x.go": "package x\nfunc X() {}\n"} { if err := os.WriteFile(filepath.Join(tree, name), []byte(body), 0o644); err != nil { @@ -83,4 +83,10 @@ func TestAnUnformattedFileFailsTheUnitLevel(t *testing.T) { if res.Passed || len(res.Failed) != 1 || res.Failed[0].Test != "gofmt" || !strings.Contains(res.Failed[0].Output, "x.go") { t.Fatalf("result = %+v, want the level failed naming x.go", res) } + // Review round 2 item 5: before the level's tests, which do not run once gofmt failed. + for _, c := range *calls { + if strings.Contains(c.line, " test -json") { + t.Errorf("the tests ran after gofmt failed: %s", c.line) + } + } } diff --git a/modules/mesh-build-go/cmd/mesh-build-go/test.go b/modules/mesh-build-go/cmd/mesh-build-go/test.go index 02a8a01d..d3b42c9e 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/test.go @@ -197,6 +197,26 @@ func (m *Module) runLevel(ctx context.Context, tree string, o levelOpts) (*TestR todo = append(todo, d) } } + if o.level == LevelUnit { + // gofmt and go vet of each directory the level runs come before its tests (ADR 0529 §1; issue 1887 D7-go): a + // directory that fails them fails the level at once, and their time is the result's Lint. + linted := m.clock() + var failed []Failure + for _, d := range todo { + f, err := m.lint(ctx, run, tree, d, append(m.goEnvFor(filepath.Join(tree, d)), o.env...)) + if err != nil { + return nil, err + } + failed = append(failed, f...) + } + res.Lint = m.clock().Sub(linted).Seconds() + if len(failed) > 0 { + res.Failed = failed + res.judge(o.bounds) + return res, nil + } + began = m.clock() // the level's running time is its tests' + } runs := make([]dirRun, len(todo)) slots := make(chan struct{}, m.parallel()) var wg sync.WaitGroup @@ -253,16 +273,6 @@ func (m *Module) runLevel(ctx context.Context, tree string, o levelOpts) (*TestR if o.wall { res.Elapsed = m.clock().Sub(began).Seconds() } - if o.level == LevelUnit { - // gofmt and go vet of each directory the level ran are the unit level's (ADR 0529 §1; issue 1887 D7-go). - for _, d := range todo { - failed, err := m.lint(ctx, run, tree, d, append(m.goEnvFor(filepath.Join(tree, d)), o.env...)) - if err != nil { - return nil, err - } - res.Failed = append(res.Failed, failed...) - } - } res.judge(o.bounds) return res, nil } diff --git a/modules/mesh-build-go/cmd/mesh-build-go/test_real_integration_test.go b/modules/mesh-build-go/cmd/mesh-build-go/test_real_integration_test.go index 26c7a75f..78a5a8f6 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/test_real_integration_test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/test_real_integration_test.go @@ -66,7 +66,7 @@ func TestUnitLevelRunsTheRealGoOnTheFixtureRepositories(t *testing.T) { "tagged": {passed: true, noTests: true}, "empty": {passed: true, noTests: true}, "slow": {passed: true}, - "broken": {failed: "fixture/broken"}, + "broken": {failed: "."}, // go vet, before the tests since R4g (ADR 0529 §1), names the directory and the compiler's line } for name, w := range want { home := t.TempDir() @@ -90,6 +90,32 @@ func TestUnitLevelRunsTheRealGoOnTheFixtureRepositories(t *testing.T) { } } +// 1887/D7-go against the real go: a tree holding an unformatted file fails the unit level naming the file, before any +// test runs, and the level's result carries the lint's time. +func TestAnUnformattedFileFailsTheRealUnitLevel(t *testing.T) { + needTools(t, "the unit level's gofmt and vet", "go", "git", "bwrap") + home := t.TempDir() + m := &Module{Home: home, Work: t.TempDir(), Cache: t.TempDir()} + tree := filepath.Join(m.Work, "ws1", "tree") + if err := os.MkdirAll(filepath.Dir(tree), 0o755); err != nil { + t.Fatal(err) + } + if err := os.Rename(repository(t, "slow"), tree); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(tree, "ugly.go"), []byte("package slow\nfunc Ugly() {}\n"), 0o644); err != nil { + t.Fatal(err) + } + m.Run = StepRunner(home) + res, err := m.runLevel(context.Background(), tree, levelOpts{workspace: "ws1", level: LevelUnit, bounds: Bounds{Run: UnitBound}}) + if err != nil { + t.Fatal(err) + } + if res.Passed || len(res.Failed) == 0 || res.Failed[0].Test != "gofmt" || !strings.Contains(res.Failed[0].Output, "ugly.go") || res.Tests != 0 { + t.Fatalf("result = %+v, want the level failed by gofmt naming ugly.go and no test run", res) + } +} + // processesNamed are the processes whose command line holds text. func processesNamed(text string) []string { var out []string diff --git a/modules/mesh-build-go/cmd/mesh-build-go/test_test.go b/modules/mesh-build-go/cmd/mesh-build-go/test_test.go index 80bbe9b9..359ce3c4 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/test_test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/test_test.go @@ -188,6 +188,9 @@ func TestUnitLevelRunsGoTestJsonInTheModulesDirectoryWithTheStepsEnvironment(t * func TestAGoTestThatCouldNotRunEndsTheStepFailed(t *testing.T) { m, states, ws, _ := workspaceFor(t, "empty") m.Run = throughBubblewrap(t, func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran { + if name == "go" && len(args) > 2 && args[2] == "vet" { + return forgekey.Ran{} // vet, before the tests, passes: this test is of go test that could not run + } if name == "go" { return forgekey.Ran{Status: 1, Stderr: "go: cannot find main module\n"} }