package cli //nolint:testpackage // shares the fixtures and capture helpers import ( "encoding/json" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // errorLinePrefix starts the final line that reports a failed command. const errorLinePrefix = "ERROR: " // TestEntryFailureIsPrintedOnce runs commands that fail after startup. // stderr must carry the error once, as the final ERROR: line. A log // record carrying the same error appears only under --verbose. // //nolint:paralleltest // replaces os.Args, os.Stdout, os.Stderr and the xdg globals func TestEntryFailureIsPrintedOnce(t *testing.T) { for _, testCase := range []struct { name string args []string }{ {name: "snapshot create", args: []string{cmdSnapshot, cmdCreate}}, {name: "remote info", args: []string{cmdRemote, cmdInfo}}, {name: "prune", args: []string{cmdPrune, flagJSON}}, } { for _, verbose := range []bool{false, true} { name := testCase.name if verbose { name += " verbose" } t.Run(name, func(t *testing.T) { args := append([]string{flagConfig, writeUnusableDestinationConfig(t)}, testCase.args...) if verbose { args = append(args, "--verbose") } code, _, stderr := runEntry(t, args...) require.Equal(t, 1, code) errorLine, records := failureOutput(t, stderr) if !verbose { assert.Zero(t, records, "without --verbose no log record repeats the error:\n%s", stderr) return } assert.NotZero(t, records, "--verbose must keep the log record of %q", errorLine) }) } } } // failureOutput requires stderr to hold exactly one ERROR: line and // returns it, with the number of log records whose error attribute is // the error that line reports. stderr is a pipe here, so every log // record is a JSON object on its own line. func failureOutput(t *testing.T, stderr string) (string, int) { t.Helper() var ( errorLines []string records []map[string]any ) for line := range strings.Lines(stderr) { var record map[string]any if json.Unmarshal([]byte(line), &record) == nil { records = append(records, record) continue } if strings.Contains(line, errorLinePrefix) { errorLines = append(errorLines, strings.TrimSpace(line)) } } require.Len(t, errorLines, 1, "the error must be printed once as an ERROR: line:\n%s", stderr) // An error can run over several lines; the ERROR: line holds the // first of them. repeats := 0 for _, record := range records { failure, ok := record["error"].(string) firstLine, _, _ := strings.Cut(failure, "\n") if ok && strings.Contains(errorLines[0], firstLine) { repeats++ } } return errorLines[0], repeats }