diff --git a/go/cli/cli.go b/go/cli/cli.go index a8222a1..30180fc 100644 --- a/go/cli/cli.go +++ b/go/cli/cli.go @@ -54,11 +54,24 @@ type Answer struct { Refused string `json:"refused,omitempty"` // Cut says the output was longer than one bus message and was cut there. Cut bool `json:"cut,omitempty"` + // Unknown says the line was asked and its outcome is not known: no answer came in time, and it may have + // run. Never said as a refusal, which means nothing ran. + Unknown string `json:"unknown,omitempty"` } // Refusal is an answer saying that nothing ran, and why. func Refusal(why string) Answer { return Answer{Exit: 1, Refused: why} } +// NotKnown is an answer saying the line was asked and what came of it is not known: it may have run. call is +// the controller's call the line runs as, when it is known, which `calls` answers. +func NotKnown(call, why string) Answer { + where := "the controller's `calls` lists what came of each line" + if call != "" { + where = "see the call " + call + " (`calls " + call + "`)" + } + return Answer{Exit: 1, Unknown: why + "; the line may have run — " + where} +} + // ErrTooLong is a request or an answer over its bound. var ErrTooLong = errors.New("longer than the protocol allows") diff --git a/go/cli/cli_test.go b/go/cli/cli_test.go index c418639..893dba4 100644 --- a/go/cli/cli_test.go +++ b/go/cli/cli_test.go @@ -26,7 +26,8 @@ func TestARequestAndAnAnswerSurviveTheirRoundTrip(t *testing.T) { } b.Reset() - sent := Answer{Stdout: []byte("ok\n\xff"), Stderr: []byte("said"), Exit: 3, Terminal: true, Why: "the terminal", Cut: true} + sent := Answer{Stdout: []byte("ok\n\xff"), Stderr: []byte("said"), Exit: 3, Terminal: true, Why: "the terminal", Cut: true, + Unknown: "no answer; the line may have run — see the call call-1"} if err := WriteAnswer(&b, sent); err != nil { t.Fatal(err) } @@ -34,7 +35,8 @@ func TestARequestAndAnAnswerSurviveTheirRoundTrip(t *testing.T) { if err != nil { t.Fatal(err) } - if !bytes.Equal(a.Stdout, sent.Stdout) || string(a.Stderr) != "said" || a.Exit != 3 || !a.Terminal || a.Why != sent.Why || !a.Cut { + if !bytes.Equal(a.Stdout, sent.Stdout) || string(a.Stderr) != "said" || a.Exit != 3 || !a.Terminal || a.Why != sent.Why || !a.Cut || + a.Unknown != sent.Unknown { t.Fatalf("the answer came back as %+v", a) } } @@ -90,3 +92,14 @@ func TestAskSpeaksTheProtocolOverASocket(t *testing.T) { t.Fatalf("a missing socket was not said: %v", err) } } + +// An outcome not known is never a refusal: it says the line may have run, and exits non-zero. +func TestAnUnknownOutcomeIsNotARefusal(t *testing.T) { + a := NotKnown("call-7", "no answer within 6m0s") + if a.Refused != "" || a.Exit == 0 || !strings.Contains(a.Unknown, "may have run") || !strings.Contains(a.Unknown, "call-7") { + t.Fatalf("an unknown outcome reads as %+v", a) + } + if b := NotKnown("", "no answer"); strings.Contains(b.Unknown, "call ") { + t.Fatalf("an unknown outcome with no call names one: %+v", b) + } +} diff --git a/go/merge-check.sh b/go/merge-check.sh new file mode 100644 index 0000000..19a7ec0 --- /dev/null +++ b/go/merge-check.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# mesh-check-toolchain: go +# +# The Go part of the SDK's own check (novox/hq ADR 0237 as amended, ADR 0272): the Go binding, run by the build +# seat in the mesh's Go toolchain, its own container, because the SDK's merge-check.sh declares it +# (`# mesh-check-also: go go/merge-check.sh`) — and by hand, from anywhere: `sh go/merge-check.sh`. +# +# Formatted, vetted, and its whole suite run: the stdio loop and the local protocol mesh-cli speaks to the +# node-engine. Standard library only, so nothing is fetched. +set -eu +cd "$(dirname "$0")" + +unformatted=$(gofmt -l .) +if [ -n "$unformatted" ]; then + echo "not gofmt'd:" + echo "$unformatted" + exit 1 +fi +go vet ./... +go test -count=1 ./... diff --git a/merge-check.sh b/merge-check.sh index 32c532f..b1c0cd0 100644 --- a/merge-check.sh +++ b/merge-check.sh @@ -1,5 +1,6 @@ #!/bin/sh # mesh-check-toolchain: typescript +# mesh-check-also: go go/merge-check.sh # # The SDK's own check (novox/hq ADR 0237 as amended): the second layer of a pull request's merge check, # `mesh/repo-check`, run by the build seat in the mesh's TypeScript toolchain — and by hand. @@ -9,8 +10,9 @@ # spine every module imports, type-checked and its suite run, its dependencies installed from the lock # file alone (public packages: the SDK depends on nothing of the mesh's). # -# **Said, never passed silently**: the Go, Python, Rust and C bindings are not tested in this toolchain. -# The Go binding's tests run when a Go compiler is present (a person's run), and are said as not run when not. +# **Said, never passed silently**: the Python, Rust and C bindings are not tested in any toolchain. The Go +# binding runs in the Go toolchain, from go/merge-check.sh, declared on the second line (novox/hq ADR 0272); here +# it runs only when a Go compiler is present (a person's run). set -eu npm ci --no-audit --no-fund --loglevel=error @@ -20,6 +22,6 @@ npm test if command -v go >/dev/null 2>&1; then (cd go && test -z "$(gofmt -l .)" && go vet ./... && go test -count=1 ./...) else - echo "NOT TESTED HERE: go/ (the Go binding) — the TypeScript toolchain holds no Go compiler" + echo "NOT TESTED HERE: go/ (the Go binding) — the Go toolchain runs it from go/merge-check.sh" fi echo "NOT TESTED HERE: python/, rust/, c/ — no toolchain of theirs runs a merge check"