package buildstep import ( "context" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-sdk/go/forgekey" ) // A module whose every artifact names its paths takes its tree from them (novox/hq ADR 0499 clause 2): ModuleTrees asks // git for each named path's object at the commit, keeps them in Paths and answers their delivery.NamedTree as Tree; a // module naming none keeps its directory's tree, as before. func TestModuleTreesNarrowsAModuleWhoseArtifactsNameTheirPaths(t *testing.T) { root := t.TempDir() sdk := `{"module":"mesh-sdk","version":"1","build":{"artifacts":[{"name":"lib","kind":"package","language":"typescript","paths":["src","package.json"]}]}}` writeTreeFile(t, root, "module.json", sdk) writeTreeFile(t, root, "modules/b/module.json", `{"module":"b","version":"1"}`) merge := strings.Repeat("c", 40) var lsTree []string run := func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran { switch args[2] { case "ls-files": return forgekey.Ran{Stdout: "module.json\nmodules/b/module.json\n"} case "rev-parse": return forgekey.Ran{Stdout: "tree-of-" + args[3] + "\n"} case "ls-tree": lsTree = append(lsTree, strings.Join(args[4:], " ")) return forgekey.Ran{Stdout: "100644 blob " + strings.Repeat("b", 40) + "\tpackage.json\x00" + "040000 tree " + strings.Repeat("a", 40) + "\tsrc\x00"} } return forgekey.Ran{Status: 1, Stderr: "unexpected " + args[2]} } got, err := ModuleTrees(context.Background(), run, nil, root, merge) if err != nil { t.Fatal(err) } paths := map[string]string{"src": strings.Repeat("a", 40), "package.json": strings.Repeat("b", 40)} want, _ := delivery.NamedTree(hexOf(sdk), paths) if g := got["."]; g.Tree != want || len(g.Paths) != 2 || g.Paths["src"] != paths["src"] || g.Paths["package.json"] != paths["package.json"] { t.Fatalf("the root module = %+v, want tree %s and paths %v", g, want, paths) } if len(lsTree) != 1 || lsTree[0] != merge+" -- package.json src" { t.Fatalf("git ls-tree ran as %v", lsTree) } if g := got["modules/b"]; g.Tree != "tree-of-"+merge+":modules/b" || g.Paths != nil { t.Fatalf("a module naming no path was narrowed: %+v", g) } } // A named path absent at the commit fails the answer, naming the module and the path: a tree computed without it would // pin a build source that does not exist. func TestModuleTreesFailsNamingANamedPathAbsentAtTheCommit(t *testing.T) { root := t.TempDir() writeTreeFile(t, root, "modules/a/module.json", `{"module":"a","version":"1","build":{"artifacts":[{"name":"lib","kind":"package","language":"typescript","paths":["src","gone"]}]}}`) run := func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran { switch args[2] { case "ls-files": return forgekey.Ran{Stdout: "modules/a/module.json\n"} case "rev-parse": return forgekey.Ran{Stdout: "t\n"} case "ls-tree": if strings.Join(args[4:], " ") != strings.Repeat("c", 40)+" -- modules/a/gone modules/a/src" { return forgekey.Ran{Status: 1, Stderr: "wrong arguments " + strings.Join(args[3:], " ")} } return forgekey.Ran{Stdout: "040000 tree " + strings.Repeat("a", 40) + "\tmodules/a/src\x00"} } return forgekey.Ran{Status: 1} } _, err := ModuleTrees(context.Background(), run, nil, root, strings.Repeat("c", 40)) if err == nil || !strings.Contains(err.Error(), `"gone"`) || !strings.Contains(err.Error(), "modules/a") { t.Fatalf("an absent named path gave %v", err) } } // A manifest whose config value falls back to a predefined key is narrowed here as the indexer narrows it: both read it // with catalogue.ParseManifestForInputs (issue 1751), so a build module holding no published list computes the same // tree (ADR 0499 clause 2). func TestModuleTreesNarrowsAManifestFallingBackToAPredefinedKey(t *testing.T) { was := catalogue.ReplacePublished(nil) // the build modules' case: no published list defer catalogue.ReplacePublished(was) root := t.TempDir() raw := `{"module":"xterm","version":"1","config":{"size":{"type":"number","description":"The text size.","predefined":"font-size","default":12,"runtime":true}},` + `"build":{"artifacts":[{"name":"lib","kind":"package","language":"typescript","paths":["src"]}]}}` if _, err := catalogue.ParseManifest([]byte(raw)); err == nil { t.Fatal("ParseManifest reads the manifest with no list; the test proves nothing") } if _, err := catalogue.ParseManifestForInputs([]byte(raw)); err != nil { t.Fatalf("the indexer's read refuses the manifest: %v", err) } writeTreeFile(t, root, "module.json", raw) run := func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran { switch args[2] { case "ls-files": return forgekey.Ran{Stdout: "module.json\n"} case "rev-parse": return forgekey.Ran{Stdout: "whole\n"} case "ls-tree": return forgekey.Ran{Stdout: "040000 tree " + strings.Repeat("a", 40) + "\tsrc\x00"} } return forgekey.Ran{Status: 1} } got, err := ModuleTrees(context.Background(), run, nil, root, strings.Repeat("c", 40)) if err != nil { t.Fatal(err) } want, _ := delivery.NamedTree(hexOf(raw), map[string]string{"src": strings.Repeat("a", 40)}) if g := got["."]; g.Tree != want || g.Paths["src"] == "" { t.Fatalf("the module = %+v, want the named tree %s", g, want) } }