package builder import ( "context" "os" "path/filepath" "slices" "strings" "testing" ) // What a build says it was made from, as files (novox/hq ADR 0267), and what a build compiling a Go // program is handed. // onTrunk answers the trunk's questions as a clone of a commit on main would, or off it. type onTrunk struct { *recorded off bool // behind is a commit on the trunk that is not its head: an older commit built by hand. behind bool } func (o onTrunk) run(ctx context.Context, dir, name string, args ...string) (string, error) { if name == "git" && len(args) > 0 && args[0] == "symbolic-ref" { return "origin/main\n", nil } if name == "git" && len(args) > 1 && args[0] == "rev-parse" && args[1] == "origin/main" && o.behind { return "feedfacefeedfacefeedfacefeedfacefeedface\n", nil } if name == "git" && len(args) > 0 && args[0] == "merge-base" && o.off { return "", os.ErrNotExist } return compiling{o.recorded}.run(ctx, dir, name, args...) } // aSharedRepository is a repository holding two programs that share a package, as the controller's does. func aSharedRepository(readme, shared string) map[string]string { return map[string]string{ "go.mod": "module example.com/ctl\n\ngo 1.22\n", "go.sum": "", "README.md": readme, "cmd/ctl/main.go": "package main\n\nimport _ \"example.com/ctl/internal/shared\"\n\nfunc main() {}\n", "proxy/main.go": "package main\n\nimport _ \"example.com/ctl/internal/shared\"\n\nfunc main() {}\n", "internal/shared/s.go": "package shared\n\nconst S = " + shared + "\n", "internal/only/o.go": "package only\n", } } const aProxy = `{"module":"route-proxy","version":"1", "build":{"artifacts":[ {"name":"server","kind":"image","from":"Dockerfile","compiles":"proxy", "context":{"repository":"https://forge.invalid/ctl.git","ref":"main"}}, {"name":"trust","kind":"upstream","from":"alpine@sha256:` + "3333333333333333333333333333333333333333333333333333333333333333" + `"}]}}` func buildTheProxy(t *testing.T, context_ map[string]string, off bool, behind ...bool) (Result, *recorded, string) { t.Helper() r := &recorded{ contents: map[string]string{"modules/route-proxy/" + ManifestName: aProxy, "modules/route-proxy/Dockerfile": "FROM scratch\nCOPY . .\n", "modules/route-proxy/README.md": "x"}, secondary: map[string]map[string]string{"https://forge.invalid/ctl.git": context_}, } workspace := t.TempDir() got, err := Build(context.Background(), onTrunk{r, off, len(behind) > 0 && behind[0]}.run, r, "https://forge.invalid/catalogue.git", "modules/route-proxy", "", workspace, nil, Npmrc{}, GitCredential{}, nil) if err != nil { t.Fatal(err) } return got, r, workspace } func TestAnImageCompilingGoIsHandedItsBuildSourceAndSaysIt(t *testing.T) { got, r, workspace := buildTheProxy(t, aSharedRepository("one", "1"), false) // Built in the narrowed tree, which holds the program's closure and nothing else. at := "" for i, line := range r.ran { if strings.HasPrefix(line, "docker build ") { at = r.dirs[i] } } if filepath.Base(at) != "narrow-server" { t.Fatalf("docker build ran in %q, not the narrowed build source", at) } for file, want := range map[string]bool{"proxy/main.go": true, "internal/shared/s.go": true, "go.mod": true, "cmd/ctl/main.go": false, "internal/only/o.go": false, "README.md": false} { _, err := os.Stat(filepath.Join(workspace, "narrow-server", filepath.FromSlash(file))) if (err == nil) != want { t.Errorf("%s handed to the recipe: %v, wanted %v", file, err == nil, want) } } // Said per repository: its own, the manifest and the recipe; the context's, the closure. if len(got.Sources) != 2 { t.Fatalf("sources %+v", got.Sources) } own, ctx := got.Sources[0], got.Sources[1] if own.Repository != "" || !slices.Equal(own.Paths, []string{"modules/route-proxy/Dockerfile", "modules/route-proxy/module.json"}) { t.Errorf("its own build source: %+v", own) } if ctx.Repository != "https://forge.invalid/ctl.git" || ctx.Ref != "main" { t.Errorf("the context's build source names %q at %q", ctx.Repository, ctx.Ref) } for file, want := range map[string]bool{"proxy/main.go": true, "internal/shared/s.go": true, "go.mod": true, "cmd/ctl/main.go": false, "README.md": false} { if SourceHolds(ctx.Paths, file) != want { t.Errorf("the context's build source holds %s: %v, wanted %v (%v)", file, !want, want, ctx.Paths) } } // **The fingerprint is over the build source** (rule 5): a change outside it is one build, inside it another. readme, _, _ := buildTheProxy(t, aSharedRepository("two", "1"), false) if readme.Source != got.Source { t.Errorf("a README of the context changed the fingerprint: %s %s", got.Source, readme.Source) } shared, _, _ := buildTheProxy(t, aSharedRepository("one", "2"), false) if shared.Source == got.Source { t.Error("a change to the program's closure kept its fingerprint") } } // A build off the trunk, or of a trunk commit that is not its head, says no build source: the planner maps // a merge onto the trunk head's, and an older commit's closure lacks what was imported since. func TestABuildOffTheTrunksHeadSaysNoBuildSource(t *testing.T) { got, _, _ := buildTheProxy(t, aSharedRepository("one", "1"), true) if len(got.Sources) != 0 { t.Fatalf("a build off the trunk said %+v", got.Sources) } got, _, _ = buildTheProxy(t, aSharedRepository("one", "1"), false, true) if len(got.Sources) != 0 { t.Fatalf("a build of an older trunk commit said %+v", got.Sources) } } // An archive of the module's whole directory holds every file of it. func TestAnArchiveOfTheWholeDirectoryHoldsIt(t *testing.T) { manifest := `{"module":"look","version":"1","build":{"artifacts":[{"name":"all","kind":"archive","from":"."}]}, "resources":[{"id":"files","type":"archive","path":"/opt/look","artifact":"all"}]}` r := &recorded{contents: map[string]string{"modules/look/" + ManifestName: manifest, "modules/look/a/b.css": "x"}} got, err := Build(context.Background(), onTrunk{recorded: r}.run, r, "https://forge.invalid/catalogue.git", "modules/look", "", t.TempDir(), nil, Npmrc{}, GitCredential{}, nil) if err != nil { t.Fatal(err) } if len(got.Sources) != 1 || !SourceHolds(got.Sources[0].Paths, "modules/look/a/b.css") || SourceHolds(got.Sources[0].Paths, "modules/other/x") { t.Fatalf("sources %+v", got.Sources) } } // A recipe reading past its build source fails, naming what it could not find — in the real docker build; // here, the file is simply not in the tree it is handed, which is what makes that so. func TestAnImageCompilingANonexistentPackageFails(t *testing.T) { r := &recorded{ contents: map[string]string{ManifestName: strings.Replace(aProxy, `"compiles":"proxy"`, `"compiles":"nowhere"`, 1), "Dockerfile": "FROM scratch\n"}, secondary: map[string]map[string]string{"https://forge.invalid/ctl.git": aSharedRepository("one", "1")}, } _, err := Build(context.Background(), onTrunk{recorded: r}.run, r, "https://forge.invalid/catalogue.git", "", "", t.TempDir(), nil, Npmrc{}, GitCredential{}, nil) if err == nil || !strings.Contains(err.Error(), "nowhere") { t.Fatalf("a package that is not there built: %v", err) } } // A Go bundle of a module built from its repository's root says its import closure, and the manifest; // an image that compiles nothing it was told of leaves the module's source whole, and says none. func TestAGoBundleSaysItsClosureAndAnUntoldImageNothing(t *testing.T) { files := aSharedRepository("one", "1") manifest := `{"module":"ctl","version":"1","build":{"artifacts":[ {"name":"controller","kind":"bundle","language":"go","system":"arch","from":"cmd/ctl","binary":"ctl"}]}, "resources":[{"id":"controller","type":"process","name":"ctl","artifact":"controller","run":["./ctl"]}]}` r := &recorded{contents: map[string]string{ManifestName: manifest}} for k, v := range files { r.contents[k] = v } held := map[string]string{"mesh-tools-go/build": "registry.invalid/mesh-tools-go/build@sha256:" + strings.Repeat("b", 64)} got, err := Build(context.Background(), onTrunk{recorded: r}.run, r, "https://forge.invalid/ctl.git", "", "", t.TempDir(), held, Npmrc{}, GitCredential{}, nil) if err != nil { t.Fatal(err) } if len(got.Sources) != 1 || got.Sources[0].Repository != "" { t.Fatalf("sources %+v", got.Sources) } for file, want := range map[string]bool{"cmd/ctl/main.go": true, "internal/shared/s.go": true, ManifestName: true, "go.sum": true, "proxy/main.go": false, "README.md": false, "internal/only/o.go": false} { if SourceHolds(got.Sources[0].Paths, file) != want { t.Errorf("%s: held %v, wanted %v (%v)", file, !want, want, got.Sources[0].Paths) } } untold := `{"module":"ctl","version":"1","build":{"artifacts":[ {"name":"server","kind":"image","from":"Dockerfile"}]}}` r = &recorded{contents: map[string]string{ManifestName: untold, "Dockerfile": "FROM scratch\n"}} got, err = Build(context.Background(), onTrunk{recorded: r}.run, r, "https://forge.invalid/ctl.git", "", "", t.TempDir(), nil, Npmrc{}, GitCredential{}, nil) if err != nil { t.Fatal(err) } if len(got.Sources) != 0 { t.Fatalf("an image compiling nothing it was told of said a build source: %+v", got.Sources) } }