diff --git a/parser/gotest/internal/reader/reader.go b/parser/gotest/internal/reader/reader.go index 35a0dd9..81ff2de 100644 --- a/parser/gotest/internal/reader/reader.go +++ b/parser/gotest/internal/reader/reader.go @@ -3,7 +3,10 @@ package reader import ( "bufio" "bytes" + "encoding/json" "io" + "strings" + "time" ) // LineReader is an interface to read lines with optional Metadata. @@ -68,3 +71,52 @@ func (r *LimitedLineReader) ReadLine() (string, *Metadata, error) { } return buf.String(), nil, nil } + +// Event represents a JSON event emitted by `go test -json`. +type Event struct { + Time time.Time + Action string + Package string + Test string + Elapsed float64 // seconds + Output string +} + +// JSONEventReader reads JSON events from an io.Reader object. +type JSONEventReader struct { + r *LimitedLineReader +} + +var _ LineReader = &JSONEventReader{} + +// jsonLineLimit is the maximum size of a single JSON line emitted by `go test +// -json`. +const jsonLineLimit = 64 * 1024 + +// NewJSONEventReader returns a JSONEventReader to read the data in JSON +// events from r. +func NewJSONEventReader(r io.Reader) *JSONEventReader { + return &JSONEventReader{NewLimitedLineReader(r, jsonLineLimit)} +} + +// ReadLine returns the next line from the underlying reader. +func (r *JSONEventReader) ReadLine() (string, *Metadata, error) { + for { + line, _, err := r.r.ReadLine() + if err != nil { + return "", nil, err + } + if len(line) == 0 || line[0] != '{' { + return line, nil, nil + } + event := &Event{} + if err := json.Unmarshal([]byte(line), event); err != nil { + return "", nil, err + } + if event.Output == "" { + // Skip events without output + continue + } + return strings.TrimSuffix(event.Output, "\n"), &Metadata{Package: event.Package}, nil + } +} diff --git a/parser/gotest/internal/reader/reader_test.go b/parser/gotest/internal/reader/reader_test.go index f052495..03664e3 100644 --- a/parser/gotest/internal/reader/reader_test.go +++ b/parser/gotest/internal/reader/reader_test.go @@ -5,6 +5,8 @@ import ( "io" "strings" "testing" + + "github.com/google/go-cmp/cmp" ) const testingLimit = 4 * 1024 * 1024 @@ -66,3 +68,34 @@ func TestLimitedLineReader(t *testing.T) { }) } } + +func TestJSONEventReader(t *testing.T) { + input := `some other output +{"Time":"2019-10-09T00:00:00.708139047+00:00","Action":"output","Package":"package/name/ok","Test":"TestOK"} +{"Time":"2019-10-09T00:00:00.708139047+00:00","Action":"output","Package":"package/name/ok","Test":"TestOK","Output":"=== RUN TestOK\n"} +` + want := []struct { + line string + metadata *Metadata + }{ + {"some other output", nil}, + {"=== RUN TestOK", &Metadata{Package: "package/name/ok"}}, + } + + r := NewJSONEventReader(strings.NewReader(input)) + for i := 0; i < len(want); i++ { + line, metadata, err := r.ReadLine() + if err == io.EOF { + return + } else if err != nil { + t.Fatalf("ReadLine() returned error %v", err) + } + + if diff := cmp.Diff(want[i].line, line); diff != "" { + t.Errorf("ReadLine() returned incorrect line, diff (-want, +got):\n%s\n", diff) + } + if diff := cmp.Diff(want[i].metadata, metadata); diff != "" { + t.Errorf("ReadLine() Returned incorrect metadata, diff (-want, +got):\n%s\n", diff) + } + } +} diff --git a/parser/gotest/json.go b/parser/gotest/json.go index 61471e9..bb0ccec 100644 --- a/parser/gotest/json.go +++ b/parser/gotest/json.go @@ -1,12 +1,10 @@ package gotest import ( - "bufio" - "encoding/json" "io" - "time" "github.com/jstemmer/go-junit-report/v2/gtr" + "github.com/jstemmer/go-junit-report/v2/parser/gotest/internal/reader" ) // NewJSONParser returns a new Go test json output parser. @@ -22,56 +20,10 @@ type JSONParser struct { // Parse parses Go test json output from the given io.Reader r and returns // gtr.Report. func (p *JSONParser) Parse(r io.Reader) (gtr.Report, error) { - return p.gp.Parse(newJSONReader(r)) + return p.gp.parse(reader.NewJSONEventReader(r)) } // Events returns the events created by the parser. func (p *JSONParser) Events() []Event { return p.gp.Events() } - -type jsonEvent struct { - Time time.Time - Action string - Package string - Test string - Elapsed float64 // seconds - Output string -} - -type jsonReader struct { - r *bufio.Reader - buf []byte -} - -func newJSONReader(reader io.Reader) *jsonReader { - return &jsonReader{r: bufio.NewReader(reader)} -} - -func (j *jsonReader) Read(p []byte) (int, error) { - var err error - for len(j.buf) == 0 { - j.buf, err = j.readNextLine() - if err != nil { - return 0, err - } - } - n := copy(p, j.buf) - j.buf = j.buf[n:] - return n, nil -} - -func (j jsonReader) readNextLine() ([]byte, error) { - line, err := j.r.ReadBytes('\n') - if err != nil { - return nil, err - } - if len(line) == 0 || line[0] != '{' { - return line, nil - } - var event jsonEvent - if err := json.Unmarshal(line, &event); err != nil { - return nil, err - } - return []byte(event.Output), nil -} diff --git a/parser/gotest/json_test.go b/parser/gotest/json_test.go deleted file mode 100644 index 85b4177..0000000 --- a/parser/gotest/json_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package gotest - -import ( - "io" - "io/ioutil" - "strings" - "testing" - - "github.com/google/go-cmp/cmp" -) - -var input = `some other output -{"Time":"2019-10-09T00:00:00.708139047+00:00","Action":"output","Package":"package/name/ok","Test":"TestOK"} -{"Time":"2019-10-09T00:00:00.708139047+00:00","Action":"output","Package":"package/name/ok","Test":"TestOK","Output":"=== RUN TestOK\n"} -` - -func TestJSONReaderReadAll(t *testing.T) { - r := newJSONReader(strings.NewReader(input)) - got, err := ioutil.ReadAll(r) - if err != nil { - t.Fatal(err) - } - - want := `some other output -=== RUN TestOK -` - - if diff := cmp.Diff(want, string(got)); diff != "" { - t.Errorf("unexpected result from jsonReader, diff (-want, +got):\n%s\n", diff) - } -} - -func TestJSONReaderReadSmallBuffer(t *testing.T) { - expected := [][]byte{ - []byte("some"), - []byte(" oth"), - []byte("er o"), - []byte("utpu"), - []byte("t\n"), - []byte("=== "), - []byte("RUN "), - []byte(" Te"), - []byte("stOK"), - []byte("\n"), - } - - r := newJSONReader(strings.NewReader(input)) - buf := make([]byte, 4) - for _, want := range expected { - n, err := r.Read(buf) - if err != nil { - t.Fatalf("Read error: %v", err) - } - - got := buf[:n] - if diff := cmp.Diff(string(want), string(got)); diff != "" { - t.Fatalf("unexpected result from jsonReader, diff (-want, +got):\n%s\n", diff) - } - } - - _, err := r.Read(buf) - if err != io.EOF { - t.Fatalf("unexpected error from jsonReader: got %v, want %v", err, io.EOF) - } -}