Route direct-stdout command output through internal/ui (closes #149)
version, info, remote info, config, and database delete wrote plain text straight to stdout, so they were unstyled and ignored --quiet. Output is now governed by internal/ui in two buckets. Status lines and confirmations (config init, config set, the database delete prompt) go through the ui message methods and are silenced by --quiet. The data a command exists to produce is written plain -- the version/info/remote-info reports, the snapshot list table, config get values, and the --json documents -- and is never suppressed, since a script depends on it and a marker would corrupt a table or document. The database delete confirmation prompt is always shown. Pure-cli commands reach ui through a small commandUI helper. Model: opus-4-8
This commit was merged in pull request #201.
This commit is contained in:
+25
-17
@@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -13,6 +12,7 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/yaml.v3"
|
||||
"sneak.berlin/go/vaultik/internal/ui"
|
||||
)
|
||||
|
||||
// configFileMode is the permission set for freshly written config files;
|
||||
@@ -265,7 +265,7 @@ The config is written to the path from --config, $VAULTIK_CONFIG, or
|
||||
the platform default config directory (e.g. ~/Library/Application Support/
|
||||
on macOS, ~/.config/ on Linux, /etc/vaultik/ as root).`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
path := configPathForInit()
|
||||
|
||||
_, err := os.Stat(path)
|
||||
@@ -285,8 +285,11 @@ on macOS, ~/.config/ on Linux, /etc/vaultik/ as root).`,
|
||||
return fmt.Errorf("writing config file: %w", err)
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(os.Stdout, "Config written to %s\n", path)
|
||||
_, _ = fmt.Fprintln(os.Stdout,
|
||||
// A written-confirmation, not scriptable output: route it
|
||||
// through the UI so it is styled and --quiet silences it.
|
||||
out := commandUI(cmd)
|
||||
out.Infof("Config written to %s.", path)
|
||||
out.Infof(
|
||||
"Edit it to set your age_recipients, snapshots, and storage_url.")
|
||||
|
||||
return nil
|
||||
@@ -328,7 +331,7 @@ func newConfigGetCommand() *cobra.Command {
|
||||
Use: "get <key>",
|
||||
Short: "Print a config value by dotted path (e.g. storage_url, compression_level)",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
path, err := ResolveConfigPath()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -344,8 +347,13 @@ func newConfigGetCommand() *cobra.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
// The value is scriptable output: it must stay machine-plain
|
||||
// (no marker, no color) and is never silenced by --quiet, so it
|
||||
// is written straight to stdout rather than through the UI.
|
||||
w := cmd.OutOrStdout()
|
||||
|
||||
if node.Kind == yaml.ScalarNode {
|
||||
_, _ = fmt.Fprintln(os.Stdout, node.Value)
|
||||
_, _ = fmt.Fprintln(w, node.Value)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -355,7 +363,7 @@ func newConfigGetCommand() *cobra.Command {
|
||||
return fmt.Errorf("marshaling value: %w", err)
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprint(os.Stdout, string(out))
|
||||
_, _ = fmt.Fprint(w, string(out))
|
||||
|
||||
return nil
|
||||
},
|
||||
@@ -377,23 +385,23 @@ Examples:
|
||||
vaultik config set compression_level 9
|
||||
vaultik config set s3.bucket mybucket # legacy S3 fields still supported`,
|
||||
Args: cobra.ExactArgs(configSetArgs),
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
path, err := ResolveConfigPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeConfigSet(os.Stdout, path, args[0], args[1])
|
||||
return writeConfigSet(commandUI(cmd), path, args[0], args[1])
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// writeConfigSet applies key=value to the config at path, writes it back
|
||||
// owner-only, and confirms the write by printing just the key name to w.
|
||||
// The value is never echoed: it may be a secret such as
|
||||
// s3.secret_access_key, and captured stdout or a pasted terminal would
|
||||
// then leak it.
|
||||
func writeConfigSet(w io.Writer, path, key, value string) error {
|
||||
// owner-only, and confirms the write by naming just the key through the
|
||||
// UI writer (styled, and silenced by --quiet). The value is never
|
||||
// echoed: it may be a secret such as s3.secret_access_key, and captured
|
||||
// stdout or a pasted terminal would then leak it.
|
||||
func writeConfigSet(out *ui.Writer, path, key, value string) error {
|
||||
root, err := loadYAMLFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -404,12 +412,12 @@ func writeConfigSet(w io.Writer, path, key, value string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := marshalConfigYAML(root)
|
||||
data, err := marshalConfigYAML(root)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshaling config: %w", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(path, out, configFileMode)
|
||||
err = os.WriteFile(path, data, configFileMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing config file: %w", err)
|
||||
}
|
||||
@@ -425,7 +433,7 @@ func writeConfigSet(w io.Writer, path, key, value string) error {
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(w, key)
|
||||
out.Infof("Set %s.", key)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
"sneak.berlin/go/vaultik/internal/config"
|
||||
"sneak.berlin/go/vaultik/internal/ui"
|
||||
)
|
||||
|
||||
// TestDefaultConfigTemplateParses ensures the init template is valid YAML
|
||||
@@ -246,19 +247,20 @@ func TestWriteConfigSetHidesSecret(t *testing.T) {
|
||||
t.Fatalf("seed config: %v", err)
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
var buf bytes.Buffer
|
||||
|
||||
err = writeConfigSet(&out, path, "s3.secret_access_key", secret)
|
||||
err = writeConfigSet(ui.NewWithColor(&buf, false), path,
|
||||
"s3.secret_access_key", secret)
|
||||
if err != nil {
|
||||
t.Fatalf("writeConfigSet: %v", err)
|
||||
}
|
||||
|
||||
if strings.Contains(out.String(), secret) {
|
||||
t.Errorf("output echoed the secret value: %q", out.String())
|
||||
if strings.Contains(buf.String(), secret) {
|
||||
t.Errorf("output echoed the secret value: %q", buf.String())
|
||||
}
|
||||
|
||||
if !strings.Contains(out.String(), "s3.secret_access_key") {
|
||||
t.Errorf("output did not confirm the key name: %q", out.String())
|
||||
if !strings.Contains(buf.String(), "s3.secret_access_key") {
|
||||
t.Errorf("output did not confirm the key name: %q", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,9 +279,10 @@ func TestWriteConfigSetTightensMode(t *testing.T) {
|
||||
t.Fatalf("seed config: %v", err)
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
var buf bytes.Buffer
|
||||
|
||||
err = writeConfigSet(&out, path, "compression_level", "9")
|
||||
err = writeConfigSet(ui.NewWithColor(&buf, false), path,
|
||||
"compression_level", "9")
|
||||
if err != nil {
|
||||
t.Fatalf("writeConfigSet: %v", err)
|
||||
}
|
||||
|
||||
+12
-11
@@ -48,7 +48,7 @@ storage destination on that run.
|
||||
|
||||
Use --force to skip the confirmation prompt.`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
// Resolve config path
|
||||
configPath, err := ResolveConfigPath()
|
||||
if err != nil {
|
||||
@@ -62,26 +62,31 @@ Use --force to skip the confirmation prompt.`,
|
||||
}
|
||||
|
||||
dbPath := cfg.IndexPath
|
||||
out := commandUI(cmd)
|
||||
|
||||
// Check if database exists
|
||||
_, err = os.Stat(dbPath)
|
||||
if os.IsNotExist(err) {
|
||||
_, _ = fmt.Fprintf(os.Stdout, "Database does not exist: %s\n", dbPath)
|
||||
out.Infof("Local state database does not exist: %s.", dbPath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Confirm unless --force
|
||||
// Confirm unless --force. The prompt and its immediate result
|
||||
// are an interactive exchange the operator must see, so they go
|
||||
// straight to stdout rather than through the UI and --quiet does
|
||||
// not silence them.
|
||||
if !force {
|
||||
_, _ = fmt.Fprintf(os.Stdout,
|
||||
w := cmd.OutOrStdout()
|
||||
_, _ = fmt.Fprintf(w,
|
||||
"This will delete the local state database at:\n %s\n\n", dbPath)
|
||||
_, _ = fmt.Fprint(os.Stdout, "Are you sure? Type 'yes' to confirm: ")
|
||||
_, _ = fmt.Fprint(w, "Are you sure? Type 'yes' to confirm: ")
|
||||
|
||||
var confirm string
|
||||
|
||||
_, err = fmt.Scanln(&confirm)
|
||||
if err != nil || confirm != "yes" {
|
||||
_, _ = fmt.Fprintln(os.Stdout, "Aborted.")
|
||||
_, _ = fmt.Fprintln(w, "Aborted.")
|
||||
|
||||
//nolint:nilerr // a failed/aborted confirmation is a clean abort
|
||||
return nil
|
||||
@@ -100,11 +105,7 @@ Use --force to skip the confirmation prompt.`,
|
||||
_ = os.Remove(walPath) // Ignore errors - files may not exist
|
||||
_ = os.Remove(shmPath)
|
||||
|
||||
rootFlags := GetRootFlags()
|
||||
if !rootFlags.Quiet {
|
||||
_, _ = fmt.Fprintf(os.Stdout, "Database deleted: %s\n", dbPath)
|
||||
}
|
||||
|
||||
out.Infof("Local state database deleted: %s.", dbPath)
|
||||
log.Info("Local state database deleted", "path", dbPath)
|
||||
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
package cli //nolint:testpackage // sets the unexported rootFlags directly
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// setRootFlags overrides the global rootFlags for the duration of one
|
||||
// test and restores it afterward. These tests must not run in parallel:
|
||||
// the flags are process-global, so the whole struct is saved and put
|
||||
// back rather than left mutated for the next test.
|
||||
func setRootFlags(t *testing.T, f RootFlags) {
|
||||
t.Helper()
|
||||
|
||||
old := rootFlags
|
||||
rootFlags = f
|
||||
|
||||
t.Cleanup(func() { rootFlags = old })
|
||||
}
|
||||
|
||||
// seedFile writes content to a fresh file and returns its path.
|
||||
func seedFile(t *testing.T, dir, name, content string) string {
|
||||
t.Helper()
|
||||
|
||||
path := filepath.Join(dir, name)
|
||||
|
||||
err := os.WriteFile(path, []byte(content), 0o600)
|
||||
if err != nil {
|
||||
t.Fatalf("seeding %s: %v", name, err)
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
// mustExecute runs a command with its output captured and fails the test
|
||||
// if it errors, returning what the command printed.
|
||||
func mustExecute(t *testing.T, cmd *cobra.Command, args ...string) string {
|
||||
t.Helper()
|
||||
|
||||
var out bytes.Buffer
|
||||
|
||||
cmd.SetOut(&out)
|
||||
cmd.SetArgs(args)
|
||||
|
||||
err := cmd.Execute()
|
||||
if err != nil {
|
||||
t.Fatalf("%s failed: %v", cmd.Name(), err)
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// TestVersionQuietSuppressesReport checks that --quiet silences the whole
|
||||
// version report: it is human-facing output, not a scriptable value.
|
||||
//
|
||||
//nolint:paralleltest // mutates the process-global rootFlags
|
||||
func TestVersionQuietSuppressesReport(t *testing.T) {
|
||||
setRootFlags(t, RootFlags{Quiet: true})
|
||||
|
||||
out := mustExecute(t, NewVersionCommand())
|
||||
|
||||
if out != "" {
|
||||
t.Errorf("--quiet version printed %q, want nothing", out)
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfigGetIgnoresQuiet checks that a config value is printed even
|
||||
// under --quiet: it is scriptable output a caller depends on, so --quiet
|
||||
// must not suppress it, and it stays machine-plain (no marker, no color).
|
||||
//
|
||||
//nolint:paralleltest // mutates the process-global rootFlags
|
||||
func TestConfigGetIgnoresQuiet(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := seedFile(t, dir, "config.yml", "storage_url: file:///mnt/x\n")
|
||||
|
||||
setRootFlags(t, RootFlags{Quiet: true, ConfigPath: path})
|
||||
|
||||
out := mustExecute(t, newConfigGetCommand(), "storage_url")
|
||||
|
||||
if out != "file:///mnt/x\n" {
|
||||
t.Errorf("config get --quiet = %q, want the plain value", out)
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfigSetQuietSuppressesConfirmation checks that --quiet silences
|
||||
// the confirmation line while still writing the value to the file.
|
||||
//
|
||||
//nolint:paralleltest // mutates the process-global rootFlags
|
||||
func TestConfigSetQuietSuppressesConfirmation(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := seedFile(t, dir, "config.yml", "compression_level: 3\n")
|
||||
|
||||
setRootFlags(t, RootFlags{Quiet: true, ConfigPath: path})
|
||||
|
||||
out := mustExecute(t, newConfigSetCommand(), "compression_level", "9")
|
||||
|
||||
if out != "" {
|
||||
t.Errorf("--quiet config set printed %q, want nothing", out)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path) //nolint:gosec // G304: test-controlled path
|
||||
if err != nil {
|
||||
t.Fatalf("reading config back: %v", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(string(data), "compression_level: 9") {
|
||||
t.Errorf("config set did not write the value under --quiet:\n%s", data)
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfigSetConfirmsWhenNotQuiet checks that the confirmation names
|
||||
// the key (styled) when --quiet is not set.
|
||||
//
|
||||
//nolint:paralleltest // mutates the process-global rootFlags
|
||||
func TestConfigSetConfirmsWhenNotQuiet(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := seedFile(t, dir, "config.yml", "compression_level: 3\n")
|
||||
|
||||
setRootFlags(t, RootFlags{ConfigPath: path})
|
||||
|
||||
out := mustExecute(t, newConfigSetCommand(), "compression_level", "9")
|
||||
|
||||
if !strings.Contains(out, "compression_level") {
|
||||
t.Errorf("config set did not confirm the key: %q", out)
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfigInitQuietSuppressesConfirmation checks that --quiet silences
|
||||
// the "config written" confirmation while still writing the file.
|
||||
//
|
||||
//nolint:paralleltest // mutates the process-global rootFlags
|
||||
func TestConfigInitQuietSuppressesConfirmation(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "new-config.yml")
|
||||
|
||||
setRootFlags(t, RootFlags{Quiet: true, ConfigPath: path})
|
||||
|
||||
out := mustExecute(t, newConfigInitCommand())
|
||||
|
||||
if out != "" {
|
||||
t.Errorf("--quiet config init printed %q, want nothing", out)
|
||||
}
|
||||
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
t.Errorf("config init did not write the file under --quiet: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// seedDatabaseDeleteConfig writes a valid config whose index_path is a
|
||||
// seeded database file, and returns both paths.
|
||||
func seedDatabaseDeleteConfig(t *testing.T, dir string) (string, string) {
|
||||
t.Helper()
|
||||
|
||||
dbPath := seedFile(t, dir, "index.sqlite", "not-a-real-db")
|
||||
cfg := fmt.Sprintf(hermeticConfig,
|
||||
filepath.Join(dir, "source"), filepath.Join(dir, "store"), dbPath)
|
||||
cfgPath := seedFile(t, dir, "config.yml", cfg)
|
||||
|
||||
return dbPath, cfgPath
|
||||
}
|
||||
|
||||
// TestDatabaseDeleteQuietSuppressesMessage checks that --quiet silences
|
||||
// the "database deleted" line while still removing the file.
|
||||
//
|
||||
//nolint:paralleltest // mutates the process-global rootFlags
|
||||
func TestDatabaseDeleteQuietSuppressesMessage(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
dbPath, cfgPath := seedDatabaseDeleteConfig(t, dir)
|
||||
|
||||
setRootFlags(t, RootFlags{Quiet: true, ConfigPath: cfgPath})
|
||||
|
||||
out := mustExecute(t, newDatabaseDeleteCommand(), "--force")
|
||||
|
||||
if out != "" {
|
||||
t.Errorf("--quiet database delete printed %q, want nothing", out)
|
||||
}
|
||||
|
||||
_, err := os.Stat(dbPath)
|
||||
if !os.IsNotExist(err) {
|
||||
t.Errorf("database delete did not remove the file: stat err = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseDeleteReportsWhenNotQuiet checks that the deletion is
|
||||
// reported when --quiet is not set.
|
||||
//
|
||||
//nolint:paralleltest // mutates the process-global rootFlags
|
||||
func TestDatabaseDeleteReportsWhenNotQuiet(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
_, cfgPath := seedDatabaseDeleteConfig(t, dir)
|
||||
|
||||
setRootFlags(t, RootFlags{ConfigPath: cfgPath})
|
||||
|
||||
out := mustExecute(t, newDatabaseDeleteCommand(), "--force")
|
||||
|
||||
if !strings.Contains(out, "deleted") {
|
||||
t.Errorf("database delete did not report the deletion: %q", out)
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/spf13/cobra"
|
||||
"sneak.berlin/go/vaultik/internal/ui"
|
||||
)
|
||||
|
||||
// errConfigNotFound is wrapped by all config-resolution failures.
|
||||
@@ -81,6 +82,20 @@ func GetRootFlags() RootFlags {
|
||||
return rootFlags
|
||||
}
|
||||
|
||||
// commandUI returns a UI writer for a command's stdout, in quiet mode
|
||||
// when the global --quiet flag is set. This is how the pure-cli
|
||||
// commands (version, config, database) reach internal/ui: color follows
|
||||
// the writer (a TTY gets color, a captured test buffer does not), and
|
||||
// --quiet silences the same message classes it silences everywhere else.
|
||||
func commandUI(cmd *cobra.Command) *ui.Writer {
|
||||
w := ui.New(cmd.OutOrStdout())
|
||||
if GetRootFlags().Quiet {
|
||||
w.SetQuiet(true)
|
||||
}
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
// ResolveConfigPath resolves the config file path from flags, environment, or default.
|
||||
// Search order: --config flag, VAULTIK_CONFIG env, XDG config dir,
|
||||
// /etc/vaultik/config.yml.
|
||||
|
||||
+14
-5
@@ -2,11 +2,11 @@ package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"sneak.berlin/go/vaultik/internal/globals"
|
||||
"sneak.berlin/go/vaultik/internal/ui"
|
||||
)
|
||||
|
||||
// NewVersionCommand creates the version command
|
||||
@@ -17,16 +17,25 @@ func NewVersionCommand() *cobra.Command {
|
||||
Long: `Print version, git commit, and build information for vaultik.`,
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, _ []string) {
|
||||
writeVersion(cmd.OutOrStdout())
|
||||
writeVersion(commandUI(cmd))
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// writeVersion prints the version report. It takes a writer rather than
|
||||
// using os.Stdout directly so the output can be asserted on in tests.
|
||||
func writeVersion(w io.Writer) {
|
||||
// writeVersion prints the version report through the UI writer. The
|
||||
// report is the output this command exists to produce, so it is written
|
||||
// plain (markers would corrupt the aligned report) via the writer's
|
||||
// underlying stdout; --quiet silences it like any other non-error
|
||||
// output.
|
||||
func writeVersion(out *ui.Writer) {
|
||||
if out.Quiet() {
|
||||
return
|
||||
}
|
||||
|
||||
w := out.Out()
|
||||
|
||||
_, _ = fmt.Fprintf(w, "vaultik %s\n", globals.Version)
|
||||
_, _ = fmt.Fprintf(w, " commit: %s\n", globals.Commit)
|
||||
_, _ = fmt.Fprintf(w, " build date: %s\n", globals.CommitDate)
|
||||
|
||||
@@ -34,9 +34,9 @@ func runVersionCommand(t *testing.T) string {
|
||||
// the report is the version the binary was actually built with. The
|
||||
// test binary carries no -ldflags, so that is the "dev" default -- the
|
||||
// same string an untagged `make vaultik` build stamps a prefix of.
|
||||
//
|
||||
//nolint:paralleltest // executes a command that reads the global rootFlags
|
||||
func TestVersionCommandReportsBuildVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
out := runVersionCommand(t)
|
||||
|
||||
wantFirst := "vaultik " + globals.Version
|
||||
@@ -55,9 +55,9 @@ func TestVersionCommandReportsBuildVersion(t *testing.T) {
|
||||
// being exactly "dev", so once untagged builds started carrying their
|
||||
// commit sha it would have gone silent and an unreleased binary would
|
||||
// have looked like a release.
|
||||
//
|
||||
//nolint:paralleltest // executes a command that reads the global rootFlags
|
||||
func TestVersionCommandFlagsDevelopmentBuild(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if !globals.IsDevVersion(globals.Version) {
|
||||
t.Skipf("test binary was stamped with release version %q",
|
||||
globals.Version)
|
||||
|
||||
Reference in New Issue
Block a user