Cover every module in the particulars check, and tell the hardware model and the software from a node (issue 1838)
A module is covered from its first commit, so no list of covered modules can lag behind the catalogue (1838/AC1). asus-zephyrus-g14, ROG Zephyrus G14, Novox Mesh and novox-mesh hold a node name and are not one (1838/AC2).
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
# The modules the check covers (novox/hq ADR 0507 §9, issue 1838), one a line; issue 1838 widens it.
|
||||
pacman
|
||||
kernel
|
||||
+41
-16
@@ -1,12 +1,14 @@
|
||||
// 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.
|
||||
// fixture of any 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` 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.
|
||||
// they may be written. Every directory under modules/ is read, with no list of covered modules, so a new module
|
||||
// is covered from its first commit. 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.
|
||||
// Three names hold a node name and are not one, so they are blanked before a line is read: the forge's host
|
||||
// (git.novox.be) and organisation path (novox/<repo>), the hardware model (asus-zephyrus-g14, ROG Zephyrus G14)
|
||||
// and the software's own name (Novox Mesh, novox-mesh).
|
||||
//
|
||||
// go run . <catalogue root>
|
||||
//
|
||||
@@ -29,6 +31,11 @@ import (
|
||||
// (testdata/novox/x, /srv/novox/data) is not blanked.
|
||||
var forge = regexp.MustCompile(`(?i)git\.novox\.be(?:/novox/[\w.-]+)?|(?:^|[^/\w.-])novox/[\w.-]+`)
|
||||
|
||||
// ownNames are the hardware model's and the software's own names, blanked before a line is read: the model
|
||||
// in any separator and case (asus-zephyrus-g14, ASUS_ZEPHYRUS_G14, ROG Zephyrus G14), the software as two
|
||||
// words or hyphened, whole (Novox Mesh, novox mesh, novox-mesh), so novox-mesh-vault stays a node name joined.
|
||||
var ownNames = regexp.MustCompile(`(?i)(?:asus[-_ ])?zephyrus[-_ ]g14|(?:^|[^\w-])novox[- ]mesh(?:$|[^\w-])`)
|
||||
|
||||
func main() {
|
||||
root := "."
|
||||
if len(os.Args) > 1 {
|
||||
@@ -43,18 +50,18 @@ func check(root string) int {
|
||||
if err == nil && len(names) == 0 {
|
||||
err = fmt.Errorf("particulars lists no name — the check would pass on anything")
|
||||
}
|
||||
var covered []string
|
||||
var modules []string
|
||||
if err == nil {
|
||||
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")
|
||||
modules, err = moduleDirs(root)
|
||||
if err == nil && len(modules) == 0 {
|
||||
err = fmt.Errorf("modules/ holds no module — the check would pass on anything")
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "particulars:", err)
|
||||
return 2
|
||||
}
|
||||
findings, err := run(root, names, covered)
|
||||
findings, err := run(root, names, modules)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "particulars:", err)
|
||||
return 2
|
||||
@@ -66,7 +73,7 @@ func check(root string) int {
|
||||
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))
|
||||
return 1
|
||||
}
|
||||
fmt.Printf("particulars: none of %d node names in the %d covered modules\n", len(names), len(covered))
|
||||
fmt.Printf("particulars: none of %d node names in the %d modules\n", len(names), len(modules))
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -89,10 +96,25 @@ func readList(path string) ([]string, error) {
|
||||
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) {
|
||||
// moduleDirs returns every directory under the catalogue's modules/, sorted.
|
||||
func moduleDirs(root string) ([]string, error) {
|
||||
entries, err := os.ReadDir(filepath.Join(root, "modules"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out []string
|
||||
for _, m := range covered {
|
||||
for _, e := range entries {
|
||||
if e.IsDir() {
|
||||
out = append(out, e.Name())
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// run reads every text file of each module named and returns the findings, sorted.
|
||||
func run(root string, names, modules []string) ([]string, error) {
|
||||
var out []string
|
||||
for _, m := range modules {
|
||||
dir := filepath.Join(root, "modules", m)
|
||||
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
@@ -143,9 +165,12 @@ func scan(file, src string, names []string) []string {
|
||||
}
|
||||
|
||||
// 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.
|
||||
// CamelCase identifier (TestAce, novoxTree), the forge's host and organisation path and the hardware's and the
|
||||
// software's own names blanked first.
|
||||
func hits(line string, names []string) []string {
|
||||
line = forge.ReplaceAllStringFunc(line, func(s string) string { return strings.Repeat(" ", len(s)) })
|
||||
blank := func(s string) string { return strings.Repeat(" ", len(s)) }
|
||||
line = forge.ReplaceAllStringFunc(line, blank)
|
||||
line = ownNames.ReplaceAllStringFunc(line, blank)
|
||||
var out []string
|
||||
for _, n := range names {
|
||||
q := regexp.QuoteMeta(n)
|
||||
|
||||
@@ -82,7 +82,6 @@ func TestCheckReturnsOneOnAFindingAndTwoOnAnUnreadableList(t *testing.T) {
|
||||
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)
|
||||
@@ -97,7 +96,7 @@ func TestCheckReturnsOneOnAFindingAndTwoOnAnUnreadableList(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnlyCoveredModulesAreRead(t *testing.T) {
|
||||
func TestRunReadsOnlyTheModulesItIsGiven(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
write := func(p, s string) {
|
||||
p = filepath.Join(root, p)
|
||||
@@ -114,3 +113,59 @@ func TestOnlyCoveredModulesAreRead(t *testing.T) {
|
||||
t.Errorf("got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// 1838/AC1: the check reads every directory under modules/ and no list of covered modules, so a module is
|
||||
// covered from its first commit.
|
||||
func TestEveryModuleIsReadWithoutAList(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", "novox\n")
|
||||
write("modules/kernel/files/a", "clean\n")
|
||||
if got := check(root); got != 0 {
|
||||
t.Errorf("clean, no covered list = %d, want 0", got)
|
||||
}
|
||||
write("modules/new-module/files/a", "on novox\n")
|
||||
if got := check(root); got != 1 {
|
||||
t.Errorf("a finding in a module no list names = %d, want 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
// 1838/AC2: the hardware model's own name and the software's own name are not node names.
|
||||
func TestTheHardwareModelAndTheSoftwareAreNotNodeNames(t *testing.T) {
|
||||
for _, s := range []string{
|
||||
"modules/asus-zephyrus-g14/README.md",
|
||||
"the asus-zephyrus-g14 module",
|
||||
"ASUS_ZEPHYRUS_G14=1",
|
||||
"product_family: ROG Zephyrus G14",
|
||||
"# This machine is a node of a Novox mesh",
|
||||
"Novox Mesh turns a handful of personal machines into one system",
|
||||
"<!-- novox mesh: this file was changed on two nodes",
|
||||
`const sealInfo = "novox-mesh sealed box v1"`,
|
||||
"module git.novox.be/novox/mesh-catalog/modules/kernel",
|
||||
} {
|
||||
if got := scan("f", s, names); len(got) != 0 {
|
||||
t.Errorf("%q refused: %v", s, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1838/AC2: a node name standing alone is still one, beside the hardware's or the software's name too.
|
||||
func TestANodeNameBesideTheHardwareOrTheSoftwareIsRefused(t *testing.T) {
|
||||
cases := map[string]string{
|
||||
"g14": "the asus-zephyrus-g14 module on g14",
|
||||
"shanks": "Novox Mesh on shanks",
|
||||
"novox": "resolves under novox.internal",
|
||||
"ace": "ROG Zephyrus G14 beside ace",
|
||||
"novox mesh-ish": "mesh.mod.gitea.call.x.novox-mesh-vault",
|
||||
}
|
||||
for want, s := range cases {
|
||||
got := scan("modules/m/a.go", s, names)
|
||||
if len(got) != 1 || !strings.HasPrefix(got[0], "modules/m/a.go:1: ") {
|
||||
t.Errorf("%s: %q gave %v, want one finding naming file:line", want, s, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user