package builder import ( "errors" "fmt" "os" "os/exec" "path/filepath" "strings" "testing" ) // manyPaths is a list shaped like the real one of mesh-delivery feat/delivery-parts 9e9c2a7 (novox/hq issue // 1247): 2494 vendored files of nats-server across four new modules, about 208 KB comma-joined. func manyPaths() []string { modules := []string{"mesh-delivery-forge-bridge", "mesh-delivery-indexer", "mesh-delivery-teller", "mesh-delivery-view"} var paths []string for i := 0; i < 2494; i++ { paths = append(paths, fmt.Sprintf("modules/%s/vendor/github.com/nats-io/nats-server/v2/server/file_%04d.go", modules[i%len(modules)], i)) } return paths } func envValue(env []string, name string) string { for _, e := range env { if v, ok := strings.CutPrefix(e, name+"="); ok { return v } } return "\x00unset" } func TestALongListOfChangedPathsGoesInAFileAndTheStringHoldsTheModules(t *testing.T) { // 1247/AC1 paths := manyPaths() if n := len(strings.Join(paths, ",")); n < 200<<10 { t.Fatalf("the fixture is only %d bytes: not the real list's shape", n) } root := t.TempDir() var said []string env, err := changedEnvironment(root, paths, func(l string) { said = append(said, l) }) if err != nil { t.Fatal(err) } for _, e := range env { if len(e) > 64<<10 { t.Errorf("an environment string of %d bytes is handed to docker: %.60s", len(e), e) } } raw, err := os.ReadFile(envValue(env, EnvChangedFile)) if err != nil { t.Fatal(err) } if got := strings.Split(strings.TrimSuffix(string(raw), "\n"), "\n"); strings.Join(got, "\n") != strings.Join(paths, "\n") { t.Errorf("the file holds %d lines, not the %d paths in the order given", len(got), len(paths)) } want := "modules/mesh-delivery-forge-bridge/,modules/mesh-delivery-indexer/,modules/mesh-delivery-teller/,modules/mesh-delivery-view/" if got := envValue(env, EnvChanged); got != want { t.Errorf("%s is %q, want each module's directory once: %q", EnvChanged, got, want) } if len(said) != 1 || !strings.Contains(said[0], EnvChangedFile) { t.Errorf("the check's output says %q, want one line naming %s", said, EnvChangedFile) } } func TestAShortListOfChangedPathsStaysAsItWasAndIsInTheFileToo(t *testing.T) { // 1247/AC2 var said []string env, err := changedEnvironment(t.TempDir(), []string{"a.go", "b.go"}, func(l string) { said = append(said, l) }) if err != nil { t.Fatal(err) } if got := envValue(env, EnvChanged); got != "a.go,b.go" { t.Errorf("%s is %q", EnvChanged, got) } raw, err := os.ReadFile(envValue(env, EnvChangedFile)) if err != nil || string(raw) != "a.go\nb.go\n" { t.Errorf("the file holds %q, %v", raw, err) } if len(said) != 0 { t.Errorf("a short list said %q", said) } } // theGatesCommand is the command gateLayer hands the toolchain for the merge gate, given the environment of the check. func theGatesCommand(t *testing.T, env []string) string { t.Helper() var args []string gateLayer(t.Context(), CheckSpec{}, t.TempDir(), t.TempDir(), "gate", filepath.Join(t.TempDir(), "verdict.json"), env, func(dir string, env []string, command ...string) []string { return command }, func(a []string) error { args = a; return errors.New("stop here") }, &tail{}, "bus", "name", false, "", nil, func(string, string, ...any) {}) return strings.Join(args, " ") } func TestTheGateIsGivenAShortListAsBeforeSoAnOlderJudgeStillRuns(t *testing.T) { // 1247/AC3 env, err := changedEnvironment(t.TempDir(), []string{"a.go", "b.go"}, func(string) {}) if err != nil { t.Fatal(err) } command := theGatesCommand(t, env) if !strings.Contains(command, `--changed "$MESH_CHECK_CHANGED"`) || strings.Contains(command, "--changed-file") { t.Errorf("a short list is not given to the gate as --changed alone: %s", command) } } func TestTheGateReadsACutListFromTheFile(t *testing.T) { // 1247/AC3 env, err := changedEnvironment(t.TempDir(), manyPaths(), func(string) {}) if err != nil { t.Fatal(err) } command := theGatesCommand(t, env) if !strings.Contains(command, `--changed-file "$MESH_CHECK_CHANGED_FILE"`) || strings.Contains(command, `--changed "`) { t.Errorf("a cut list is not given to the gate through the file alone: %s", command) } } func TestACommandThatCannotStartFailsWithItsError(t *testing.T) { // 1247/AC4 var out tail layer := ownCheck(t.Context(), CheckSpec{Toolchain: "go-image"}, []ScriptPart{{Toolchain: "go", Script: CheckScript}}, t.TempDir(), &out, func() bool { return false }, func(image, script string) *exec.Cmd { return exec.CommandContext(t.Context(), "/nonexistent/docker", script) }, func(string, string, ...any) {}) if layer == nil || layer.Verdict != "fail" { t.Fatalf("a command that cannot start answered %+v", layer) } if !strings.Contains(layer.Summary, CheckScript) || !strings.Contains(layer.Summary, "no such file or directory") || strings.HasSuffix(strings.TrimSpace(layer.Summary), "failed:") { t.Errorf("the summary does not say why: %q", layer.Summary) } }