Skip to content

Commit

Permalink
Merge pull request #306 from dnephin/jsonfile-events
Browse files Browse the repository at this point in the history
Add jsonfile-timing-events flag for capturing only the timing events in a file
  • Loading branch information
dnephin authored Apr 4, 2023
2 parents 4ea330e + 37f2656 commit 83a50de
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 15 deletions.
58 changes: 47 additions & 11 deletions cmd/handler.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"bufio"
"fmt"
"io"
"os"
Expand All @@ -13,28 +14,33 @@ import (
)

type eventHandler struct {
formatter testjson.EventFormatter
err io.Writer
jsonFile writeSyncer
maxFails int
formatter testjson.EventFormatter
err *bufio.Writer
jsonFile writeSyncer
jsonFileTimingEvents writeSyncer
maxFails int
}

type writeSyncer interface {
io.WriteCloser
Sync() error
}

// nolint:errcheck
func (h *eventHandler) Err(text string) error {
_, _ = h.err.Write([]byte(text + "\n"))
h.err.WriteString(text)
h.err.WriteRune('\n')
h.err.Flush()
// always return nil, no need to stop scanning if the stderr write fails
return nil
}

func (h *eventHandler) Event(event testjson.TestEvent, execution *testjson.Execution) error {
// ignore artificial events with no raw Bytes()
if h.jsonFile != nil && len(event.Bytes()) > 0 {
_, err := h.jsonFile.Write(append(event.Bytes(), '\n'))
if err != nil {
if err := writeWithNewline(h.jsonFile, event.Bytes()); err != nil {
return fmt.Errorf("failed to write JSON file: %w", err)
}
if event.Action.IsTerminal() {
if err := writeWithNewline(h.jsonFileTimingEvents, event.Bytes()); err != nil {
return fmt.Errorf("failed to write JSON file: %w", err)
}
}
Expand All @@ -50,12 +56,29 @@ func (h *eventHandler) Event(event testjson.TestEvent, execution *testjson.Execu
return nil
}

func writeWithNewline(out io.Writer, b []byte) error {
// ignore artificial events that have len(b) == 0
if out == nil || len(b) == 0 {
return nil
}
if _, err := out.Write(b); err != nil {
return err
}
_, err := out.Write([]byte{'\n'})
return err
}

func (h *eventHandler) Flush() {
if h.jsonFile != nil {
if err := h.jsonFile.Sync(); err != nil {
log.Errorf("Failed to sync JSON file: %v", err)
}
}
if h.jsonFileTimingEvents != nil {
if err := h.jsonFileTimingEvents.Sync(); err != nil {
log.Errorf("Failed to sync JSON file: %v", err)
}
}
}

func (h *eventHandler) Close() error {
Expand All @@ -64,6 +87,11 @@ func (h *eventHandler) Close() error {
log.Errorf("Failed to close JSON file: %v", err)
}
}
if h.jsonFileTimingEvents != nil {
if err := h.jsonFileTimingEvents.Close(); err != nil {
log.Errorf("Failed to close JSON file: %v", err)
}
}
return nil
}

Expand All @@ -76,15 +104,22 @@ func newEventHandler(opts *options) (*eventHandler, error) {
}
handler := &eventHandler{
formatter: formatter,
err: opts.stderr,
err: bufio.NewWriter(opts.stderr),
maxFails: opts.maxFails,
}
var err error
if opts.jsonFile != "" {
_ = os.MkdirAll(filepath.Dir(opts.jsonFile), 0o755)
handler.jsonFile, err = os.Create(opts.jsonFile)
if err != nil {
return handler, fmt.Errorf("failed to open JSON file: %w", err)
return handler, fmt.Errorf("failed to create file: %w", err)
}
}
if opts.jsonFileTimingEvents != "" {
_ = os.MkdirAll(filepath.Dir(opts.jsonFileTimingEvents), 0o755)
handler.jsonFileTimingEvents, err = os.Create(opts.jsonFileTimingEvents)
if err != nil {
return handler, fmt.Errorf("failed to create file: %w", err)
}
}
return handler, nil
Expand Down Expand Up @@ -125,6 +160,7 @@ func postRunHook(opts *options, execution *testjson.Execution) error {
cmd.Env = append(
os.Environ(),
"GOTESTSUM_JSONFILE="+opts.jsonFile,
"GOTESTSUM_JSONFILE_TIMING_EVENTS="+opts.jsonFileTimingEvents,
"GOTESTSUM_JUNITFILE="+opts.junitFile,
fmt.Sprintf("TESTS_TOTAL=%d", execution.Total()),
fmt.Sprintf("TESTS_FAILED=%d", len(execution.Failed())),
Expand Down
9 changes: 5 additions & 4 deletions cmd/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ func TestPostRunHook(t *testing.T) {

buf := new(bytes.Buffer)
opts := &options{
postRunHookCmd: command,
jsonFile: "events.json",
junitFile: "junit.xml",
stdout: buf,
postRunHookCmd: command,
jsonFile: "events.json",
jsonFileTimingEvents: "timing.json",
junitFile: "junit.xml",
stdout: buf,
}

env.Patch(t, "GOTESTSUM_FORMAT", "short")
Expand Down
4 changes: 4 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ func setupFlags(name string) (*pflag.FlagSet, *options) {
flags.StringVar(&opts.jsonFile, "jsonfile",
lookEnvWithDefault("GOTESTSUM_JSONFILE", ""),
"write all TestEvents to file")
flags.StringVar(&opts.jsonFileTimingEvents, "jsonfile-timing-events",
lookEnvWithDefault("GOTESTSUM_JSONFILE_TIMING_EVENTS", ""),
"write only the pass, skip, and fail TestEvents to the file")
flags.BoolVar(&opts.noColor, "no-color", defaultNoColor, "disable color output")

flags.Var(opts.hideSummary, "no-summary",
Expand Down Expand Up @@ -160,6 +163,7 @@ type options struct {
rawCommand bool
ignoreNonJSONOutputLines bool
jsonFile string
jsonFileTimingEvents string
junitFile string
postRunHookCmd *commandValue
noColor bool
Expand Down
34 changes: 34 additions & 0 deletions cmd/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,3 +486,37 @@ func TestRun_JsonFileIsSyncedBeforePostRunCommand(t *testing.T) {
_, actual, _ := strings.Cut(out.String(), "s\n") // remove the DONE line
assert.Equal(t, actual, expected)
}

func TestRun_JsonFileTimingEvents(t *testing.T) {
input := golden.Get(t, "../../testjson/testdata/input/go-test-json.out")

fn := func(args []string) *proc {
return &proc{
cmd: fakeWaiter{},
stdout: bytes.NewReader(input),
stderr: bytes.NewReader(nil),
}
}
reset := patchStartGoTestFn(fn)
defer reset()

tmp := t.TempDir()
jsonFileTiming := filepath.Join(tmp, "json.log")

out := new(bytes.Buffer)
opts := &options{
rawCommand: true,
args: []string{"./test.test"},
format: "none",
stdout: out,
stderr: os.Stderr,
hideSummary: &hideSummaryValue{value: testjson.SummarizeNone},
jsonFileTimingEvents: jsonFileTiming,
}
err := run(opts)
assert.NilError(t, err)

raw, err := os.ReadFile(jsonFileTiming)
assert.NilError(t, err)
golden.Assert(t, string(raw), "expected-jsonfile-timing-events")
}
64 changes: 64 additions & 0 deletions cmd/testdata/expected-jsonfile-timing-events
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{"Time":"2022-06-19T13:44:44.851087257-04:00","Action":"fail","Package":"gotest.tools/gotestsum/testjson/internal/badmain","Elapsed":0.001}
{"Time":"2022-06-19T13:44:44.855151131-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/empty","Elapsed":0}
{"Time":"2022-06-19T13:44:44.859699224-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestPassed","Elapsed":0}
{"Time":"2022-06-19T13:44:44.859712195-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestPassedWithLog","Elapsed":0}
{"Time":"2022-06-19T13:44:44.859724262-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestPassedWithStdout","Elapsed":0}
{"Time":"2022-06-19T13:44:44.859741082-04:00","Action":"skip","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestSkipped","Elapsed":0}
{"Time":"2022-06-19T13:44:44.859753158-04:00","Action":"skip","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestSkippedWitLog","Elapsed":0}
{"Time":"2022-06-19T13:44:44.859765298-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestWithStderr","Elapsed":0}
{"Time":"2022-06-19T13:44:44.859850991-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestNestedSuccess/a/sub","Elapsed":0}
{"Time":"2022-06-19T13:44:44.859853265-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestNestedSuccess/a","Elapsed":0}
{"Time":"2022-06-19T13:44:44.859862788-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestNestedSuccess/b/sub","Elapsed":0}
{"Time":"2022-06-19T13:44:44.85986508-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestNestedSuccess/b","Elapsed":0}
{"Time":"2022-06-19T13:44:44.859872517-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestNestedSuccess/c/sub","Elapsed":0}
{"Time":"2022-06-19T13:44:44.859874632-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestNestedSuccess/c","Elapsed":0}
{"Time":"2022-06-19T13:44:44.859881961-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestNestedSuccess/d/sub","Elapsed":0}
{"Time":"2022-06-19T13:44:44.859884191-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestNestedSuccess/d","Elapsed":0}
{"Time":"2022-06-19T13:44:44.859886372-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestNestedSuccess","Elapsed":0}
{"Time":"2022-06-19T13:44:44.859895611-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestParallelTheFirst","Elapsed":0.01}
{"Time":"2022-06-19T13:44:44.859913525-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestParallelTheThird","Elapsed":0}
{"Time":"2022-06-19T13:44:44.859918336-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestParallelTheSecond","Elapsed":0.01}
{"Time":"2022-06-19T13:44:44.859926497-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/good","Elapsed":0}
{"Time":"2022-06-19T13:44:44.914336528-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/parallelfails","Test":"TestPassed","Elapsed":0}
{"Time":"2022-06-19T13:44:44.914351368-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/parallelfails","Test":"TestPassedWithLog","Elapsed":0}
{"Time":"2022-06-19T13:44:44.914364274-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/parallelfails","Test":"TestPassedWithStdout","Elapsed":0}
{"Time":"2022-06-19T13:44:44.914382623-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/parallelfails","Test":"TestWithStderr","Elapsed":0}
{"Time":"2022-06-19T13:44:44.914503606-04:00","Action":"fail","Package":"gotest.tools/gotestsum/testjson/internal/parallelfails","Test":"TestNestedParallelFailures/a","Elapsed":0}
{"Time":"2022-06-19T13:44:44.914508601-04:00","Action":"fail","Package":"gotest.tools/gotestsum/testjson/internal/parallelfails","Test":"TestNestedParallelFailures/d","Elapsed":0}
{"Time":"2022-06-19T13:44:44.914513457-04:00","Action":"fail","Package":"gotest.tools/gotestsum/testjson/internal/parallelfails","Test":"TestNestedParallelFailures/c","Elapsed":0}
{"Time":"2022-06-19T13:44:44.914518402-04:00","Action":"fail","Package":"gotest.tools/gotestsum/testjson/internal/parallelfails","Test":"TestNestedParallelFailures/b","Elapsed":0}
{"Time":"2022-06-19T13:44:44.914520636-04:00","Action":"fail","Package":"gotest.tools/gotestsum/testjson/internal/parallelfails","Test":"TestNestedParallelFailures","Elapsed":0}
{"Time":"2022-06-19T13:44:44.924699091-04:00","Action":"fail","Package":"gotest.tools/gotestsum/testjson/internal/parallelfails","Test":"TestParallelTheFirst","Elapsed":0.01}
{"Time":"2022-06-19T13:44:44.926895283-04:00","Action":"fail","Package":"gotest.tools/gotestsum/testjson/internal/parallelfails","Test":"TestParallelTheThird","Elapsed":0}
{"Time":"2022-06-19T13:44:44.933108555-04:00","Action":"fail","Package":"gotest.tools/gotestsum/testjson/internal/parallelfails","Test":"TestParallelTheSecond","Elapsed":0.01}
{"Time":"2022-06-19T13:44:44.933277617-04:00","Action":"fail","Package":"gotest.tools/gotestsum/testjson/internal/parallelfails","Elapsed":0.02}
{"Time":"2022-06-19T13:44:44.988321998-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestPassed","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988339579-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestPassedWithLog","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988360671-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestPassedWithStdout","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988373636-04:00","Action":"skip","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestSkipped","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988385879-04:00","Action":"skip","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestSkippedWitLog","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988400233-04:00","Action":"fail","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestFailed","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988412375-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestWithStderr","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988429392-04:00","Action":"fail","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestFailedWithStderr","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988523195-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestNestedWithFailure/a/sub","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988525729-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestNestedWithFailure/a","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988533344-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestNestedWithFailure/b/sub","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988535616-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestNestedWithFailure/b","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988540575-04:00","Action":"fail","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestNestedWithFailure/c","Elapsed":0}
{"Time":"2022-06-19T13:44:44.98855307-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestNestedWithFailure/d/sub","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988555926-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestNestedWithFailure/d","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988558303-04:00","Action":"fail","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestNestedWithFailure","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988613572-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestNestedSuccess/a/sub","Elapsed":0}
{"Time":"2022-06-19T13:44:44.98861593-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestNestedSuccess/a","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988623564-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestNestedSuccess/b/sub","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988625803-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestNestedSuccess/b","Elapsed":0}
{"Time":"2022-06-19T13:44:44.98863313-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestNestedSuccess/c/sub","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988635513-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestNestedSuccess/c","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988643545-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestNestedSuccess/d/sub","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988645759-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestNestedSuccess/d","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988647935-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestNestedSuccess","Elapsed":0}
{"Time":"2022-06-19T13:44:44.988663887-04:00","Action":"skip","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestTimeout","Elapsed":0}
{"Time":"2022-06-19T13:44:44.998850256-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestParallelTheFirst","Elapsed":0.01}
{"Time":"2022-06-19T13:44:45.000983481-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestParallelTheThird","Elapsed":0}
{"Time":"2022-06-19T13:44:45.007374647-04:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Test":"TestParallelTheSecond","Elapsed":0.01}
{"Time":"2022-06-19T13:44:45.00795073-04:00","Action":"fail","Package":"gotest.tools/gotestsum/testjson/internal/withfails","Elapsed":0.02}
1 change: 1 addition & 0 deletions cmd/testdata/gotestsum-help-text
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Flags:
--format-hivis use high visibility characters in some formats
--hide-summary summary hide sections of the summary: skipped,failed,errors,output (default none)
--jsonfile string write all TestEvents to file
--jsonfile-timing-events string write only the pass, skip, and fail TestEvents to the file
--junitfile string write a JUnit XML file
--junitfile-hide-empty-pkg omit packages with no tests from the junit.xml file
--junitfile-project-name string name of the project used in the junit.xml file
Expand Down
1 change: 1 addition & 0 deletions cmd/testdata/post-run-hook-expected
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
GOTESTSUM_FORMAT=short
GOTESTSUM_JSONFILE=events.json
GOTESTSUM_JSONFILE_TIMING_EVENTS=timing.json
GOTESTSUM_JUNITFILE=junit.xml
TESTS_ERRORS=0
TESTS_FAILED=13
Expand Down

0 comments on commit 83a50de

Please sign in to comment.