buildstep's generated.go and generated_test.go and claude-code's config_test.go reached the join unformatted: the second of a kind, which no check caught since each module's tests hold only themselves. All three are formatted, and checks/gofmt refuses a Go file of the catalogue gofmt would change. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
135 lines
5.3 KiB
Go
135 lines
5.3 KiB
Go
package buildstep
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/seatgen"
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// contracts writes a contracts module of one seat, its generated files as the generator writes them, and answers its
|
|
// directory and the generated paths.
|
|
func contracts(t *testing.T, manifest string) (string, []string) {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
for p, text := range map[string]string{
|
|
"module.json": manifest,
|
|
"seats/index.json": `{"format":1,"seats":["one-store"]}`,
|
|
"seats/one-store/seat.json": `{"format":1,"seat":"one-store","scope":"mesh","grain":"seat-list","decision":"ADR 1","verbs":[{"name":"query","input":{"type":"object"}}]}`,
|
|
} {
|
|
writeContract(t, filepath.Join(dir, p), text)
|
|
}
|
|
m, err := seatgen.Load(dir, "example.org/c/go", "@example/c")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
files, err := seatgen.Generate(m)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var paths []string
|
|
for p, b := range files {
|
|
writeContract(t, filepath.Join(dir, p), string(b))
|
|
paths = append(paths, p)
|
|
}
|
|
// The names the compare reads, where the contracts module states them; a generated go.mod or package.json is kept.
|
|
if _, ok := files["go/go.mod"]; !ok {
|
|
writeContract(t, filepath.Join(dir, "go/go.mod"), "module example.org/c/go\n")
|
|
}
|
|
if _, ok := files["ts/package.json"]; !ok {
|
|
writeContract(t, filepath.Join(dir, "ts/package.json"), `{"name":"@example/c"}`)
|
|
}
|
|
return dir, paths
|
|
}
|
|
|
|
func writeContract(t *testing.T, path, text string) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(path, []byte(text), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// **CAT-BG5, CAT-BN2**: a contracts module's generated files are regenerated and compared, each build module its own
|
|
// root; a file that differs, one missing and one the generator did not write are each named; a clean tree and a module
|
|
// naming no generated-from name none; a build ask answers them in its out-of-step.
|
|
func TestAContractsModulesGeneratedFilesAreComparedWithTheGenerator(t *testing.T) {
|
|
marked := `{"module":"c","build":{"artifacts":[{"name":"go","kind":"package","language":"go","paths":["go"],"generated-from":"."}]}}`
|
|
dir, paths := contracts(t, marked)
|
|
var goFile, tsFile string
|
|
for _, p := range paths {
|
|
if strings.HasPrefix(p, "go/") && strings.HasSuffix(p, ".go") && goFile == "" {
|
|
goFile = p
|
|
}
|
|
if strings.HasPrefix(p, "ts/") && strings.HasSuffix(p, ".ts") && tsFile == "" {
|
|
tsFile = p
|
|
}
|
|
}
|
|
if goFile == "" || tsFile == "" {
|
|
t.Fatalf("the generator wrote no Go or no TypeScript file: %v", paths)
|
|
}
|
|
for _, root := range []string{GeneratedRootGo, GeneratedRootTS} {
|
|
if got, err := OutOfStep(dir, root); err != nil || len(got) != 0 {
|
|
t.Fatalf("a clean tree, root %s: %v %v", root, got, err)
|
|
}
|
|
}
|
|
writeContract(t, filepath.Join(dir, goFile), "package changed\n")
|
|
writeContract(t, filepath.Join(dir, "go/extra.go"), "package extra\n")
|
|
writeContract(t, filepath.Join(dir, tsFile), "// changed\n")
|
|
got, err := OutOfStep(dir, GeneratedRootGo)
|
|
if want := sorted(goFile, "go/extra.go"); err != nil || !reflect.DeepEqual(got, want) {
|
|
t.Errorf("Go root: %v %v, want %v", got, err, want)
|
|
}
|
|
if got, _ := OutOfStep(dir, GeneratedRootTS); !reflect.DeepEqual(got, []string{tsFile}) {
|
|
t.Errorf("TypeScript root: %v, want %s alone", got, tsFile)
|
|
}
|
|
_ = os.Remove(filepath.Join(dir, goFile))
|
|
if got, _ := OutOfStep(dir, GeneratedRootGo); !reflect.DeepEqual(got, sorted(goFile, "go/extra.go")) {
|
|
t.Errorf("a missing file is not named: %v", got)
|
|
}
|
|
plain, _ := contracts(t, `{"module":"p","build":{"artifacts":[{"name":"go","kind":"package","language":"go","paths":["go"]}]}}`)
|
|
writeContract(t, filepath.Join(plain, "go/extra.go"), "package extra\n")
|
|
if got, err := OutOfStep(plain, GeneratedRootGo); err != nil || len(got) != 0 {
|
|
t.Errorf("a module naming no generated-from compared: %v %v", got, err)
|
|
}
|
|
|
|
// The build ask answers them beside what it built, and with a failed build too.
|
|
built := 0
|
|
steps := AskSteps{Compare: CompareStep(GeneratedRootGo, func(ws string) string { return ws }),
|
|
Fetch: func(context.Context, map[string]any) (any, error) {
|
|
return map[string]any{"workspace": filepath.Dir(dir)}, nil
|
|
},
|
|
Build: func(context.Context, map[string]any) (any, error) {
|
|
if built++; built > 1 {
|
|
return nil, errors.New("the second artifact does not build")
|
|
}
|
|
return map[string]any{"digest": "sha256:aa"}, nil
|
|
}}
|
|
job := delivery.BuildJob{Module: "c", Path: filepath.Base(dir), Fetch: map[string]any{"repository": "novox/c"},
|
|
Artifacts: []delivery.AskArtifact{{Artifact: "go"}}}
|
|
out, err := RunAsk(context.Background(), steps, ask(job))
|
|
if a, ok := out.(delivery.BuildAnswer); err != nil || !ok || !reflect.DeepEqual(a.OutOfStep, sorted(goFile, "go/extra.go")) || len(a.Artifacts) != 1 {
|
|
t.Errorf("the build ask's out-of-step: %+v %v", out, err)
|
|
}
|
|
job.Artifacts = append(job.Artifacts, delivery.AskArtifact{Artifact: "go-other"})
|
|
built = 0
|
|
out, err = RunAsk(context.Background(), steps, ask(job))
|
|
if a, ok := out.(delivery.BuildAnswer); err == nil || !ok || len(a.OutOfStep) != 2 {
|
|
t.Errorf("a failed build answered no out-of-step: %+v %v", out, err)
|
|
}
|
|
}
|
|
|
|
func sorted(paths ...string) []string {
|
|
sort.Strings(paths)
|
|
return paths
|
|
}
|