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

199 lines
7.9 KiB
Go

//go:build integration
package main
import (
"bytes"
"context"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
"slices"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
// realMachine gives the tests the real toolchain of the module's own lock and makes two workspaces holding one fixture
// bundle. The toolchain is the one this node's installed Node build module keeps (ADR 0533 §1): the integration level
// is given its toolchains read-only and no network, so it is copied into the test's own cache from there. Only where
// no toolchain is installed (a desk) is it installed with `npm ci --ignore-scripts` from the mesh's registry, as the
// module does, which needs the network.
func realMachine(t *testing.T) (*Module, string) {
t.Helper()
for _, p := range []string{"node", "npm", "bwrap"} { // bwrap: tsc, esbuild and the scripts run shut off (1309)
if _, err := exec.LookPath(p); err != nil {
t.Skipf("NOT RUN: no %s installed where this check runs (the mesh's Go toolchain image holds none); proven live on a build node", p)
}
}
home, cache, work := t.TempDir(), t.TempDir(), t.TempDir()
for _, n := range []string{"package.json", "package-lock.json"} {
raw, err := os.ReadFile(filepath.Join(moduleDir, "toolchain", n))
if err != nil {
t.Fatal(err)
}
write(t, filepath.Join(home, "toolchain", n), string(raw), 0o644)
}
lock, err := ReadRecipe(filepath.Join(home, "toolchain", "package-lock.json"))
if err != nil {
t.Fatal(err)
}
run := forgekey.ExecRunnerFor(0)
m := &Module{Node: "ace", Recipe: lock, Work: work, Run: run, Ctx: context.Background(),
Toolchain: &Toolchain{Paths: Paths{Home: home, Cache: cache}, Run: run, Registry: func() (Registry, error) {
return Registry{URL: "https://git.novox.be/api/packages/novox/npm/"}, nil
}}}
withStore(t, m)
if from := installedToolchain(t, filepath.Join(CacheDir, "toolchain"), filepath.Join(home, "toolchain", "package-lock.json")); from != "" {
copyToolchain(t, from, m.Toolchain.Paths.Dir(m.Recipe.Version()))
}
if err := m.Toolchain.Ensure(context.Background(), m.Recipe.Version()); err != nil {
t.Fatalf("no toolchain of this lock is installed under %s, and installing it from the registry failed: %v", filepath.Join(CacheDir, "toolchain"), err)
}
for _, id := range []string{"w1", "w2"} {
for p, body := range map[string]string{
"modules/m/module.json": `{"module":"m","build":{"artifacts":[{"name":"tools","kind":"bundle","language":"typescript","entrypoints":["tools/index.js"]}]}}`,
"modules/m/package.json": `{"name":"m","type":"module","scripts":{"test":"node -e \"process.exit(0)\""}}`,
"modules/m/tools/index.ts": "import { serveRegisteredOverStdio } from \"@novox/mesh-sdk/stdio\";\nexport const f: () => Promise<void> = serveRegisteredOverStdio as () => Promise<void>;\n",
} {
write(t, filepath.Join(work, id, "tree", filepath.FromSlash(p)), body, 0o644)
}
}
return m, work
}
// 1490/AC2: a fixture TypeScript bundle importing @novox/mesh-sdk, built twice by the real tsc and esbuild, has equal
// digests, and its archive holds the bundled .js, the .serve.mjs launcher and package.json.
func TestAFixtureBundleBuiltTwiceHasOneDigest(t *testing.T) {
m, work := realMachine(t)
var digests []string
for _, id := range []string{"w1", "w2"} {
out, err := m.build(context.Background(), map[string]any{"workspace": id, "module": "modules/m", "artifact": "tools"})
if err != nil {
t.Fatal(err)
}
r := out.(BuildResult)
digests = append(digests, r.Digest)
if want := []string{"package.json", "tools/index.js", "tools/index.serve.mjs"}; !slices.Equal(r.Files, want) {
t.Errorf("files = %v, want %v", r.Files, want)
}
}
if digests[0] != digests[1] {
t.Errorf("digests differ: %v", digests)
}
if links := symlinksUnder(t, work); len(links) != 0 {
t.Errorf("symlinks in the workspaces: %v", links)
}
}
// 1491: the real type-check and test script of the same fixture pass the unit level.
func TestTheFixturePassesTheUnitLevel(t *testing.T) {
m, _ := realMachine(t)
out, err := m.test(context.Background(), map[string]any{"workspace": "w1", "module": "modules/m", "level": "unit"})
if err != nil {
t.Fatal(err)
}
if r := out.(*TestResult); !r.Passed || r.NoTests || len(r.Ran) != 2 {
t.Errorf("result = %+v", r)
}
}
// Issue 1869: a package whose test imports its own build output (as mesh-sdk's do) passes the unit level: the test step
// emits it with the real tsc, shut off, before the test script runs.
func TestAPackageWhoseTestsImportItsBuildPassesTheUnitLevel(t *testing.T) {
if _, err := exec.LookPath("bwrap"); err != nil {
t.Skip("NOT RUN: no bubblewrap installed where this check runs; every build node has it (the module declares it)")
}
m, work := realMachine(t)
for p, body := range map[string]string{
"modules/p/package.json": `{"name":"p","type":"module","scripts":{"build":"tsc","test":"node --test test/a.test.mjs","prepare":"tsc"}}`,
"modules/p/tsconfig.json": `{"compilerOptions":{"module":"NodeNext","moduleResolution":"NodeNext","target":"ES2022","outDir":"dist","rootDir":"src"},"include":["src"]}`,
"modules/p/src/index.ts": "export const answer: number = 42;\n",
"modules/p/test/a.test.mjs": "import { test } from 'node:test';\nimport assert from 'node:assert';\nimport { answer } from '../dist/index.js';\n" +
"test('the build is there', () => assert.equal(answer, 42));\n",
} {
write(t, filepath.Join(work, "w1", "tree", filepath.FromSlash(p)), body, 0o644)
}
out, err := m.test(context.Background(), map[string]any{"workspace": "w1", "module": "modules/p", "level": "unit"})
if err != nil {
t.Fatal(err)
}
if r := out.(*TestResult); !r.Passed || !slices.Equal(r.Ran, []string{"build", "test"}) {
t.Errorf("result = %+v", r)
}
}
// installedToolchain is the toolchain directory under toolchains that this node's Node build module installed: the
// one installed from the lock at lockPath, byte for byte, or, when the change carries a lock no delivered release has
// installed yet, the one installed last, said in the test's log (ADR 0533 §3: a new lock is proven by the module that
// installs it on its first node). "" when none is there or the directory cannot be read: no Node build module here.
func installedToolchain(t *testing.T, toolchains, lockPath string) string {
t.Helper()
want, err := os.ReadFile(lockPath)
if err != nil {
t.Fatal(err)
}
entries, err := os.ReadDir(toolchains)
if err != nil {
return ""
}
last, lastAt := "", time.Time{}
for _, e := range entries {
dir := filepath.Join(toolchains, e.Name())
mark, err := os.Stat(filepath.Join(dir, installedMark))
if err != nil {
continue
}
got, err := os.ReadFile(filepath.Join(dir, "package-lock.json"))
if err != nil {
continue
}
if bytes.Equal(got, want) {
t.Logf("the toolchain is this node's installed %s, of this change's lock", dir)
return dir
}
if mark.ModTime().After(lastAt) {
last, lastAt = dir, mark.ModTime()
}
}
if last != "" {
t.Logf("NOT THIS LOCK: no toolchain of this change's lock is installed here; the tests run with the one installed last, %s", last)
}
return last
}
// copyToolchain copies the installed toolchain from into to whole, files and folders with their modes, so the test's
// steps run behind a toolchain of their own and the installed one is only read.
func copyToolchain(t *testing.T, from, to string) {
t.Helper()
err := filepath.WalkDir(from, func(p string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
rel, _ := filepath.Rel(from, p)
dst := filepath.Join(to, rel)
info, err := d.Info()
if err != nil {
return err
}
switch {
case d.IsDir():
return os.MkdirAll(dst, 0o755)
case info.Mode().IsRegular():
raw, err := os.ReadFile(p)
if err != nil {
return err
}
return os.WriteFile(dst, raw, info.Mode().Perm()|0o600)
default:
return fmt.Errorf("%s is neither a file nor a folder", p)
}
})
if err != nil {
t.Fatalf("copying the installed toolchain %s: %v", from, err)
}
}