Each run step goes through mesh-sdk's buildstep.Isolated: no network, none of the account, the workspace read-write, the Go caches beneath a throwaway layer unless the build step is told publish (a trunk build). The integration level runs its store, its bus and its tests in one shut-off process (this program again, level <spec>), so they reach each other and nothing else. build_go_test takes a -run pattern (a try's tests). The module declares the bubblewrap package and refuses a step, unhealthy, while bwrap is missing or older than 0.10. The proof test runs a probe test through the real unit level.
244 lines
9.2 KiB
Go
244 lines
9.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
|
)
|
|
|
|
// capturedRunner answers the commands of a test step from a fixture repository: ls-files with the go.mod, and every
|
|
// `go test` with the output captured from the real go (testdata/captured, see its README).
|
|
func capturedRunner(t *testing.T, captured string, calls *[]cmdRecord) forgekey.Runner {
|
|
t.Helper()
|
|
raw, err := os.ReadFile(filepath.Join("testdata", "captured", captured+".json"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
status := 0
|
|
if strings.Contains(string(raw), `"Action":"fail"`) {
|
|
status = 1 // go test exits 1 when a test or a build failed
|
|
}
|
|
return func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
|
|
*calls = append(*calls, cmdRecord{env: env, line: name + " " + strings.Join(args, " ")})
|
|
switch {
|
|
case name == "git" && args[2] == "ls-files":
|
|
return forgekey.Ran{Stdout: "go.mod\n"}
|
|
case name == "go":
|
|
return forgekey.Ran{Stdout: string(raw), Status: status}
|
|
}
|
|
return forgekey.Ran{}
|
|
}
|
|
}
|
|
|
|
// workspaceFor is a module whose work directory holds a fetched workspace, and the id of it.
|
|
func workspaceFor(t *testing.T, captured string) (*Module, *keptStates, string, *[]cmdRecord) {
|
|
t.Helper()
|
|
calls := &[]cmdRecord{}
|
|
states := newStates()
|
|
m := moduleWith(states, "go1.27.1")
|
|
m.Home, m.Work, m.Cache = t.TempDir(), t.TempDir(), t.TempDir()
|
|
m.Run = throughBubblewrap(t, capturedRunner(t, captured, calls), &[]shutOff{}) // the step's go runs shut off (1309)
|
|
if err := os.MkdirAll(filepath.Join(m.Work, "ws1", "tree"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return m, states, "ws1", calls
|
|
}
|
|
|
|
func runUnit(t *testing.T, m *Module, states *keptStates, ws string) (TestResult, delivery.Step) {
|
|
t.Helper()
|
|
var run func(map[string]any) (any, error)
|
|
for _, tool := range m.Tools(m.StepTools()) {
|
|
if tool.Name == "build_go_test" {
|
|
run = tool.Run
|
|
}
|
|
}
|
|
if _, err := run(map[string]any{"workspace": ws, "level": "unit"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
st := states.waitEnd(t)
|
|
var res TestResult
|
|
if st.State == delivery.StepPassed {
|
|
if err := json.Unmarshal(st.Result, &res); err != nil {
|
|
t.Fatalf("result %s: %v", st.Result, err)
|
|
}
|
|
}
|
|
return res, st
|
|
}
|
|
|
|
// 1290/AC1: a fixture with one passing package and one failing test: the result names the failing test and its
|
|
// package, and the level fails. The step itself finished, so it ends passed with the verdict in its result.
|
|
func TestUnitLevelNamesTheFailingTestAndFailsTheLevel(t *testing.T) {
|
|
m, states, ws, _ := workspaceFor(t, "failing")
|
|
res, st := runUnit(t, m, states, ws)
|
|
if st.State != delivery.StepPassed {
|
|
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
|
|
}
|
|
if res.Passed || res.Level != "unit" {
|
|
t.Errorf("result = %+v, want the unit level failed", res)
|
|
}
|
|
if len(res.Failed) != 1 || res.Failed[0].Package != "fixture/failing/bad" || res.Failed[0].Test != "TestThreeIsFour" ||
|
|
!strings.Contains(res.Failed[0].Output, "Three() = 3, want 4") {
|
|
t.Errorf("failed = %+v", res.Failed)
|
|
}
|
|
if !strings.Contains(res.Why, "fixture/failing/bad TestThreeIsFour") {
|
|
t.Errorf("why = %q", res.Why)
|
|
}
|
|
good := false
|
|
for _, p := range res.Packages {
|
|
if p.Package == "fixture/failing/good" && p.Passed && p.Tests == 1 {
|
|
good = true
|
|
}
|
|
}
|
|
if !good || res.Tests != 2 {
|
|
t.Errorf("packages = %+v, tests = %d; want the good package passed with its test, 2 tests", res.Packages, res.Tests)
|
|
}
|
|
}
|
|
|
|
// 1290/AC2: a fixture whose tests take more than the bound (lowered by the module's option, not by waiting five
|
|
// seconds): the level fails naming the bound and the slowest tests; under the real bound the same run passes.
|
|
func TestUnitLevelOverItsBoundFailsNamingTheBoundAndTheSlowestTests(t *testing.T) {
|
|
m, states, ws, _ := workspaceFor(t, "slow")
|
|
m.UnitBound = 10 * time.Millisecond
|
|
res, _ := runUnit(t, m, states, ws)
|
|
if !res.Passed || !res.OverBound || res.OverWhich != "run" {
|
|
t.Fatalf("a level of %.3f s under a bound of %v: passed %v, over_bound %v (%s); the tests passed and the bound was kept no longer", res.Elapsed, m.UnitBound, res.Passed, res.OverBound, res.OverWhich)
|
|
}
|
|
for _, want := range []string{"over its bound of 0.01 s", "fixture/slow TestTakesAWhile"} {
|
|
if !strings.Contains(res.Why, want) {
|
|
t.Errorf("why = %q lacks %q", res.Why, want)
|
|
}
|
|
}
|
|
if len(res.Slowest) == 0 || res.Slowest[0].Test != "TestTakesAWhile" || res.Bound != 0.01 {
|
|
t.Errorf("slowest = %+v, bound = %v", res.Slowest, res.Bound)
|
|
}
|
|
|
|
m2, states2, ws2, _ := workspaceFor(t, "slow")
|
|
if res, _ := runUnit(t, m2, states2, ws2); !res.Passed || res.OverBound || res.Bound != 5 {
|
|
t.Errorf("under the bound of five seconds: %+v", res)
|
|
}
|
|
}
|
|
|
|
// 1290/AC3: a fixture whose only test carries //go:build integration: go test says [no test files], and the unit
|
|
// level reports no tests and passes. No command of the unit level asks for the tag.
|
|
func TestUnitLevelOfAFixtureWithOnlyAnIntegrationTestHasNoTests(t *testing.T) {
|
|
m, states, ws, calls := workspaceFor(t, "tagged")
|
|
res, _ := runUnit(t, m, states, ws)
|
|
if !res.Passed || !res.NoTests || res.Tests != 0 || !strings.Contains(res.Why, "no tests") {
|
|
t.Errorf("result = %+v, want no tests, passed", res)
|
|
}
|
|
for _, c := range *calls {
|
|
if strings.Contains(c.line, "-tags") {
|
|
t.Errorf("the unit level asked for a tag: %s", c.line)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1290/AC4: a fixture with no tests at all: the result says the repository has no tests, neither a failure nor a warning.
|
|
func TestUnitLevelOfAFixtureWithNoTestsSaysSo(t *testing.T) {
|
|
m, states, ws, _ := workspaceFor(t, "empty")
|
|
res, st := runUnit(t, m, states, ws)
|
|
if st.State != delivery.StepPassed || !res.Passed || !res.NoTests || res.Why != "the repository has no tests at the unit level" || len(res.Failed) != 0 {
|
|
t.Errorf("result = %+v (%s)", res, st.State)
|
|
}
|
|
}
|
|
|
|
// A package that does not build fails the level with the compiler's words.
|
|
func TestUnitLevelOfAPackageThatDoesNotBuildFailsWithTheCompilersLine(t *testing.T) {
|
|
m, states, ws, _ := workspaceFor(t, "broken")
|
|
res, _ := runUnit(t, m, states, ws)
|
|
if res.Passed || len(res.Failed) != 1 || res.Failed[0].Package != "fixture/broken" ||
|
|
!strings.Contains(res.Failed[0].Output, `cannot use "one" (untyped string constant) as int value`) {
|
|
t.Errorf("result = %+v", res)
|
|
}
|
|
}
|
|
|
|
// The command is go test -json in the module's directory of the workspace, with the toolchain pinned to the installed
|
|
// one and the module's caches.
|
|
func TestUnitLevelRunsGoTestJsonInTheModulesDirectoryWithTheStepsEnvironment(t *testing.T) {
|
|
m, states, ws, calls := workspaceFor(t, "empty")
|
|
runUnit(t, m, states, ws)
|
|
var line string
|
|
var env string
|
|
for _, c := range *calls {
|
|
if strings.HasPrefix(c.line, "go ") {
|
|
line, env = c.line, strings.Join(c.env, " ")
|
|
}
|
|
}
|
|
want := "go -C " + filepath.Join(m.Work, ws, "tree", ".") + " test -json -count=1 -timeout 10m ./..."
|
|
if line != want {
|
|
t.Errorf("command = %q, want %q", line, want)
|
|
}
|
|
for _, w := range []string{"GOTOOLCHAIN=local", "GOFLAGS=-mod=mod", "GOMODCACHE=" + filepath.Join(m.Cache, "gomod")} {
|
|
if !strings.Contains(env, w) {
|
|
t.Errorf("environment %s lacks %s", env, w)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A go test that said nothing a test says (it could not run) ends the step failed, not the level.
|
|
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" {
|
|
return forgekey.Ran{Status: 1, Stderr: "go: cannot find main module\n"}
|
|
}
|
|
return forgekey.Ran{Stdout: "go.mod\n"}
|
|
}, &[]shutOff{})
|
|
_, st := runUnit(t, m, states, ws)
|
|
if st.State != delivery.StepFailed || !strings.Contains(st.Reason, "go: cannot find main module") {
|
|
t.Errorf("the step ended %s: %q", st.State, st.Reason)
|
|
}
|
|
}
|
|
|
|
func TestATestOfAnUnknownWorkspaceOrAnotherLevelIsRefused(t *testing.T) {
|
|
m, states, _, _ := workspaceFor(t, "empty")
|
|
var run func(map[string]any) (any, error)
|
|
for _, tool := range m.Tools(m.StepTools()) {
|
|
if tool.Name == "build_go_test" {
|
|
run = tool.Run
|
|
}
|
|
}
|
|
for _, args := range []map[string]any{
|
|
{"workspace": "nope", "level": "unit"},
|
|
{"workspace": "../ws1", "level": "unit"},
|
|
{"workspace": "ws1", "level": "stress"},
|
|
{"workspace": "ws1", "level": "unit", "packages": []any{"-run=x"}},
|
|
{"workspace": "ws1", "level": "unit", "packages": []any{"../x"}},
|
|
} {
|
|
run(args)
|
|
if st := states.waitEnd(t); st.State != delivery.StepFailed {
|
|
t.Errorf("%v: the step ended %s", args, st.State)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A directory that vendors is tested, and listed, from its vendor directory: fetch downloaded nothing for it, and the
|
|
// last value of GOFLAGS is the one go uses.
|
|
func TestADirectoryThatVendorsIsReadFromItsVendorDirectory(t *testing.T) {
|
|
m, states, ws, calls := workspaceFor(t, "empty")
|
|
vendor := filepath.Join(m.Work, ws, "tree", "vendor")
|
|
os.MkdirAll(vendor, 0o755)
|
|
os.WriteFile(filepath.Join(vendor, "modules.txt"), nil, 0o644)
|
|
runUnit(t, m, states, ws)
|
|
flags := ""
|
|
for _, c := range *calls {
|
|
if strings.HasPrefix(c.line, "go ") {
|
|
for _, w := range c.env {
|
|
if strings.HasPrefix(w, "GOFLAGS=") {
|
|
flags = w
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if flags != "GOFLAGS=-mod=vendor" {
|
|
t.Errorf("the last GOFLAGS of go test in a vendoring directory is %q", flags)
|
|
}
|
|
}
|