199 lines
7.9 KiB
Go
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)
|
|
}
|
|
}
|