From 43b59c5dff583255568bd62d7fc80311c328e71e Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 17 Oct 2026 15:58:46 +0200 Subject: [PATCH] =?UTF-8?q?issue=201025:=20the=20merge=20check=20vets=20an?= =?UTF-8?q?d=20tests=20every=20nested=20Go=20module=20(ADR=200420=20=C2=A7?= =?UTF-8?q?5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../merge_check_nested_test.go | 70 +++++++++++++++++++ merge-check.sh | 30 +++++++- 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 cmd/mesh-controller/merge_check_nested_test.go diff --git a/cmd/mesh-controller/merge_check_nested_test.go b/cmd/mesh-controller/merge_check_nested_test.go new file mode 100644 index 00000000..933c7a5d --- /dev/null +++ b/cmd/mesh-controller/merge_check_nested_test.go @@ -0,0 +1,70 @@ +package main + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" +) + +// **A repository's merge check runs every module's unit tests, a Go module of its own below the root included** +// (novox/hq ADR 0420 §5, issue 1025): `./...` stops at a nested go.mod. This runs the script's own +// `nested-modules` mode in a tree with a `go` that only records where it was run and fails for a module named bad. +func TestTheMergeCheckVetsAndTestsEveryNestedGoModule(t *testing.T) { + script, err := filepath.Abs("../../merge-check.sh") + if err != nil { + t.Fatal(err) + } + bin, tree := t.TempDir(), t.TempDir() + log := filepath.Join(t.TempDir(), "go.log") + fake := "#!/bin/sh\necho \"$(basename \"$PWD\") $1 GOFLAGS=[$GOFLAGS]\" >>\"$LOG\"\n" + + "[ \"$(basename \"$PWD\")\" = bad ] && [ \"$1\" = test ] && exit 1\nexit 0\n" + if err := os.WriteFile(filepath.Join(bin, "go"), []byte(fake), 0o755); err != nil { + t.Fatal(err) + } + run := func() (string, error) { + cmd := exec.Command("sh", script, "nested-modules") + cmd.Dir = tree + cmd.Env = append(os.Environ(), "PATH="+bin+":"+os.Getenv("PATH"), "LOG="+log, "GOFLAGS=-mod=vendor") + out, err := cmd.CombinedOutput() + return string(out), err + } + // No modules directory: a pass, nothing run. + if out, err := run(); err != nil { + t.Fatalf("a repository with no nested module failed: %v\n%s", err, out) + } + if _, err := os.Stat(log); err == nil { + t.Fatal("go ran with no nested module") + } + for _, m := range []string{"good", "bad", "other"} { + dir := filepath.Join(tree, "modules", m) + if err := os.MkdirAll(dir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module x\n"), 0o644); err != nil { + t.Fatal(err) + } + } + // A directory with no go.mod is no Go module of its own. + if err := os.MkdirAll(filepath.Join(tree, "modules", "typescript"), 0o755); err != nil { + t.Fatal(err) + } + out, err := run() + if err == nil { + t.Fatalf("a nested module whose tests fail passed the check:\n%s", out) + } + raw, _ := os.ReadFile(log) + ran := string(raw) + for _, want := range []string{"good vet GOFLAGS=[]", "good test GOFLAGS=[]", "other test GOFLAGS=[]", "bad test GOFLAGS=[]"} { + if !strings.Contains(ran, want) { + t.Errorf("%q was not run, nor outside the vendor directory:\n%s", want, ran) + } + } + if strings.Contains(ran, "typescript") { + t.Errorf("a directory with no go.mod was run:\n%s", ran) + } + if !strings.Contains(out, "nested module modules/bad FAILED") { + t.Errorf("the failing module is not named:\n%s", out) + } +} diff --git a/merge-check.sh b/merge-check.sh index 221d4bba..f30cc9de 100755 --- a/merge-check.sh +++ b/merge-check.sh @@ -36,6 +36,30 @@ judge_store_tests() { missing=$((missing + 1)) fi } +# nested_modules vets and tests every Go module of its own below the root, `modules//go.mod` (novox/hq ADR 0420 +# §5, issue 1025): `./...` stops at a nested go.mod, so a module that lives whole in this repository would have its +# unit tests run by nobody. None is a pass. Each runs in its own directory, outside the vendor directory this +# repository's own packages are built from; how its dependencies are found offline is issue 1019's (the move of the +# first two modules in), and it is settled there. A failing module fails the check; the others are still run. +nested_modules() { + nested=0 + for gomod in modules/*/go.mod; do + [ -e "$gomod" ] || continue + dir=$(dirname "$gomod") + echo "nested module $dir:" + if ! (cd "$dir" && GOFLAGS= CGO_ENABLED=0 go vet ./... && + GOFLAGS= CGO_ENABLED="${cgo:-0}" go test ${race:-} -count=1 -timeout 30m ./...); then + echo "nested module $dir FAILED" + nested=1 + fi + done + return $nested +} +# `sh merge-check.sh nested-modules` runs only that, to try it on a tree. +if [ "${1:-}" = nested-modules ]; then + nested_modules + exit +fi # `sh merge-check.sh judge-store-tests …` runs only the judging, to try it on a # recorded `go test -v` output. if [ "${1:-}" = judge-store-tests ]; then @@ -46,7 +70,10 @@ if [ "${1:-}" = judge-store-tests ]; then exit fi -unformatted=$(gofmt -l cmd internal examples) +gofmt_dirs="cmd internal examples" +[ -d modules ] && gofmt_dirs="$gofmt_dirs modules" +# shellcheck disable=SC2086 # the directories are one word each, split on purpose +unformatted=$(gofmt -l $gofmt_dirs) if [ -n "$unformatted" ]; then echo "not gofmt'd:" echo "$unformatted" @@ -232,6 +259,7 @@ else cat "$work/rest.out" [ "$(cat "$work/rest.status")" -eq 0 ] || suite_failed=1 say_shards || suite_failed=1 + nested_modules || suite_failed=1 fi if [ -z "$listed" ]; then