oss.indeed.com/go/go-opine@v1.3.0/internal/gotest/event.go (about) 1 package gotest 2 3 import ( 4 "bufio" 5 "encoding/json" 6 "io" 7 "regexp" 8 "time" 9 ) 10 11 var workaroundGoIssue35169Regexp = regexp.MustCompile(`^FAIL\s+(\S+)\s+\[build failed\]$`) 12 13 // event is a test event printed by "go test -json". See 14 // "go doc test2json" for more details. This struct was 15 // copied directly from those docs. 16 type event struct { 17 Time time.Time // encodes as an RFC3339-format string 18 Action string 19 Package string 20 Test string 21 Elapsed float64 // seconds 22 Output string 23 } 24 25 // eventAccepter accepts events created by an eventStreamParser. 26 type eventAccepter interface { 27 Accept(e event) error 28 } 29 30 // eventConverter converts a single line (without the newline) 31 // to events. 32 type eventConverter interface { 33 Convert(line []byte) ([]event, error) 34 } 35 36 // jsonEventConverter converts JSON event lines (as printed by 37 // "go test -json") into singleton lists with the corresponding 38 // event. 39 type jsonEventConverter struct { 40 } 41 42 var _ eventConverter = jsonEventConverter{} 43 44 func (jsonEventConverter) Convert(line []byte) ([]event, error) { 45 var e event 46 if err := json.Unmarshal(line, &e); err != nil { 47 return nil, err 48 } 49 return []event{e}, nil 50 } 51 52 // workaroundGoIssue35169EventConverter first calls the primary 53 // eventConverter and, if that fails, falls back to converting 54 // lines like "FAIL example.com [build failed]" into events. 55 // 56 // This is to work around https://github.com/golang/go/issues/35169. 57 type workaroundGoIssue35169EventConverter struct { 58 primary eventConverter 59 } 60 61 var _ eventConverter = (*workaroundGoIssue35169EventConverter)(nil) 62 63 func (w *workaroundGoIssue35169EventConverter) Convert(line []byte) ([]event, error) { 64 events, err := w.primary.Convert(line) 65 if err == nil { 66 return events, nil 67 } 68 69 match := workaroundGoIssue35169Regexp.FindSubmatch(line) 70 if match != nil { 71 ts := time.Now() 72 pkg := string(match[1]) 73 events := []event{ 74 { 75 Time: ts, 76 Action: "output", 77 Package: pkg, 78 Output: string(line) + "\n", // bufio.Sanner removes the newline 79 }, 80 { 81 Time: ts, 82 Action: "fail", 83 Package: pkg, 84 Elapsed: 0, 85 }, 86 } 87 return events, nil 88 } 89 90 return nil, err 91 } 92 93 // eventStreamParser reads "go test -json" output, converts 94 // each line to an event, and passes each event to the eventAccepter. 95 type eventStreamParser struct { 96 to eventAccepter 97 converter eventConverter 98 } 99 100 func newEventStreamParser(to eventAccepter) *eventStreamParser { 101 return &eventStreamParser{ 102 to: to, 103 converter: &workaroundGoIssue35169EventConverter{jsonEventConverter{}}, 104 } 105 } 106 107 // Parse "go test -json" output into events and pass them to the 108 // eventAccepter. 109 // 110 // If any line is not JSON, or if the eventAccepter returns an 111 // error then Parse will stop immediately and return the error. 112 func (esp *eventStreamParser) Parse(r io.Reader) error { 113 scanner := bufio.NewScanner(r) 114 for scanner.Scan() { 115 events, err := esp.converter.Convert(scanner.Bytes()) 116 if err != nil { 117 return err 118 } 119 for _, e := range events { 120 if err := esp.to.Accept(e); err != nil { 121 return err 122 } 123 } 124 } 125 return scanner.Err() 126 }