Skip to content

Commit 21cefd7

Browse files
committed
feat: print stats to stderr instead of stdout
Signed-off-by: Brian McGee <brian@bmcgee.ie> diff --git a/cmd/format/format.go b/cmd/format/format.go index 7c053d5..041a28d 100644 --- a/cmd/format/format.go +++ b/cmd/format/format.go @@ -197,9 +197,9 @@ func Run(v *viper.Viper, statz *stats.Stats, cmd *cobra.Command, paths []string) return fmt.Errorf("failed to close walker: %w", err) } - // print stats to stdout, unless we are processing from stdin and therefore outputting the results to stdout - if !cfg.Stdin { - statz.Print() + // print stats to stderr + if !cfg.Quiet { + statz.PrintToStderr() } if formatErr != nil { diff --git a/cmd/root_test.go b/cmd/root_test.go index 9d42266..5707ec5 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -74,7 +74,7 @@ func TestOnUnmatched(t *testing.T) { // default is WARN t.Run("default", func(t *testing.T) { - treefmt(t, withNoError(t), withOutput(checkOutput(log.WarnLevel))) + treefmt(t, withNoError(t), withStderr(checkOutput(log.WarnLevel))) }) // should exit with error when using fatal @@ -99,7 +99,7 @@ func TestOnUnmatched(t *testing.T) { treefmt(t, withArgs("-vv", "--on-unmatched", levelStr), withNoError(t), - withOutput(checkOutput(level)), + withStderr(checkOutput(level)), ) t.Setenv("TREEFMT_ON_UNMATCHED", levelStr) @@ -107,7 +107,7 @@ func TestOnUnmatched(t *testing.T) { treefmt(t, withArgs("-vv"), withNoError(t), - withOutput(checkOutput(level)), + withStderr(checkOutput(level)), ) }) } @@ -1583,7 +1583,7 @@ func TestStdin(t *testing.T) { withError(func(err error) { as.EqualError(err, "exactly one path should be specified when using the --stdin flag") }), - withOutput(func(out []byte) { + withStderr(func(out []byte) { as.Equal("Error: exactly one path should be specified when using the --stdin flag\n", string(out)) }), ) @@ -1600,7 +1600,7 @@ func TestStdin(t *testing.T) { stats.Formatted: 1, stats.Changed: 1, }), - withOutput(func(out []byte) { + withStdout(func(out []byte) { as.Equal(`{ ...}: "hello" `, string(out)) }), @@ -1616,7 +1616,7 @@ func TestStdin(t *testing.T) { withError(func(err error) { as.Errorf(err, "path ../test.nix not inside the tree root %s", tempDir) }), - withOutput(func(out []byte) { + withStderr(func(out []byte) { as.Contains(string(out), "Error: path ../test.nix not inside the tree root") }), ) @@ -1639,7 +1639,7 @@ func TestStdin(t *testing.T) { stats.Formatted: 1, stats.Changed: 1, }), - withOutput(func(out []byte) { + withStdout(func(out []byte) { as.Equal(`| col1 | col2 | | ------ | --------- | | nice | fits | @@ -1806,7 +1806,9 @@ type options struct { value *config.Config } - assertOut func([]byte) + assertStdout func([]byte) + assertStderr func([]byte) + assertError func(error) assertStats func(*stats.Stats) @@ -1873,9 +1875,15 @@ func withNoError(t *testing.T) option { } } -func withOutput(fn func([]byte)) option { +func withStdout(fn func([]byte)) option { + return func(o *options) { + o.assertStdout = fn + } +} + +func withStderr(fn func([]byte)) option { return func(o *options) { - o.assertOut = fn + o.assertStderr = fn } } @@ -1931,17 +1939,19 @@ func treefmt( t.Logf("treefmt %s", strings.Join(args, " ")) tempDir := t.TempDir() - tempOut := test.TempFile(t, tempDir, "combined_output", nil) + + tempStdout := test.TempFile(t, tempDir, "stdout", nil) + tempStderr := test.TempFile(t, tempDir, "stderr", nil) // capture standard outputs before swapping them stdout := os.Stdout stderr := os.Stderr // swap them temporarily - os.Stdout = tempOut - os.Stderr = tempOut + os.Stdout = tempStdout + os.Stderr = tempStderr - log.SetOutput(tempOut) + log.SetOutput(tempStdout) defer func() { // swap outputs back @@ -1954,30 +1964,49 @@ func treefmt( root, statz := cmd.NewRoot() root.SetArgs(args) - root.SetOut(tempOut) - root.SetErr(tempOut) + root.SetOut(tempStdout) + root.SetErr(tempStderr) // execute the command cmdErr := root.Execute() - // reset and read the temporary output - if _, resetErr := tempOut.Seek(0, 0); resetErr != nil { + // reset and read the temporary outputs + if _, resetErr := tempStdout.Seek(0, 0); resetErr != nil { t.Fatal(fmt.Errorf("failed to reset temp output for reading: %w", resetErr)) } - out, readErr := io.ReadAll(tempOut) + if _, resetErr := tempStderr.Seek(0, 0); resetErr != nil { + t.Fatal(fmt.Errorf("failed to reset temp output for reading: %w", resetErr)) + } + + // read back stderr and validate + out, readErr := io.ReadAll(tempStderr) if readErr != nil { - t.Fatal(fmt.Errorf("failed to read temp output: %w", readErr)) + t.Fatal(fmt.Errorf("failed to read temp stderr: %w", readErr)) + } + + if opts.assertStderr != nil { + opts.assertStderr(out) } t.Log("\n" + string(out)) - if opts.assertStats != nil { - opts.assertStats(statz) + // read back stdout and validate + out, readErr = io.ReadAll(tempStdout) + if readErr != nil { + t.Fatal(fmt.Errorf("failed to read temp stdout: %w", readErr)) + } + + t.Log("\n" + string(out)) + + if opts.assertStdout != nil { + opts.assertStdout(out) } - if opts.assertOut != nil { - opts.assertOut(out) + // assert other properties + + if opts.assertStats != nil { + opts.assertStats(statz) } if opts.assertError != nil { diff --git a/stats/stats.go b/stats/stats.go index 70c174f..7c00efd 100644 --- a/stats/stats.go +++ b/stats/stats.go @@ -2,6 +2,7 @@ package stats import ( "fmt" + "os" "strings" "sync/atomic" "time" @@ -34,7 +35,7 @@ func (s *Stats) Elapsed() time.Duration { return time.Since(s.start) } -func (s *Stats) Print() { +func (s *Stats) PrintToStderr() { components := []string{ "traversed %d files", "emitted %d files for processing", @@ -42,7 +43,8 @@ func (s *Stats) Print() { "", } - fmt.Printf( + _, _ = fmt.Fprintf( + os.Stderr, strings.Join(components, "\n"), s.Value(Traversed), s.Value(Matched),
1 parent d182c6d commit 21cefd7

