119 lines
3.6 KiB
Go
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, " ") }
|