issue 1838: refuse a node name of this mesh in the pacman and kernel modules
A module is for any mesh; the check reads the list of names at the root and the covered modules, and the kernel module's fixtures now say what they show.
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# The modules the check covers (novox/hq ADR 0507 §9, issue 1838), one a line; issue 1838 widens it.
|
||||
pacman
|
||||
kernel
|
||||
@@ -0,0 +1,3 @@
|
||||
module git.novox.be/novox/mesh-catalog/checks/particulars
|
||||
|
||||
go 1.22
|
||||
@@ -0,0 +1,146 @@
|
||||
// Command particulars keeps this mesh's node names out of the modules it covers (novox/hq ADR 0507 §9,
|
||||
// issue 1838): a module is for any mesh, so no manifest, file, code string, identifier, comment, test name or
|
||||
// fixture of a covered module names a node of this one.
|
||||
//
|
||||
// The names are in the file `particulars` at the catalogue's root, one a line, `#` comments — the one place
|
||||
// they may be written. The modules covered are in `covered` beside this file, one a line. A name is matched
|
||||
// whole and in any case; a hyphen joins a word, an underscore or a CamelCase boundary parts one, so a longer word holding a name is another word.
|
||||
// The forge is not a node name: its host (git.novox.be) and the organisation path (novox/<repo>) are blanked
|
||||
// before a line is read.
|
||||
//
|
||||
// go run . <catalogue root>
|
||||
//
|
||||
// Exits 1 naming file:line and the word, 2 when a list cannot be read.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var forge = regexp.MustCompile(`(?i)git\.novox\.be|\bnovox/[\w.-]+`)
|
||||
|
||||
func main() {
|
||||
root := "."
|
||||
if len(os.Args) > 1 {
|
||||
root = os.Args[1]
|
||||
}
|
||||
names, err := readList(filepath.Join(root, "particulars"))
|
||||
if err == nil && len(names) == 0 {
|
||||
err = fmt.Errorf("particulars lists no name — the check would pass on anything")
|
||||
}
|
||||
var covered []string
|
||||
if err == nil {
|
||||
covered, err = readList("covered")
|
||||
if err == nil && len(covered) == 0 {
|
||||
err = fmt.Errorf("covered lists no module — the check would pass on anything")
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "particulars:", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
findings, err := run(root, names, covered)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "particulars:", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
for _, f := range findings {
|
||||
fmt.Println(f)
|
||||
}
|
||||
if len(findings) > 0 {
|
||||
fmt.Printf("particulars: %d node name(s) in a module for any mesh (novox/hq ADR 0507 §9) — say what it shows, not where it was seen\n", len(findings))
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("particulars: none of %d node names in the %d covered modules\n", len(names), len(covered))
|
||||
}
|
||||
|
||||
// readList reads a list: one entry a line; blank lines and lines starting with # are not entries.
|
||||
func readList(path string) ([]string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
var out []string
|
||||
sc := bufio.NewScanner(f)
|
||||
for sc.Scan() {
|
||||
line := strings.TrimSpace(sc.Text())
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
out = append(out, line)
|
||||
}
|
||||
return out, sc.Err()
|
||||
}
|
||||
|
||||
// run reads every text file of each covered module and returns the findings, sorted.
|
||||
func run(root string, names, covered []string) ([]string, error) {
|
||||
var out []string
|
||||
for _, m := range covered {
|
||||
dir := filepath.Join(root, "modules", m)
|
||||
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() {
|
||||
switch d.Name() {
|
||||
case "node_modules", "dist", "vendor", ".git":
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
rel, _ := filepath.Rel(root, path)
|
||||
// A path is read too: a testdata directory named after a node is a finding.
|
||||
out = append(out, scan(rel+" (name)", filepath.ToSlash(rel), names)...)
|
||||
if d.Name() == "go.sum" {
|
||||
return nil
|
||||
}
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bytes.IndexByte(b, 0) >= 0 {
|
||||
return nil
|
||||
}
|
||||
out = append(out, scan(filepath.ToSlash(rel), string(b), names)...)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for i, f := range out {
|
||||
out[i] = strings.Replace(f, " (name):", ":0:", 1)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// scan returns a finding for each node name in src, whole-word and in any case.
|
||||
func scan(file, src string, names []string) []string {
|
||||
var out []string
|
||||
var rxs []*regexp.Regexp
|
||||
for _, n := range names {
|
||||
// Whole word, underscore a separator, hyphen a joiner; and a CamelCase part of an identifier (TestAce).
|
||||
q := regexp.QuoteMeta(n)
|
||||
camel := regexp.QuoteMeta(strings.ToUpper(n[:1]) + n[1:])
|
||||
rxs = append(rxs, regexp.MustCompile(`(?i:(?:^|[^A-Za-z0-9-])`+q+`(?:$|[^A-Za-z0-9-]))|[a-z0-9]`+camel+`(?:$|[^a-z])`))
|
||||
}
|
||||
for i, line := range strings.Split(src, "\n") {
|
||||
line = forge.ReplaceAllStringFunc(line, func(s string) string { return strings.Repeat(" ", len(s)) })
|
||||
for j, rx := range rxs {
|
||||
if rx.MatchString(line) {
|
||||
out = append(out, fmt.Sprintf("%s:%d: node name %q — %s", file, i+1, strings.ToLower(names[j]), strings.Join(strings.Fields(line), " ")))
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var names = []string{"novox", "ace", "shanks", "g14"}
|
||||
|
||||
func TestANodeNameIsRefusedWhereItIsWritten(t *testing.T) {
|
||||
cases := map[string]struct{ file, src string }{
|
||||
"manifest string": {"modules/kernel/module.json", "{\n \"description\": \"as on Shanks\"\n}\n"},
|
||||
"file line": {"modules/kernel/files/boot.sh", "#!/bin/sh\necho run on g14\n"},
|
||||
"go identifier": {"modules/kernel/cmd/x/a.go", "package main\n\nfunc novox() {}\n"},
|
||||
"test name": {"modules/kernel/cmd/x/a_test.go", "package main\n\nfunc TestAce(t *testing.T) {}\n\nfunc TestOnAce_x(t *testing.T) {}\n"},
|
||||
}
|
||||
for name, c := range cases {
|
||||
if got := scan(c.file, c.src, names); len(got) == 0 {
|
||||
t.Errorf("%s: %q not refused", name, c.src)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheFindingNamesTheLineAndTheWord(t *testing.T) {
|
||||
got := scan("f.sh", "a\nb\nrun on ACE now\n", names)
|
||||
if len(got) != 1 || !strings.HasPrefix(got[0], "f.sh:3:") || !strings.Contains(got[0], "ace") {
|
||||
t.Errorf("got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheForgeIsNotANodeName(t *testing.T) {
|
||||
for _, s := range []string{
|
||||
"see novox/hq issue 5",
|
||||
"module git.novox.be/novox/mesh-sdk/go v1.2.3",
|
||||
"https://git.novox.be/novox/mesh-catalog",
|
||||
"space, trace, surface, grace, ace-of, mesh-novox, g14x",
|
||||
} {
|
||||
if got := scan("f", s, names); len(got) != 0 && !strings.Contains(s, "ace-of") {
|
||||
t.Errorf("%q refused: %v", s, got)
|
||||
}
|
||||
}
|
||||
if got := scan("f", "space trace surface grace g14x", names); len(got) != 0 {
|
||||
t.Errorf("longer words refused: %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnlyCoveredModulesAreRead(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
write := func(p, s string) {
|
||||
p = filepath.Join(root, p)
|
||||
os.MkdirAll(filepath.Dir(p), 0o755)
|
||||
os.WriteFile(p, []byte(s), 0o644)
|
||||
}
|
||||
write("modules/kernel/files/a", "on novox\n")
|
||||
write("modules/other/files/a", "on novox\n")
|
||||
got, err := run(root, names, []string{"kernel"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(got) != 1 || !strings.Contains(got[0], "modules/kernel/files/a:1") {
|
||||
t.Errorf("got %v", got)
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,9 @@
|
||||
set -eu
|
||||
|
||||
(cd checks/words && go test -count=1 . && go run . ../..)
|
||||
# Node names (novox/hq ADR 0507 §9, issue 1838): no node name of this mesh in a covered module; the names are in
|
||||
# `particulars`, the covered modules in checks/particulars/covered.
|
||||
(cd checks/particulars && go test -count=1 . && go run . ../..)
|
||||
# The toolchains (novox/hq ADR 0477 clause 4): the package go only in modules/go, nodejs and npm only in modules/nodejs.
|
||||
(cd checks/toolchains && go test -count=1 . && go run . ../..)
|
||||
# The image builder (novox/hq ADR 0487 §3): only modules/buildah provides rootless-image-build, docker never.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package main
|
||||
|
||||
// Over a stand-in tree holding novox's own files of 2026-10-14 23:25 (read through node-reads for
|
||||
// Over a stand-in tree holding the files of a real machine of 2026-10-14 23:25 (read through node-reads for
|
||||
// novox/hq issue 746): the mode, the per-size controls, meminfo's huge page lines, and fail2ban's and
|
||||
// journald's memory.stat and io.stat, cut to the lines the tool reads.
|
||||
|
||||
@@ -26,7 +26,7 @@ func tree(t *testing.T, files map[string]string) *Machine {
|
||||
return &Machine{Root: root}
|
||||
}
|
||||
|
||||
func novox(t *testing.T) *Machine {
|
||||
func capturedMachine(t *testing.T) *Machine {
|
||||
thp := "sys/kernel/mm/transparent_hugepage/"
|
||||
cg := "sys/fs/cgroup/system.slice/"
|
||||
files := map[string]string{
|
||||
@@ -53,8 +53,8 @@ func novox(t *testing.T) *Machine {
|
||||
return tree(t, files)
|
||||
}
|
||||
|
||||
func TestNovoxBeforeTheChange(t *testing.T) {
|
||||
a, err := novox(t).HugePages(10)
|
||||
func TestTheCapturedMachineBeforeTheChange(t *testing.T) {
|
||||
a, err := capturedMachine(t).HugePages(10)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -86,7 +86,7 @@ func TestNovoxBeforeTheChange(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnitsAreBoundedAndNoneIsSaid(t *testing.T) {
|
||||
a, err := novox(t).HugePages(1)
|
||||
a, err := capturedMachine(t).HugePages(1)
|
||||
if err != nil || len(a.Units) != 1 {
|
||||
t.Fatalf("%v %+v", err, a)
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ func (b bed) file(t *testing.T, p string) string {
|
||||
}
|
||||
|
||||
func TestApplySetsTheModeRotatesTheJournalOnceAndRestoreGivesBackWhatWasFound(t *testing.T) {
|
||||
// novox's file on 2026-10-14, read through node-reads.
|
||||
// a real machine's file on 2026-10-14, read through node-reads.
|
||||
b := newBed(t, "madvise", "[always] madvise never\n")
|
||||
out, err := b.run(t, "apply")
|
||||
if err != nil || !strings.Contains(out, "always -> madvise") {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# This mesh's node names (novox/hq ADR 0507 §9, issue 1838): the one place they may be written.
|
||||
novox
|
||||
ace
|
||||
shanks
|
||||
g14
|
||||
Reference in New Issue
Block a user