Give -v to verbose, move --version to -V (closes #64)
check / check (push) Successful in 2m3s
check / check (push) Successful in 2m3s
urfave/cli's built-in version flag claimed -v, so "mfer -v --version" failed to parse, and -v meant version at the root but verbose on generate, check, freshen and fetch. Verbose is the more common meaning, so the root now takes -v and -q too, the version flag takes -V, and a -v or -q before the subcommand name applies to that subcommand. User-visible changes: -v alone prints help with verbose logging, not the version; use -V or --version. "mfer version" prints "mfer version 0.1.0 (...)", the same line as --version, instead of the bare "0.1.0 (...)". The error-message tests now send their log lines to io.Discard; they were landing in other tests' output buffers. Model: opus-4-8 (implementation); opus-5-5 (rework)
This commit is contained in:
+106
-1
@@ -27,6 +27,7 @@ const (
|
||||
testManifest = "/manifest.mf"
|
||||
testFlagBase = "--base"
|
||||
testFlagNoExtra = "--no-extra-files"
|
||||
testFlagVersion = "--version"
|
||||
)
|
||||
|
||||
var errSimulatedWrite = errors.New("simulated write failure")
|
||||
@@ -102,7 +103,7 @@ func TestVersionCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
opts := testOpts([]string{testApp, "version"}, fs)
|
||||
opts := testOpts([]string{testApp, cmdVersion}, fs)
|
||||
|
||||
exitCode := runCLI(opts)
|
||||
|
||||
@@ -113,6 +114,110 @@ func TestVersionCommand(t *testing.T) {
|
||||
assert.Contains(t, stdout, "abc123")
|
||||
}
|
||||
|
||||
// TestVFlagCollision covers the -v/--verbose vs --version flag interaction
|
||||
// (issue #64). Verbose owns -v; version answers to --version and -V. None of
|
||||
// these invocations may produce a parser error, and the two ways of asking
|
||||
// for the version must print the same thing.
|
||||
func TestVFlagCollision(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Invocations that must print the version and exit 0.
|
||||
versionCases := map[string][]string{
|
||||
"long version flag": {testApp, testFlagVersion},
|
||||
"short version flag": {testApp, "-V"},
|
||||
"verbose then version": {testApp, "-v", testFlagVersion},
|
||||
"long verbose and version": {testApp, "--verbose", testFlagVersion},
|
||||
}
|
||||
|
||||
for name, args := range versionCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts := testOpts(args, afero.NewMemMapFs())
|
||||
exitCode := runCLI(opts)
|
||||
|
||||
assert.Equal(t, 0, exitCode, "stderr: %s", testStderr(t, opts))
|
||||
assert.Contains(t, testStdout(t, opts), mfer.Version)
|
||||
assert.NotContains(t, testStderr(t, opts), "two forms of the same flag")
|
||||
})
|
||||
}
|
||||
|
||||
// Invocations that must enable verbose and exit 0 without a parser error.
|
||||
verboseCases := map[string][]string{
|
||||
"short verbose flag": {testApp, "-v"},
|
||||
"long verbose flag": {testApp, "--verbose"},
|
||||
}
|
||||
|
||||
for name, args := range verboseCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts := testOpts(args, afero.NewMemMapFs())
|
||||
exitCode := runCLI(opts)
|
||||
|
||||
assert.Equal(t, 0, exitCode, "stderr: %s", testStderr(t, opts))
|
||||
assert.Contains(t, testStdout(t, opts), cmdGenerate,
|
||||
"root should show help listing subcommands")
|
||||
assert.Empty(t, testStderr(t, opts))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestVersionFlagAndCommandMatch asserts that "mfer --version" and
|
||||
// "mfer version" produce identical output (issue #64).
|
||||
func TestVersionFlagAndCommandMatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
flagOpts := testOpts([]string{testApp, testFlagVersion}, afero.NewMemMapFs())
|
||||
require.Equal(t, 0, runCLI(flagOpts))
|
||||
|
||||
cmdOpts := testOpts([]string{testApp, cmdVersion}, afero.NewMemMapFs())
|
||||
require.Equal(t, 0, runCLI(cmdOpts))
|
||||
|
||||
assert.Equal(t, testStdout(t, flagOpts), testStdout(t, cmdOpts))
|
||||
}
|
||||
|
||||
// TestVerbosityFlagsBeforeSubcommand asserts that -q and -v given before the
|
||||
// subcommand name take effect in that subcommand (issue #64).
|
||||
func TestVerbosityFlagsBeforeSubcommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("quiet before fetch hides the banner", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts := testOpts([]string{testApp, "-q", cmdFetch}, afero.NewMemMapFs())
|
||||
|
||||
assert.Equal(t, 1, runCLI(opts))
|
||||
assert.Contains(t, testStderr(t, opts), errURLRequired.Error())
|
||||
assert.NotContains(t, testStdout(t, opts), banner)
|
||||
})
|
||||
|
||||
t.Run("verbose twice before fetch enables debug logging", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts := testOpts([]string{testApp, "-v", "-v", cmdFetch}, afero.NewMemMapFs())
|
||||
|
||||
assert.Equal(t, 1, runCLI(opts))
|
||||
assert.Contains(t, testStderr(t, opts), "fetchManifestOperation()")
|
||||
})
|
||||
|
||||
t.Run("quiet before export hides the manifest summary", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
manifest := buildTestManifest(t, map[string][]byte{"a.txt": []byte("a")})
|
||||
require.NoError(t, afero.WriteFile(fs, testManifest, manifest, 0o644))
|
||||
|
||||
loud := testOpts([]string{testApp, cmdExport, testManifest}, fs)
|
||||
require.Equal(t, 0, runCLI(loud))
|
||||
assert.Contains(t, testStderr(t, loud), "loaded manifest")
|
||||
|
||||
quiet := testOpts([]string{testApp, "-q", cmdExport, testManifest}, fs)
|
||||
require.Equal(t, 0, runCLI(quiet))
|
||||
assert.NotContains(t, testStderr(t, quiet), "loaded manifest")
|
||||
})
|
||||
}
|
||||
|
||||
func TestHelpCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user