From 7efa7d8ac03d9ba3e44666b86d3a97d52c836f1c Mon Sep 17 00:00:00 2001 From: Fuad Daoud Date: Fri, 31 Jul 2026 10:55:11 +0300 Subject: [PATCH] test(cmd/morphic): pin the rendered help text with goldens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Golden files make the rendered text reviewable as a diff, so wording changes are deliberate rather than incidental. All four texts the CLI renders are pinned: the two help texts a user asks for, and the two misuse texts a user gets by accident, whose wording nothing else held. The table's stream column doubles as the stream-discipline assertion — help never reaches stderr, misuse never reaches stdout, and the stream a text does not use stays empty. The -update flag comes from ir/irtest rather than a second declaration here, which would panic the test binary at init the moment anything else imported irtest. Document the help forms and their exit code in the README, and replace the compile synopsis the CLI never prints with the one it does — the flag table below it is compile's, not the root command's. --- README.md | 14 ++++-- cmd/morphic/help_test.go | 61 ++++++++++++++++++++++++ cmd/morphic/testdata/compile-help.txt | 19 ++++++++ cmd/morphic/testdata/compile-usage.txt | 4 ++ cmd/morphic/testdata/root-help.txt | 9 ++++ cmd/morphic/testdata/unknown-command.txt | 10 ++++ 6 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 cmd/morphic/testdata/compile-help.txt create mode 100644 cmd/morphic/testdata/compile-usage.txt create mode 100644 cmd/morphic/testdata/root-help.txt create mode 100644 cmd/morphic/testdata/unknown-command.txt diff --git a/README.md b/README.md index b970217..ccb1d64 100644 --- a/README.md +++ b/README.md @@ -102,18 +102,26 @@ morphic compile openapi.yaml -o api.ir.json # ...or to a file ``` usage: - morphic compile [-o out.json] [--fail-on error|warning] [--skip-validate] + morphic [flags] + morphic compile [flags] ``` +`morphic`, `morphic help`, and `morphic` with a help flag (`-h`, `--help` or `-help`) print the +command list. `morphic help compile` and `morphic compile --help` print a command's flags. Help +always prints to stdout and exits `0`. + +The flags below are `compile`'s: + | Flag | Meaning | |---|---| | `-o ` | Write IR JSON to `` instead of stdout. | | `--fail-on error\|warning` | Exit non-zero when a diagnostic at or above this severity is emitted (default `error`). | | `--skip-validate` | Skip the referential-integrity `validate` pass. | +| `--explain ` | Report what compiling produced at this source coordinate instead of writing the document. | Diagnostics print one per line as ` #: `. Exit codes: -`0` clean, `1` a diagnostic reached the `--fail-on` threshold (or the spec could not be lowered), -`2` a usage or I/O error. +`0` clean (and for any help request), `1` a diagnostic reached the `--fail-on` threshold (or the +spec could not be lowered), `2` a usage or I/O error. ### Library diff --git a/cmd/morphic/help_test.go b/cmd/morphic/help_test.go index f421ac9..5fda7ea 100644 --- a/cmd/morphic/help_test.go +++ b/cmd/morphic/help_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/dexpace/morphic/internal/testspec" + "github.com/dexpace/morphic/ir/irtest" ) func TestRun_HelpForms(t *testing.T) { @@ -131,3 +132,63 @@ func TestRootHelp_ListsEveryCommand(t *testing.T) { assert.Contains(t, got, c.summary) } } + +// compareHelpGolden compares got against the golden file at path, or rewrites +// it when -update is passed to go test. +// +// The -update flag comes from irtest rather than being declared here. A second +// flag.Bool("update", ...) in this package would not merely shadow the first: +// both register on the same command-line FlagSet, so the binary panics at init +// the moment anything pulls irtest into these tests. +func compareHelpGolden(t *testing.T, path, got string) { + t.Helper() + + if irtest.Update() { + require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755)) + require.NoError(t, os.WriteFile(path, []byte(got), 0o644)) + return + } + + want, err := os.ReadFile(path) + require.NoError(t, err, "read golden %s (run with -update to create)", path) + assert.Empty(t, cmp.Diff(string(want), got), "golden mismatch for %s", path) +} + +// TestHelp_MatchesGolden pins every text the CLI renders. The two help texts +// are the ones a user asks for; the two misuse texts are the ones a user gets +// by accident, and nothing else holds their wording — writeCommandUsage and the +// root help that misuse prints to stderr could otherwise drift in silence. +// +// The wantStderr column doubles as the stream-discipline assertion: help must +// never reach stderr, misuse must never reach stdout, and whichever stream is +// not under test must be empty. +func TestHelp_MatchesGolden(t *testing.T) { + // Not parallel: the -update path writes files. + tests := []struct { + name string + args []string + golden string + wantCode int + wantStderr bool + }{ + {"root", nil, "root-help.txt", 0, false}, + {"compile", []string{"help", "compile"}, "compile-help.txt", 0, false}, + {"compile misuse", []string{"compile"}, "compile-usage.txt", 2, true}, + {"unknown command", []string{"bogus"}, "unknown-command.txt", 2, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var stdout, stderr bytes.Buffer + require.Equal(t, tt.wantCode, run(tt.args, &stdout, &stderr)) + + got, quiet := stdout.String(), stderr.String() + if tt.wantStderr { + got, quiet = quiet, got + } + require.Empty(t, quiet, "nothing may reach the stream this text does not use") + + compareHelpGolden(t, filepath.Join("testdata", tt.golden), got) + }) + } +} diff --git a/cmd/morphic/testdata/compile-help.txt b/cmd/morphic/testdata/compile-help.txt new file mode 100644 index 0000000..115d6b1 --- /dev/null +++ b/cmd/morphic/testdata/compile-help.txt @@ -0,0 +1,19 @@ +usage: + morphic compile [flags] + +Lower an API spec (OpenAPI 3.x) into Morphic IR JSON on stdout, and write +diagnostics to stderr. + +--explain reports what compiling produced at one source coordinate — the +type node interned there, the coordinates interned beneath it, and the +diagnostics stamped at it — instead of writing the document. + +flags: + -explain string + report what compiling produced at this source pointer instead of writing IR JSON + -fail-on string + fail (exit 1) on diagnostics at or above this severity: error|warning (default "error") + -o string + write IR JSON to this file instead of stdout + -skip-validate + skip the referential-integrity validate pass diff --git a/cmd/morphic/testdata/compile-usage.txt b/cmd/morphic/testdata/compile-usage.txt new file mode 100644 index 0000000..5c00ad9 --- /dev/null +++ b/cmd/morphic/testdata/compile-usage.txt @@ -0,0 +1,4 @@ +morphic: compile requires exactly one spec file +usage: + morphic compile [flags] +run "morphic help compile" for details. diff --git a/cmd/morphic/testdata/root-help.txt b/cmd/morphic/testdata/root-help.txt new file mode 100644 index 0000000..d753569 --- /dev/null +++ b/cmd/morphic/testdata/root-help.txt @@ -0,0 +1,9 @@ +usage: + morphic [flags] + +morphic lowers an API spec into Morphic IR. + +commands: + compile lower an API spec (OpenAPI 3.x) into Morphic IR JSON + +run "morphic help " for command details. diff --git a/cmd/morphic/testdata/unknown-command.txt b/cmd/morphic/testdata/unknown-command.txt new file mode 100644 index 0000000..8210934 --- /dev/null +++ b/cmd/morphic/testdata/unknown-command.txt @@ -0,0 +1,10 @@ +morphic: unknown command "bogus" +usage: + morphic [flags] + +morphic lowers an API spec into Morphic IR. + +commands: + compile lower an API spec (OpenAPI 3.x) into Morphic IR JSON + +run "morphic help " for command details.