package build import ( "bytes" "os" "path/filepath" "strings" "testing" ) func TestLoadHelloManifest(t *testing.T) { m, err := LoadManifest(filepath.Join("..", "..", "examples", "hello", "summer.yaml")) if err != nil { t.Fatal(err) } if m.Binary != "hello" { t.Fatalf("binary = %q", m.Binary) } if len(m.Plugins) != 3 || m.Plugins[0].ID != "golem15.hello" || m.Plugins[1].ID != "golem15.greeter" || m.Plugins[2].ID != "golem15.optional" { t.Fatalf("plugins = %+v", m.Plugins) } } func TestParseManifestRejectsDuplicatesAndInvalidPaths(t *testing.T) { dir := t.TempDir() t.Run("duplicate id", func(t *testing.T) { path := filepath.Join(dir, "dup.yaml") write(t, path, "module: example.com/app\nbinary: app\nplugins:\n - id: a.b\n module: example.com/one\n - id: a.b\n module: example.com/two\n") if _, err := LoadManifest(path); err == nil || !strings.Contains(err.Error(), "a.b") { t.Fatalf("want duplicate id error, got %v", err) } }) t.Run("invalid module", func(t *testing.T) { path := filepath.Join(dir, "bad.yaml") write(t, path, "module: example.com/app\nbinary: app\nplugins:\n - id: a.b\n module: \"example.com/one; rm -rf /\"\n") if _, err := LoadManifest(path); err == nil { t.Fatal("want invalid module error") } }) t.Run("path traversal module", func(t *testing.T) { path := filepath.Join(dir, "dotdot.yaml") write(t, path, "module: example.com/app\nbinary: app\nplugins:\n - id: a.b\n module: example.com/../evil\n") if _, err := LoadManifest(path); err == nil { t.Fatal("want invalid module error") } }) } func TestGenerateStableQuotedImportsInManifestOrder(t *testing.T) { m := Manifest{ Module: "example.com/app", Binary: "hello", Plugins: []Plugin{ {ID: "golem15.hello", Module: "example.com/app/plugins/base"}, {ID: "golem15.greeter", Module: "example.com/app/plugins/greeter"}, }, } first, err := generatePluginsGen(m) if err != nil { t.Fatal(err) } second, err := generatePluginsGen(m) if err != nil { t.Fatal(err) } if !bytes.Equal(first, second) { t.Fatal("generated plugins.gen.go is not byte-stable") } src := string(first) baseAt := strings.Index(src, `example.com/app/plugins/base`) greeterAt := strings.Index(src, `example.com/app/plugins/greeter`) if baseAt < 0 || greeterAt < 0 || baseAt > greeterAt { t.Fatalf("imports not in manifest order:\n%s", src) } if !strings.Contains(src, "\t_ \"example.com/app/plugins/base\"") { t.Fatalf("import is not Go-quoted:\n%s", src) } mainSrc, err := generateMain(m) if err != nil { t.Fatal(err) } again, err := generateMain(m) if err != nil { t.Fatal(err) } if !bytes.Equal(mainSrc, again) { t.Fatal("generated main.go is not byte-stable") } if !bytes.Contains(mainSrc, []byte(`bonfire.NewRoot("hello"`)) { t.Fatalf("main does not use manifest binary:\n%s", mainSrc) } if bytes.Contains(mainSrc, []byte("examples/hello")) { t.Fatal("generated main hard-codes examples/hello") } } func TestWriteIfChangedSkipsIdenticalBytes(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "out.go") content := []byte("package main\n") if err := writeIfChanged(path, content); err != nil { t.Fatal(err) } info, err := os.Stat(path) if err != nil { t.Fatal(err) } if err := writeIfChanged(path, content); err != nil { t.Fatal(err) } again, err := os.Stat(path) if err != nil { t.Fatal(err) } if !info.ModTime().Equal(again.ModTime()) { t.Fatal("identical content rewrote the file") } } func write(t *testing.T, path, body string) { t.Helper() if err := os.WriteFile(path, []byte(body), 0o644); err != nil { t.Fatal(err) } }