issue 1838: catch node names in paths, hyphenated and lowerCamel forms; test the exit codes
mesh/merge-gate head a3b51a9d9a02 merged into trunk db67a0e55ec3 — pass: the change touches no module of the mesh's graph
mesh/repo-check head a3b51a9d9a02 merged into trunk db67a0e55ec3 — pass: THE CHANGE ALTERS ITS OWN CHECK (merge-check.sh): main's version judged it; the ch…
mesh/delivery delivered
mesh/check failed: unit: nfsserver/cmd/nfs-server.TestARealRootOwnedFolderIsRefused: --- FAIL: TestARealRootOwnedFolderIsRefused (0.00s) | a3b51a9d9a0…

The forge blank hid any novox/ path segment, and joined names passed; read covered from the root.
This commit is contained in:
2026-10-22 14:18:26 +02:00
parent 138a1e1c4b
commit a3b51a9d9a
2 changed files with 91 additions and 27 deletions
+36 -23
View File
@@ -3,8 +3,8 @@
// 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.
// they may be written. The modules covered are in `covered` at checks/particulars/covered, one a line. A name is matched
// whole and in any case; a hyphen, 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.
//
@@ -25,41 +25,49 @@ import (
"strings"
)
var forge = regexp.MustCompile(`(?i)git\.novox\.be|\bnovox/[\w.-]+`)
// forge is the forge's host and organisation path, blanked before a line is read; a path segment novox/ elsewhere
// (testdata/novox/x, /srv/novox/data) is not blanked.
var forge = regexp.MustCompile(`(?i)git\.novox\.be(?:/novox/[\w.-]+)?|(?:^|[^/\w.-])novox/[\w.-]+`)
func main() {
root := "."
if len(os.Args) > 1 {
root = os.Args[1]
}
os.Exit(check(root))
}
// check runs the check over a catalogue and returns the exit code: 0 clean, 1 a finding, 2 a list unreadable.
func check(root string) int {
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")
covered, err = readList(filepath.Join(root, "checks/particulars/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)
return 2
}
findings, err := run(root, names, covered)
if err != nil {
fmt.Fprintln(os.Stderr, "particulars:", err)
os.Exit(2)
return 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)
return 1
}
fmt.Printf("particulars: none of %d node names in the %d covered modules\n", len(names), len(covered))
return 0
}
// readList reads a list: one entry a line; blank lines and lines starting with # are not entries.
@@ -99,7 +107,9 @@ func run(root string, names, covered []string) ([]string, error) {
}
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)...)
for _, n := range hits(filepath.ToSlash(rel), names) {
out = append(out, fmt.Sprintf("%s:0: node name %q in the path", filepath.ToSlash(rel), n))
}
if d.Name() == "go.sum" {
return nil
}
@@ -117,9 +127,6 @@ func run(root string, names, covered []string) ([]string, error) {
return nil, err
}
}
for i, f := range out {
out[i] = strings.Replace(f, " (name):", ":0:", 1)
}
sort.Strings(out)
return out, nil
}
@@ -127,19 +134,25 @@ func run(root string, names, covered []string) ([]string, error) {
// 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), " ")))
}
for _, n := range hits(line, names) {
out = append(out, fmt.Sprintf("%s:%d: node name %q — %s", file, i+1, n, strings.Join(strings.Fields(line), " ")))
}
}
return out
}
// hits returns the node names one line holds: as a word (a hyphen or underscore parts it), as a part of a
// CamelCase identifier (TestAce, novoxTree), the forge's host and organisation path blanked first.
func hits(line string, names []string) []string {
line = forge.ReplaceAllStringFunc(line, func(s string) string { return strings.Repeat(" ", len(s)) })
var out []string
for _, n := range names {
q := regexp.QuoteMeta(n)
camel := regexp.QuoteMeta(strings.ToUpper(n[:1]) + n[1:])
rx := regexp.MustCompile(`(?i:(?:^|[^A-Za-z0-9])` + q + `(?:$|[^A-Za-z0-9]))|[a-z0-9]` + camel + `(?:$|[^a-z])|(?:^|[^A-Za-z0-9])` + q + `[A-Z0-9]`)
if rx.MatchString(line) {
out = append(out, strings.ToLower(n))
}
}
return out
+55 -4
View File
@@ -35,14 +35,65 @@ func TestTheForgeIsNotANodeName(t *testing.T) {
"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",
"git.novox.be",
"space trace surface grace g14x",
} {
if got := scan("f", s, names); len(got) != 0 && !strings.Contains(s, "ace-of") {
if got := scan("f", s, names); len(got) != 0 {
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 TestANodeNameInAPathOrJoinedIsRefused(t *testing.T) {
for _, s := range []string{
"modules/kernel/testdata/novox/meminfo",
"/srv/novox/data",
"testdata/shanks-meminfo.txt",
"on-shanks",
"shanks-desktop",
"mesh-novox",
"func novoxTree() {}",
"aceFiles := 1",
"g14Meminfo := 2",
"novox_tree := 3",
} {
if got := scan("f", s, names); len(got) == 0 {
t.Errorf("%q not refused", s)
}
}
}
func TestANodeNamedPathIsRefusedThroughRun(t *testing.T) {
root := t.TempDir()
p := filepath.Join(root, "modules/kernel/testdata/ace/x")
os.MkdirAll(filepath.Dir(p), 0o755)
os.WriteFile(p, []byte("clean\n"), 0o644)
got, err := run(root, names, []string{"kernel"})
if err != nil || len(got) != 1 || !strings.HasPrefix(got[0], "modules/kernel/testdata/ace/x:0: ") {
t.Errorf("got %v, %v", got, err)
}
}
func TestCheckReturnsOneOnAFindingAndTwoOnAnUnreadableList(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("particulars", "ace\n")
write("checks/particulars/covered", "kernel\n")
write("modules/kernel/a", "clean\n")
if got := check(root); got != 0 {
t.Errorf("clean = %d, want 0", got)
}
write("modules/kernel/a", "on ace\n")
if got := check(root); got != 1 {
t.Errorf("finding = %d, want 1", got)
}
os.Remove(filepath.Join(root, "particulars"))
if got := check(root); got != 2 {
t.Errorf("no list = %d, want 2", got)
}
}