Files
mesh-catalog/modules/mesh-build-node/cmd/mesh-build-node/harness_test.go
T

119 lines
3.6 KiB
Go

package main
import (
"context"
"os"
"path/filepath"
"slices"
"strings"
"sync"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
// A fake machine for the step tests: a toolchain directory laid out as `npm ci --no-bin-links` leaves it, a work
// directory, a clock that only the fake commands advance, and a runner that records every command and answers by
// what the test set.
type call struct {
dir string // env -C <dir>
name string // the program after env -C <dir>
args []string // its arguments
env []string
}
type fakeMachine struct {
mu sync.Mutex
calls []call
now time.Time
// cost is how long a program takes, by the base name of its path; answer is what it prints and its status;
// effect runs the program's effects on the disk.
cost map[string]time.Duration
answer map[string]forgekey.Ran
effect func(c call)
}
func (f *fakeMachine) run(_ context.Context, env []string, name string, args ...string) forgekey.Ran {
f.mu.Lock()
defer f.mu.Unlock()
c := call{name: name, args: args, env: env}
key := filepath.Base(name)
if name == "env" && len(args) >= 3 && args[0] == "-C" {
c = call{dir: args[1], name: args[2], args: args[3:], env: env}
key = filepath.Base(c.name)
if key == "node" && len(c.args) > 0 {
key = filepath.Base(c.args[0]) // node <tsc>
}
} else if name == "git" {
for _, a := range args {
if slices.Contains([]string{"init", "remote", "fetch", "checkout", "rev-parse", "ls-files"}, a) {
key = "git " + a
break
}
}
}
f.calls = append(f.calls, c)
f.now = f.now.Add(f.cost[key])
if f.effect != nil {
f.effect(c)
}
return f.answer[key]
}
func (f *fakeMachine) clock() time.Time { f.mu.Lock(); defer f.mu.Unlock(); return f.now }
// keys of the programs: tsc, esbuild, npm.
func newMachine(t *testing.T) (*Module, *fakeMachine, string) {
t.Helper()
home, cache, work := t.TempDir(), t.TempDir(), t.TempDir()
f := &fakeMachine{now: time.Unix(1_000_000, 0), cost: map[string]time.Duration{}, answer: map[string]forgekey.Ran{}}
m := &Module{Node: "ace", Recipe: Recipe{Steps: "1", Node: "v26.10.0", Lock: "lock"},
Toolchain: &Toolchain{Paths: Paths{Home: home, Cache: cache}, Registry: func() (Registry, error) {
return Registry{URL: "https://git.novox.be/api/packages/novox/npm/"}, nil
}},
Work: work, Run: f.run, Clock: f.clock, Ctx: context.Background()}
// The toolchain directory as npm ci leaves it, with no bin links.
nm := filepath.Join(m.toolchainDir(), "node_modules")
for _, p := range []string{"typescript/bin/tsc", "esbuild/bin/esbuild", "@esbuild/linux-x64/bin/esbuild",
"@novox/mesh-sdk/dist/stdio.js", "@types/node/index.d.ts", "nats/lib/nats.js"} {
write(t, filepath.Join(nm, p), "x", 0o644)
}
return m, f, work
}
func write(t *testing.T, path, body string, mode os.FileMode) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte(body), mode); err != nil {
t.Fatal(err)
}
}
// workspaceWith makes workspace "w1" of the machine with the files given (path under tree -> body).
func workspaceWith(t *testing.T, work string, files map[string]string) string {
t.Helper()
for p, body := range files {
write(t, filepath.Join(work, "w1", "tree", filepath.FromSlash(p)), body, 0o644)
}
return "w1"
}
func symlinksUnder(t *testing.T, root string) []string {
t.Helper()
var out []string
filepath.Walk(root, func(p string, info os.FileInfo, err error) error {
if err == nil && info.Mode()&os.ModeSymlink != 0 {
out = append(out, p)
}
return nil
})
return out
}
func joined(c call) string { return c.name + " " + strings.Join(c.args, " ") }