File tree

3 files changed

+60
-29
lines changed

3 files changed

+60
-29
lines changed

cmd/format/format.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,9 @@ func Run(v *viper.Viper, statz *stats.Stats, cmd *cobra.Command, paths []string)
197197
return fmt.Errorf("failed to close walker: %w", err)
198198
}
199199

200-
// print stats to stdout, unless we are processing from stdin and therefore outputting the results to stdout
201-
if !cfg.Stdin {
202-
statz.Print()
200+
// print stats to stderr
201+
if !cfg.Quiet {
202+
statz.PrintToStderr()
203203
}
204204

205205
if formatErr != nil {

cmd/root_test.go

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func TestOnUnmatched(t *testing.T) {
7474

7575
// default is WARN
7676
t.Run("default", func(t *testing.T) {
77-
treefmt(t, withNoError(t), withOutput(checkOutput(log.WarnLevel)))
77+
treefmt(t, withNoError(t), withStderr(checkOutput(log.WarnLevel)))
7878
})
7979

8080
// should exit with error when using fatal
@@ -99,15 +99,15 @@ func TestOnUnmatched(t *testing.T) {
9999
treefmt(t,
100100
withArgs("-vv", "--on-unmatched", levelStr),
101101
withNoError(t),
102-
withOutput(checkOutput(level)),
102+
withStderr(checkOutput(level)),
103103
)
104104

105105
t.Setenv("TREEFMT_ON_UNMATCHED", levelStr)
106106

107107
treefmt(t,
108108
withArgs("-vv"),
109109
withNoError(t),
110-
withOutput(checkOutput(level)),
110+
withStderr(checkOutput(level)),
111111
)
112112
})
113113
}
@@ -1583,7 +1583,7 @@ func TestStdin(t *testing.T) {
15831583
withError(func(err error) {
15841584
as.EqualError(err, "exactly one path should be specified when using the --stdin flag")
15851585
}),
1586-
withOutput(func(out []byte) {
1586+
withStderr(func(out []byte) {
15871587
as.Equal("Error: exactly one path should be specified when using the --stdin flag\n", string(out))
15881588
}),
15891589
)
@@ -1600,7 +1600,7 @@ func TestStdin(t *testing.T) {
16001600
stats.Formatted: 1,
16011601
stats.Changed: 1,
16021602
}),
1603-
withOutput(func(out []byte) {
1603+
withStdout(func(out []byte) {
16041604
as.Equal(`{ ...}: "hello"
16051605
`, string(out))
16061606
}),
@@ -1616,7 +1616,7 @@ func TestStdin(t *testing.T) {
16161616
withError(func(err error) {
16171617
as.Errorf(err, "path ../test.nix not inside the tree root %s", tempDir)
16181618
}),
1619-
withOutput(func(out []byte) {
1619+
withStderr(func(out []byte) {
16201620
as.Contains(string(out), "Error: path ../test.nix not inside the tree root")
16211621
}),
16221622
)
@@ -1639,7 +1639,7 @@ func TestStdin(t *testing.T) {
16391639
stats.Formatted: 1,
16401640
stats.Changed: 1,
16411641
}),
1642-
withOutput(func(out []byte) {
1642+
withStdout(func(out []byte) {
16431643
as.Equal(`| col1 | col2 |
16441644
| ------ | --------- |
16451645
| nice | fits |
@@ -1806,7 +1806,9 @@ type options struct {
18061806
value *config.Config
18071807
}
18081808

1809-
assertOut func([]byte)
1809+
assertStdout func([]byte)
1810+
assertStderr func([]byte)
1811+
18101812
assertError func(error)
18111813
assertStats func(*stats.Stats)
18121814

@@ -1873,9 +1875,15 @@ func withNoError(t *testing.T) option {
18731875
}
18741876
}
18751877

1876-
func withOutput(fn func([]byte)) option {
1878+
func withStdout(fn func([]byte)) option {
1879+
return func(o *options) {
1880+
o.assertStdout = fn
1881+
}
1882+
}
1883+
1884+
func withStderr(fn func([]byte)) option {
18771885
return func(o *options) {
1878-
o.assertOut = fn
1886+
o.assertStderr = fn
18791887
}
18801888
}
18811889

@@ -1931,17 +1939,19 @@ func treefmt(
19311939
t.Logf("treefmt %s", strings.Join(args, " "))
19321940

19331941
tempDir := t.TempDir()
1934-
tempOut := test.TempFile(t, tempDir, "combined_output", nil)
1942+
1943+
tempStdout := test.TempFile(t, tempDir, "stdout", nil)
1944+
tempStderr := test.TempFile(t, tempDir, "stderr", nil)
19351945

19361946
// capture standard outputs before swapping them
19371947
stdout := os.Stdout
19381948
stderr := os.Stderr
19391949

19401950
// swap them temporarily
1941-
os.Stdout = tempOut
1942-
os.Stderr = tempOut
1951+
os.Stdout = tempStdout
1952+
os.Stderr = tempStderr
19431953

1944-
log.SetOutput(tempOut)
1954+
log.SetOutput(tempStdout)
19451955

19461956
defer func() {
19471957
// swap outputs back
@@ -1954,30 +1964,49 @@ func treefmt(
19541964
root, statz := cmd.NewRoot()
19551965

19561966
root.SetArgs(args)
1957-
root.SetOut(tempOut)
1958-
root.SetErr(tempOut)
1967+
root.SetOut(tempStdout)
1968+
root.SetErr(tempStderr)
19591969

19601970
// execute the command
19611971
cmdErr := root.Execute()
19621972

1963-
// reset and read the temporary output
1964-
if _, resetErr := tempOut.Seek(0, 0); resetErr != nil {
1973+
// reset and read the temporary outputs
1974+
if _, resetErr := tempStdout.Seek(0, 0); resetErr != nil {
19651975
t.Fatal(fmt.Errorf("failed to reset temp output for reading: %w", resetErr))
19661976
}
19671977

1968-
out, readErr := io.ReadAll(tempOut)
1978+
if _, resetErr := tempStderr.Seek(0, 0); resetErr != nil {
1979+
t.Fatal(fmt.Errorf("failed to reset temp output for reading: %w", resetErr))
1980+
}
1981+
1982+
// read back stderr and validate
1983+
out, readErr := io.ReadAll(tempStderr)
19691984
if readErr != nil {
1970-
t.Fatal(fmt.Errorf("failed to read temp output: %w", readErr))
1985+
t.Fatal(fmt.Errorf("failed to read temp stderr: %w", readErr))
1986+
}
1987+
1988+
if opts.assertStderr != nil {
1989+
opts.assertStderr(out)
19711990
}
19721991

19731992
t.Log("\n" + string(out))
19741993

1975-
if opts.assertStats != nil {
1976-
opts.assertStats(statz)
1994+
// read back stdout and validate
1995+
out, readErr = io.ReadAll(tempStdout)
1996+
if readErr != nil {
1997+
t.Fatal(fmt.Errorf("failed to read temp stdout: %w", readErr))
1998+
}
1999+
2000+
t.Log("\n" + string(out))
2001+
2002+
if opts.assertStdout != nil {
2003+
opts.assertStdout(out)
19772004
}
19782005

1979-
if opts.assertOut != nil {
1980-
opts.assertOut(out)
2006+
// assert other properties
2007+
2008+
if opts.assertStats != nil {
2009+
opts.assertStats(statz)
19812010
}
19822011

19832012
if opts.assertError != nil {

stats/stats.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package stats
22

33
import (
44
"fmt"
5+
"os"
56
"strings"
67
"sync/atomic"
78
"time"
@@ -34,15 +35,16 @@ func (s *Stats) Elapsed() time.Duration {
3435
return time.Since(s.start)
3536
}
3637

37-
func (s *Stats) Print() {
38+
func (s *Stats) PrintToStderr() {
3839
components := []string{
3940
"traversed %d files",
4041
"emitted %d files for processing",
4142
"formatted %d files (%d changed) in %v",
4243
"",
4344
}
4445

45-
fmt.Printf(
46+
_, _ = fmt.Fprintf(
47+
os.Stderr,
4648
strings.Join(components, "\n"),
4749
s.Value(Traversed),
4850
s.Value(Matched),

0 commit comments

Comments
 (0)