feat(01-01): generate and build hello app from manifest
- Add summer build with validated summer.yaml codegen - Generate stable main.go and plugins.gen.go then go build - Smoke-test the built hello binary greeter:hello command
This commit is contained in:
129
internal/build/build_test.go
Normal file
129
internal/build/build_test.go
Normal file
@@ -0,0 +1,129 @@
|
||||
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) != 2 || m.Plugins[0].ID != "golem15.hello" || m.Plugins[1].ID != "golem15.greeter" {
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user