Skip to content

Commit

Permalink
Modify --follow behavior by minimizing noisy output (#122)
Browse files Browse the repository at this point in the history
  • Loading branch information
mfridman authored Jun 21, 2024
1 parent 6ab1958 commit 5e3b302
Show file tree
Hide file tree
Showing 22 changed files with 593 additions and 136 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

- Modify `--follow` behavior by minimizing noisy output. (#122)

> [!TIP]
> If you want the existing behavior, I added a `--follow-verbose` flag. But please do let me know if this affected you, as I plan to remove this before cutting a `v1.0.0`. Thank you!
## [v0.13.3]

- General housekeeping and dependency updates.
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,7 @@ build:
search-todo:
@echo "Searching for TODOs in Go files..."
@rg '// TODO\(mf\):' --glob '*.go' || echo "No TODOs found."

.PHONY: clean
clean:
@find . -type f -name '*.FAIL' -delete
5 changes: 4 additions & 1 deletion internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ type Options struct {
SummaryTableOptions SummaryTableOptions

// FollowOutput will follow the raw output as go test is running.
FollowOutput bool
FollowOutput bool
FollowOutputVerbose bool

// Progress will print a single summary line for each package once the package has completed.
// Useful for long running test suites. Maybe used with FollowOutput or on its own.
Progress bool
Expand Down Expand Up @@ -59,6 +61,7 @@ func Run(w io.Writer, option Options) (int, error) {
summary, err := parse.Process(
reader,
parse.WithFollowOutput(option.FollowOutput),
parse.WithFollowVersboseOutput(option.FollowOutputVerbose),
parse.WithWriter(w),
parse.WithProgress(option.Progress),
)
Expand Down
9 changes: 6 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ var (
sortPtr = flag.String("sort", "name", "")
progressPtr = flag.Bool("progress", false, "")
comparePtr = flag.String("compare", "", "")
// Undocumented flags
followVerbosePtr = flag.Bool("follow-verbose", false, "")

// Legacy flags
noBordersPtr = flag.Bool("noborders", false, "")
Expand Down Expand Up @@ -117,9 +119,10 @@ func main() {
disableColor = true
}
options := app.Options{
DisableColor: disableColor,
FollowOutput: *followPtr,
FileName: *fileNamePtr,
DisableColor: disableColor,
FollowOutput: *followPtr,
FollowOutputVerbose: *followVerbosePtr,
FileName: *fileNamePtr,
TestTableOptions: app.TestTableOptions{
Pass: *passPtr,
Skip: *skipPtr,
Expand Down
37 changes: 34 additions & 3 deletions parse/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,43 @@ func (e *Event) DiscardEmptyTestOutput() bool {
return e.Action == ActionOutput && e.Test == ""
}

// Prefixes for the different types of test updates. See:
//
// https://github.com/golang/go/blob/38cfb3be9d486833456276777155980d1ec0823e/src/cmd/internal/test2json/test2json.go#L160-L168
const (
updatePrefixRun = "=== RUN "
updatePrefixPause = "=== PAUSE "
updatePrefixCont = "=== CONT "
updatePrefixName = "=== NAME "
updatePrefixPass = "=== PASS "
updatePrefixFail = "=== FAIL "
updatePrefixSkip = "=== SKIP "
)

var updates = []string{
"=== RUN ",
"=== PAUSE ",
"=== CONT ",
updatePrefixRun,
updatePrefixPause,
updatePrefixCont,
}

// Prefix for the different types of test results. See
//
// https://github.com/golang/go/blob/38cfb3be9d486833456276777155980d1ec0823e/src/cmd/internal/test2json/test2json.go#L170-L175
const (
resultPrefixPass = "--- PASS: "
resultPrefixFail = "--- FAIL: "
resultPrefixSkip = "--- SKIP: "
resultPrefixBench = "--- BENCH: "
)

// BigResult reports whether the test output is a big pass or big fail
//
// https://github.com/golang/go/blob/38cfb3be9d486833456276777155980d1ec0823e/src/cmd/internal/test2json/test2json.go#L146-L150
const (
bigPass = "PASS"
bigFail = "FAIL"
)

// Let's try using the LastLine method to report the package result.
// If there are issues with LastLine() we can switch to this method.
//
Expand Down
35 changes: 34 additions & 1 deletion parse/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,36 @@ func Process(r io.Reader, optionsFunc ...OptionsFunc) (*GoTestSummary, error) {
Packages: make(map[string]*Package),
}

noisy := []string{
// 1. Filter out noisy output, such as === RUN, === PAUSE, etc.
updatePrefixRun,
updatePrefixPause,
updatePrefixCont,
updatePrefixPass,
updatePrefixSkip,
// 2. Filter out report output, such as --- PASS: and --- SKIP:
resultPrefixPass,
resultPrefixSkip,
}
isNoisy := func(e *Event) bool {
output := strings.TrimSpace(e.Output)
// If the event is a big pass or fail, we can safely discard it. These are typically the
// lines preceding the package summary line. For example:
//
// PASS
// ok fmt 0.144s
if e.Test == "" && (output == bigPass || output == bigFail) {
return true
}
for _, prefix := range noisy {
if strings.HasPrefix(output, prefix) {
return true
}
}
return false

}

sc := bufio.NewScanner(r)
var started bool
var badLines int
Expand Down Expand Up @@ -70,7 +100,10 @@ func Process(r io.Reader, optionsFunc ...OptionsFunc) (*GoTestSummary, error) {

// Optionally, as test output is piped to us, we write the plain
// text Output as if go test was run without the -json flag.
if option.follow && option.w != nil {
if (option.follow || option.followVerbose) && option.w != nil {
if !option.followVerbose && isNoisy(e) {
continue
}
fmt.Fprint(option.w, e.Output)
}
// Progress is a special case of follow, where we only print the
Expand Down
13 changes: 9 additions & 4 deletions parse/process_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import (
)

type options struct {
w io.Writer
follow bool
debug bool
progress bool
w io.Writer
follow bool
followVerbose bool
debug bool
progress bool
}

type OptionsFunc func(o *options)
Expand All @@ -17,6 +18,10 @@ func WithFollowOutput(b bool) OptionsFunc {
return func(o *options) { o.follow = b }
}

func WithFollowVersboseOutput(b bool) OptionsFunc {
return func(o *options) { o.followVerbose = b }
}

func WithWriter(w io.Writer) OptionsFunc {
return func(o *options) { o.w = w }
}
Expand Down
132 changes: 89 additions & 43 deletions tests/follow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,51 +15,97 @@ import (
func TestFollow(t *testing.T) {
t.Parallel()

base := filepath.Join("testdata", "follow")
t.Run("follow_verbose", func(t *testing.T) {
base := filepath.Join("testdata", "follow-verbose")

tt := []struct {
fileName string
err error
exitCode int
}{
// race detected
{"test_01", nil, 1},
{"test_02", nil, 0},
{"test_03", nil, 0},
{"test_04", nil, 0},
{"test_05", parse.ErrNotParsable, 1},
// build failure in one package
{"test_06", nil, 2},
}
for _, tc := range tt {
t.Run(tc.fileName, func(t *testing.T) {
inputFile := filepath.Join(base, tc.fileName+".jsonl")
options := app.Options{
FileName: inputFile,
FollowOutput: true,
DisableTableOutput: true,
}
var buf bytes.Buffer
gotExitCode, err := app.Run(&buf, options)
if err != nil && !errors.Is(err, tc.err) {
t.Fatal(err)
}
check.Number(t, gotExitCode, tc.exitCode)
goldenFile := filepath.Join(base, tc.fileName+".golden")
want, err := os.ReadFile(goldenFile)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(buf.Bytes(), want) {
t.Error("input does not match expected output; diff files in follow dir suffixed with .FAIL to debug")
t.Logf("diff %v %v",
"tests/"+goldenFile+".FAIL",
"tests/"+inputFile+".FAIL",
)
if err := os.WriteFile(goldenFile+".FAIL", buf.Bytes(), 0644); err != nil {
tt := []struct {
fileName string
err error
exitCode int
}{
// race detected
{"test_01", nil, 1},
{"test_02", nil, 0},
{"test_03", nil, 0},
{"test_04", nil, 0},
{"test_05", parse.ErrNotParsable, 1},
// build failure in one package
{"test_06", nil, 2},
}
for _, tc := range tt {
t.Run(tc.fileName, func(t *testing.T) {
inputFile := filepath.Join(base, tc.fileName+".jsonl")
options := app.Options{
FileName: inputFile,
FollowOutput: true,
FollowOutputVerbose: true,
DisableTableOutput: true,
}
var buf bytes.Buffer
gotExitCode, err := app.Run(&buf, options)
if err != nil && !errors.Is(err, tc.err) {
t.Fatal(err)
}
check.Number(t, gotExitCode, tc.exitCode)
goldenFile := filepath.Join(base, tc.fileName+".golden")
want, err := os.ReadFile(goldenFile)
if err != nil {
t.Fatal(err)
}
}
})
checkGolden(t, inputFile, goldenFile, buf.Bytes(), want)
})
}
})

t.Run("follow_no_verbose", func(t *testing.T) {
base := filepath.Join("testdata", "follow")

tt := []struct {
fileName string
err error
exitCode int
}{
{"test_01", nil, 0},
}
for _, tc := range tt {
t.Run(tc.fileName, func(t *testing.T) {
inputFile := filepath.Join(base, tc.fileName+".jsonl")
options := app.Options{
FileName: inputFile,
FollowOutput: true,
DisableTableOutput: true,
}
var buf bytes.Buffer
gotExitCode, err := app.Run(&buf, options)
if err != nil && !errors.Is(err, tc.err) {
t.Fatal(err)
}
check.Number(t, gotExitCode, tc.exitCode)
goldenFile := filepath.Join(base, tc.fileName+".golden")
want, err := os.ReadFile(goldenFile)
if err != nil {
t.Fatal(err)
}
checkGolden(t, inputFile, goldenFile, buf.Bytes(), want)
})
}
})
}

func checkGolden(
t *testing.T,
inputFile, goldenFile string,
got, want []byte,
) {
t.Helper()
if !bytes.Equal(got, want) {
t.Error("input does not match expected output; diff files in follow dir suffixed with .FAIL to debug")
t.Logf("diff %v %v",
"tests/"+goldenFile+".FAIL",
"tests/"+inputFile+".FAIL",
)
if err := os.WriteFile(goldenFile+".FAIL", got, 0644); err != nil {
t.Fatal(err)
}
}
}
39 changes: 39 additions & 0 deletions tests/testdata/follow-verbose/test_01.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
=== RUN TestRace
==================
WARNING: DATA RACE
Write at 0x00c000090090 by goroutine 7:
debug/tparse-24.TestRace.func1()
/Users/michael.fridman/go/src/debug/tparse-24/some_test.go:7 +0x38

Previous write at 0x00c000090090 by goroutine 6:
debug/tparse-24.TestRace()
/Users/michael.fridman/go/src/debug/tparse-24/some_test.go:8 +0x88
testing.tRunner()
/usr/local/go/src/testing/testing.go:827 +0x162

Goroutine 7 (running) created at:
debug/tparse-24.TestRace()
/Users/michael.fridman/go/src/debug/tparse-24/some_test.go:7 +0x7a
testing.tRunner()
/usr/local/go/src/testing/testing.go:827 +0x162

Goroutine 6 (running) created at:
testing.(*T).Run()
/usr/local/go/src/testing/testing.go:878 +0x650
testing.runTests.func1()
/usr/local/go/src/testing/testing.go:1119 +0xa8
testing.tRunner()
/usr/local/go/src/testing/testing.go:827 +0x162
testing.runTests()
/usr/local/go/src/testing/testing.go:1117 +0x4ee
testing.(*M).Run()
/usr/local/go/src/testing/testing.go:1034 +0x2ee
main.main()
_testmain.go:42 +0x221
==================
--- FAIL: TestRace (0.00s)
some_test.go:9: 64
testing.go:771: race detected during execution of test
FAIL
exit status 1
FAIL debug/tparse-24 0.020s
Loading

0 comments on commit 5e3b302

Please sign in to comment.