Skip to content

Commit

Permalink
feat: add flag to write go test output to a file (#134)
Browse files Browse the repository at this point in the history
  • Loading branch information
mfridman authored Nov 2, 2024
1 parent cf5a9d7 commit 5ae5274
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 37 deletions.
21 changes: 16 additions & 5 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
)

type Options struct {
// Output is used to write the final output, such as the tables, summary, etc.
Output io.Writer
// DisableColor will disable all colors.
DisableColor bool
// Format will set the output format for tables.
Expand All @@ -26,12 +28,16 @@ type Options struct {
SummaryTableOptions SummaryTableOptions

// FollowOutput will follow the raw output as go test is running.
FollowOutput bool
FollowOutput bool // Output to stdout
FollowOutputWriter io.WriteCloser // Output to a file, takes precedence over FollowOutput
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
//
// This will output to stdout.
Progress bool
ProgressOutput io.Writer

// DisableTableOutput will disable all table output. This is used for testing.
DisableTableOutput bool
Expand All @@ -44,7 +50,7 @@ type Options struct {
Compare string
}

func Run(w io.Writer, option Options) (int, error) {
func Run(option Options) (int, error) {
var reader io.ReadCloser
var err error
if option.FileName != "" {
Expand All @@ -58,12 +64,17 @@ func Run(w io.Writer, option Options) (int, error) {
}
defer reader.Close()

if option.FollowOutputWriter != nil {
defer option.FollowOutputWriter.Close()
}

summary, err := parse.Process(
reader,
parse.WithFollowOutput(option.FollowOutput),
parse.WithFollowVersboseOutput(option.FollowOutputVerbose),
parse.WithWriter(w),
parse.WithWriter(option.FollowOutputWriter),
parse.WithProgress(option.Progress),
parse.WithProgressOutput(option.ProgressOutput),
)
if err != nil {
return 1, err
Expand All @@ -74,7 +85,7 @@ func Run(w io.Writer, option Options) (int, error) {
// Useful for tests that don't need tparse table output. Very useful for testing output from
// [parse.Process]
if !option.DisableTableOutput {
display(w, summary, option)
display(option.Output, summary, option)
}
return summary.ExitCode(), nil
}
Expand Down
12 changes: 12 additions & 0 deletions internal/utils/utils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package utils

import (
"io"
"sort"
"strings"
)
Expand Down Expand Up @@ -45,3 +46,14 @@ func minimum(a, b int) int {
}
return b
}

// DiscardCloser is an io.Writer that implements io.Closer by doing nothing.
//
// https://github.com/golang/go/issues/22823
type WriteNopCloser struct {
io.Writer
}

func (WriteNopCloser) Close() error {
return nil
}
75 changes: 49 additions & 26 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,37 @@ import (
"errors"
"flag"
"fmt"
"io"
"log"
"os"

"github.com/mfridman/buildversion"
"github.com/mfridman/tparse/internal/app"
"github.com/mfridman/tparse/internal/utils"
"github.com/mfridman/tparse/parse"
)

// Flags.
var (
vPtr = flag.Bool("v", false, "")
versionPtr = flag.Bool("version", false, "")
hPtr = flag.Bool("h", false, "")
helpPtr = flag.Bool("help", false, "")
allPtr = flag.Bool("all", false, "")
passPtr = flag.Bool("pass", false, "")
skipPtr = flag.Bool("skip", false, "")
showNoTestsPtr = flag.Bool("notests", false, "")
smallScreenPtr = flag.Bool("smallscreen", false, "")
noColorPtr = flag.Bool("nocolor", false, "")
slowPtr = flag.Int("slow", 0, "")
fileNamePtr = flag.String("file", "", "")
formatPtr = flag.String("format", "", "")
followPtr = flag.Bool("follow", false, "")
sortPtr = flag.String("sort", "name", "")
progressPtr = flag.Bool("progress", false, "")
comparePtr = flag.String("compare", "", "")
trimPathPtr = flag.String("trimpath", "", "")
vPtr = flag.Bool("v", false, "")
versionPtr = flag.Bool("version", false, "")
hPtr = flag.Bool("h", false, "")
helpPtr = flag.Bool("help", false, "")
allPtr = flag.Bool("all", false, "")
passPtr = flag.Bool("pass", false, "")
skipPtr = flag.Bool("skip", false, "")
showNoTestsPtr = flag.Bool("notests", false, "")
smallScreenPtr = flag.Bool("smallscreen", false, "")
noColorPtr = flag.Bool("nocolor", false, "")
slowPtr = flag.Int("slow", 0, "")
fileNamePtr = flag.String("file", "", "")
formatPtr = flag.String("format", "", "")
followPtr = flag.Bool("follow", false, "")
followOutputPtr = flag.String("follow-output", "", "")
sortPtr = flag.String("sort", "name", "")
progressPtr = flag.Bool("progress", false, "")
comparePtr = flag.String("compare", "", "")
trimPathPtr = flag.String("trimpath", "", "")
// Undocumented flags
followVerbosePtr = flag.Bool("follow-verbose", false, "")

Expand All @@ -57,7 +60,8 @@ Options:
-nocolor Disable all colors. (NO_COLOR also supported)
-format The output format for tables [basic, plain, markdown]. Default is basic.
-file Read test output from a file.
-follow Follow raw output as go test is running.
-follow Follow raw output from go test to stdout.
-follow-output Write raw output from go test to a file (takes precedence over -follow).
-progress Print a single summary line for each package. Useful for long running test suites.
-compare Compare against a previous test output file. (experimental)
-trimpath Remove path prefix from package names in output, simplifying their display.
Expand Down Expand Up @@ -95,7 +99,7 @@ func main() {
format = app.OutputFormatPlain
}
default:
fmt.Fprintf(os.Stderr, "invalid option:%q. The -format flag must be one of: basic, plain or markdown", *formatPtr)
fmt.Fprintf(os.Stderr, "invalid option:%q. The -format flag must be one of: basic, plain or markdown\n", *formatPtr)
return
}
var sorter parse.PackageSorter
Expand All @@ -120,10 +124,28 @@ func main() {
if _, ok := os.LookupEnv("NO_COLOR"); ok || *noColorPtr {
disableColor = true
}

var followOutput io.WriteCloser
switch {
case *followOutputPtr != "":
var err error
followOutput, err = os.Create(*followOutputPtr)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
case *followPtr:
followOutput = os.Stdout
default:
// If no follow flags are set, we should not write to followOutput.
followOutput = utils.WriteNopCloser{Writer: io.Discard}
}
// TODO(mf): we should marry the options with the flags to avoid having to do this.
options := app.Options{
Output: os.Stdout,
DisableColor: disableColor,
FollowOutput: *followPtr,
FollowOutputWriter: followOutput,
FollowOutputVerbose: *followVerbosePtr,
FileName: *fileNamePtr,
TestTableOptions: app.TestTableOptions{
Expand All @@ -137,16 +159,17 @@ func main() {
Trim: *smallScreenPtr,
TrimPath: *trimPathPtr,
},
Format: format,
Sorter: sorter,
ShowNoTests: *showNoTestsPtr,
Progress: *progressPtr,
Compare: *comparePtr,
Format: format,
Sorter: sorter,
ShowNoTests: *showNoTestsPtr,
Progress: *progressPtr,
ProgressOutput: os.Stdout,
Compare: *comparePtr,

// Do not expose publicly.
DisableTableOutput: false,
}
exitCode, err := app.Run(os.Stdout, options)
exitCode, err := app.Run(options)
if err != nil {
msg := err.Error()
if errors.Is(err, parse.ErrNotParsable) {
Expand Down
2 changes: 1 addition & 1 deletion parse/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func Process(r io.Reader, optionsFunc ...OptionsFunc) (*GoTestSummary, error) {
// Progress is a special case of follow, where we only print the
// progress of the test suite, but not the output.
if option.progress && option.w != nil {
printProgress(option.w, e, summary.Packages)
printProgress(option.progressOutput, e, summary.Packages)
}

summary.AddEvent(e)
Expand Down
8 changes: 7 additions & 1 deletion parse/process_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ type options struct {
follow bool
followVerbose bool
debug bool
progress bool

progress bool
progressOutput io.Writer
}

type OptionsFunc func(o *options)
Expand All @@ -33,3 +35,7 @@ func WithDebug() OptionsFunc {
func WithProgress(b bool) OptionsFunc {
return func(o *options) { o.progress = b }
}

func WithProgressOutput(w io.Writer) OptionsFunc {
return func(o *options) { o.progressOutput = w }
}
11 changes: 7 additions & 4 deletions tests/follow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/assert"

"github.com/mfridman/tparse/internal/app"
"github.com/mfridman/tparse/internal/utils"
"github.com/mfridman/tparse/parse"
)

Expand All @@ -35,15 +36,16 @@ func TestFollow(t *testing.T) {
}
for _, tc := range tt {
t.Run(tc.fileName, func(t *testing.T) {
buf := bytes.NewBuffer(nil)
inputFile := filepath.Join(base, tc.fileName+".jsonl")
options := app.Options{
FileName: inputFile,
FollowOutput: true,
FollowOutputWriter: utils.WriteNopCloser{Writer: buf},
FollowOutputVerbose: true,
DisableTableOutput: true,
}
var buf bytes.Buffer
gotExitCode, err := app.Run(&buf, options)
gotExitCode, err := app.Run(options)
if err != nil && !errors.Is(err, tc.err) {
t.Fatal(err)
}
Expand All @@ -70,14 +72,15 @@ func TestFollow(t *testing.T) {
}
for _, tc := range tt {
t.Run(tc.fileName, func(t *testing.T) {
buf := bytes.NewBuffer(nil)
inputFile := filepath.Join(base, tc.fileName+".jsonl")
options := app.Options{
FileName: inputFile,
FollowOutput: true,
FollowOutputWriter: utils.WriteNopCloser{Writer: buf},
DisableTableOutput: true,
}
var buf bytes.Buffer
gotExitCode, err := app.Run(&buf, options)
gotExitCode, err := app.Run(options)
if err != nil && !errors.Is(err, tc.err) {
t.Fatal(err)
}
Expand Down

0 comments on commit 5ae5274

Please sign in to comment.