138 lines
6.6 KiB
Go
138 lines
6.6 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// A file's text kept as a template file in its module's repository (novox/hq ADR 0349 §2, design 57 package T1,
|
|
// issue 654): `module check` and the merge gate read it from the tree beside the manifest, so a change to a
|
|
// template alone is judged as a change to its module.
|
|
|
|
const templatedNotifier = `{"module":"notifier","version":"1",
|
|
"build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]},
|
|
"resources":[{"id":"conf","type":"file","path":"/etc/notifier/notifier.conf","mode":"0644","template":"files/notifier.conf"}]}`
|
|
|
|
// inlineNotifier is the same module as the mesh holds it: built, its text inline.
|
|
const inlineNotifier = `{"module":"notifier","version":"1",
|
|
"resources":[{"id":"conf","type":"file","path":"/etc/notifier/notifier.conf","mode":"0644","content":"volume = 3\n"}]}`
|
|
|
|
// aTemplatedModule writes a module's directory: its manifest and its files/.
|
|
func aTemplatedModule(t *testing.T, dir, manifest string, files map[string]string) string {
|
|
t.Helper()
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
path := filepath.Join(dir, "module.json")
|
|
if err := os.WriteFile(path, []byte(manifest), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for name, text := range files {
|
|
full := filepath.Join(dir, filepath.FromSlash(name))
|
|
if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(full, []byte(text), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
return path
|
|
}
|
|
|
|
func TestModuleCheckReadsTemplatesFromTheTreeBesideTheManifest(t *testing.T) {
|
|
good := aTemplatedModule(t, t.TempDir(), templatedNotifier, map[string]string{"files/notifier.conf": "volume = 3\n"})
|
|
var out bytes.Buffer
|
|
if err := moduleCheck([]string{good}, &out); err != nil || !strings.Contains(out.String(), "notifier: ok") {
|
|
t.Fatalf("a module with its text in files/ was refused: %v\n%s", err, out.String())
|
|
}
|
|
|
|
// A change to the template alone, to text the controller refuses inline, is refused the same.
|
|
bad := aTemplatedModule(t, t.TempDir(), templatedNotifier,
|
|
map[string]string{"files/notifier.conf": "volume = 3\nstore = ${dir:nowhere}\n"})
|
|
out.Reset()
|
|
if err := moduleCheck([]string{bad}, &out); err == nil || !strings.Contains(out.String(), "nowhere") {
|
|
t.Fatalf("a template whose text is refused inline passed:\n%s", out.String())
|
|
}
|
|
|
|
// A symlink under files/ that no template names: the build's packing refuses it, so the check does too.
|
|
linked := t.TempDir()
|
|
aTemplatedModule(t, linked, templatedNotifier, map[string]string{"files/notifier.conf": "volume = 3\n"})
|
|
if err := os.Symlink("/etc/hostname", filepath.Join(linked, "files", "elsewhere")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out.Reset()
|
|
if err := moduleCheck([]string{filepath.Join(linked, "module.json")}, &out); err == nil ||
|
|
!strings.Contains(out.String(), "files/elsewhere is a symlink") {
|
|
t.Fatalf("a symlink under files/ passed the check:\n%s", out.String())
|
|
}
|
|
|
|
// And a template the tree lacks, by name.
|
|
missing := aTemplatedModule(t, t.TempDir(), templatedNotifier, nil)
|
|
out.Reset()
|
|
if err := moduleCheck([]string{missing}, &out); err == nil || !strings.Contains(out.String(), "files/notifier.conf") {
|
|
t.Fatalf("a missing template was not refused by name:\n%s", out.String())
|
|
}
|
|
}
|
|
|
|
// templatedMesh is catalogueMesh with the notifier on the laptop, registered as built: its text inline.
|
|
|
|
// aTemplatedTree is the catalogue's checkout with the notifier's text in files/.
|
|
func aTemplatedTree(t *testing.T, manifests map[string]string, text string, has bool) string {
|
|
t.Helper()
|
|
tree := aTree(t, withEdit(manifests, "notifier", ""))
|
|
files := map[string]string{}
|
|
if has {
|
|
files["files/notifier.conf"] = text
|
|
}
|
|
aTemplatedModule(t, filepath.Join(tree, "modules", "notifier"), templatedNotifier, files)
|
|
return tree
|
|
}
|
|
|
|
const notifierTemplate = "modules/notifier/files/notifier.conf"
|
|
|
|
// What the gate composes for the module in its template form is what the mesh composes for it inline.
|
|
|
|
// A module whose files/ is unpacked onto the machine names its own templates directory (issue 731): `module check`
|
|
// reads its templates from there and says of it exactly what it says of the same module with its text inline.
|
|
func TestModuleCheckReadsTemplatesFromTheTemplatesDirectoryTheManifestNames(t *testing.T) {
|
|
const inline = `{"module":"notifier","version":"1",
|
|
"build":{"artifacts":[{"name":"scripts","kind":"archive","from":"files"}]},
|
|
"resources":[{"id":"scripts","type":"archive","path":"/usr/local/lib/notifier","artifact":"scripts"},
|
|
{"id":"conf","type":"file","path":"/etc/notifier/notifier.conf","mode":"0644","content":"volume = 3\n"}]}`
|
|
const templated = `{"module":"notifier","version":"1","templates":"templates",
|
|
"build":{"artifacts":[{"name":"scripts","kind":"archive","from":"files"},
|
|
{"name":"templates","kind":"archive","from":"templates"}]},
|
|
"resources":[{"id":"scripts","type":"archive","path":"/usr/local/lib/notifier","artifact":"scripts"},
|
|
{"id":"conf","type":"file","path":"/etc/notifier/notifier.conf","mode":"0644","template":"templates/notifier.conf"}]}`
|
|
scripts := map[string]string{"files/bin/notify": "#!/bin/sh\n"}
|
|
var want bytes.Buffer
|
|
if err := moduleCheck([]string{aTemplatedModule(t, t.TempDir(), inline, scripts)}, &want); err != nil {
|
|
t.Fatalf("the inline module was refused: %v\n%s", err, want.String())
|
|
}
|
|
with := map[string]string{"files/bin/notify": "#!/bin/sh\n", "templates/notifier.conf": "volume = 3\n"}
|
|
var got bytes.Buffer
|
|
if err := moduleCheck([]string{aTemplatedModule(t, t.TempDir(), templated, with)}, &got); err != nil {
|
|
t.Fatalf("a module with its text in templates/ was refused: %v\n%s", err, got.String())
|
|
}
|
|
if got.String() != want.String() {
|
|
t.Fatalf("module check says otherwise of the templated module:\ninline: %s\ntemplated: %s", want.String(),
|
|
got.String())
|
|
}
|
|
|
|
// Its text in files/ instead, where the machine unpacks it, is not where its manifest says its templates are.
|
|
misplaced := strings.Replace(templated, `"templates/notifier.conf"`, `"files/notifier.conf"`, 1)
|
|
got.Reset()
|
|
if err := moduleCheck([]string{aTemplatedModule(t, t.TempDir(), misplaced,
|
|
map[string]string{"files/notifier.conf": "volume = 3\n"})}, &got); err == nil ||
|
|
!strings.Contains(got.String(), "files/notifier.conf") {
|
|
t.Fatalf("a template outside the templates directory the manifest names passed:\n%s", got.String())
|
|
}
|
|
}
|
|
|
|
// Issue 876: the gate judges a moved module that names its templates as registration reads it after a real build,
|
|
// handed its templates; it refused every such module as "marked moved and names no template". This calls the gate's
|
|
// own shelf (shelfWithChange), which needs no database.
|