github.com/goproxy0/go@v0.0.0-20171111080102-49cc0c489d2c/src/cmd/internal/test2json/test2json.go (about) 1 // Copyright 2017 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package test2json implements conversion of test binary output to JSON. 6 // It is used by cmd/test2json and cmd/go. 7 // 8 // See the cmd/test2json documentation for details of the JSON encoding. 9 package test2json 10 11 import ( 12 "bytes" 13 "encoding/json" 14 "fmt" 15 "io" 16 "strconv" 17 "strings" 18 "time" 19 "unicode/utf8" 20 ) 21 22 // Mode controls details of the conversion. 23 type Mode int 24 25 const ( 26 Timestamp Mode = 1 << iota // include Time in events 27 ) 28 29 // event is the JSON struct we emit. 30 type event struct { 31 Time *time.Time `json:",omitempty"` 32 Action string 33 Package string `json:",omitempty"` 34 Test string `json:",omitempty"` 35 Elapsed *float64 `json:",omitempty"` 36 Output *textBytes `json:",omitempty"` 37 } 38 39 // textBytes is a hack to get JSON to emit a []byte as a string 40 // without actually copying it to a string. 41 // It implements encoding.TextMarshaler, which returns its text form as a []byte, 42 // and then json encodes that text form as a string (which was our goal). 43 type textBytes []byte 44 45 func (b textBytes) MarshalText() ([]byte, error) { return b, nil } 46 47 // A converter holds the state of a test-to-JSON conversion. 48 // It implements io.WriteCloser; the caller writes test output in, 49 // and the converter writes JSON output to w. 50 type converter struct { 51 w io.Writer // JSON output stream 52 pkg string // package to name in events 53 mode Mode // mode bits 54 start time.Time // time converter started 55 testName string // name of current test, for output attribution 56 report []*event // pending test result reports (nested for subtests) 57 passed bool // whether we've seen the final whole-package PASS line 58 input lineBuffer // input buffer 59 output lineBuffer // output buffer 60 } 61 62 // inBuffer and outBuffer are the input and output buffer sizes. 63 // They're variables so that they can be reduced during testing. 64 // 65 // The input buffer needs to be able to hold any single test 66 // directive line we want to recognize, like: 67 // 68 // <many spaces> --- PASS: very/nested/s/u/b/t/e/s/t 69 // 70 // If anyone reports a test directive line > 4k not working, it will 71 // be defensible to suggest they restructure their test or test names. 72 // 73 // The output buffer must be >= utf8.UTFMax, so that it can 74 // accumulate any single UTF8 sequence. Lines that fit entirely 75 // within the output buffer are emitted in single output events. 76 // Otherwise they are split into multiple events. 77 // The output buffer size therefore limits the size of the encoding 78 // of a single JSON output event. 1k seems like a reasonable balance 79 // between wanting to avoid splitting an output line and not wanting to 80 // generate enormous output events. 81 var ( 82 inBuffer = 4096 83 outBuffer = 1024 84 ) 85 86 // NewConverter returns a "test to json" converter. 87 // Writes on the returned writer are written as JSON to w, 88 // with minimal delay. 89 // 90 // The writes to w are whole JSON events ending in \n, 91 // so that it is safe to run multiple tests writing to multiple converters 92 // writing to a single underlying output stream w. 93 // As long as the underlying output w can handle concurrent writes 94 // from multiple goroutines, the result will be a JSON stream 95 // describing the relative ordering of execution in all the concurrent tests. 96 // 97 // The mode flag adjusts the behavior of the converter. 98 // Passing ModeTime includes event timestamps and elapsed times. 99 // 100 // The pkg string, if present, specifies the import path to 101 // report in the JSON stream. 102 func NewConverter(w io.Writer, pkg string, mode Mode) io.WriteCloser { 103 c := new(converter) 104 *c = converter{ 105 w: w, 106 pkg: pkg, 107 mode: mode, 108 start: time.Now(), 109 input: lineBuffer{ 110 b: make([]byte, 0, inBuffer), 111 line: c.handleInputLine, 112 part: c.output.write, 113 }, 114 output: lineBuffer{ 115 b: make([]byte, 0, outBuffer), 116 line: c.writeOutputEvent, 117 part: c.writeOutputEvent, 118 }, 119 } 120 return c 121 } 122 123 // Write writes the test input to the converter. 124 func (c *converter) Write(b []byte) (int, error) { 125 c.input.write(b) 126 return len(b), nil 127 } 128 129 var ( 130 bigPass = []byte("PASS\n") 131 bigFail = []byte("FAIL\n") 132 133 updates = [][]byte{ 134 []byte("=== RUN "), 135 []byte("=== PAUSE "), 136 []byte("=== CONT "), 137 } 138 139 reports = [][]byte{ 140 []byte("--- PASS: "), 141 []byte("--- FAIL: "), 142 } 143 144 fourSpace = []byte(" ") 145 ) 146 147 // handleInputLine handles a single whole test output line. 148 // It must write the line to c.output but may choose to do so 149 // before or after emitting other events. 150 func (c *converter) handleInputLine(line []byte) { 151 // Final PASS or FAIL. 152 if bytes.Equal(line, bigPass) || bytes.Equal(line, bigFail) { 153 c.flushReport(0) 154 c.output.write(line) 155 c.passed = bytes.Equal(line, bigPass) 156 return 157 } 158 159 // "=== RUN " 160 // "=== PAUSE " 161 // "=== CONT " 162 origLine := line 163 ok := false 164 indent := 0 165 for _, magic := range updates { 166 if bytes.HasPrefix(line, magic) { 167 ok = true 168 break 169 } 170 } 171 if !ok { 172 // "--- PASS: " 173 // "--- FAIL: " 174 // but possibly indented. 175 for bytes.HasPrefix(line, fourSpace) { 176 line = line[4:] 177 indent++ 178 } 179 for _, magic := range reports { 180 if bytes.HasPrefix(line, magic) { 181 ok = true 182 break 183 } 184 } 185 } 186 187 if !ok { 188 // Not a special test output line. 189 c.output.write(origLine) 190 return 191 } 192 193 // Parse out action and test name. 194 action := strings.ToLower(strings.TrimSuffix(strings.TrimSpace(string(line[4:4+6])), ":")) 195 name := strings.TrimSpace(string(line[4+6:])) 196 197 e := &event{Action: action} 198 if line[0] == '-' { // PASS or FAIL report 199 // Parse out elapsed time. 200 if i := strings.Index(name, " ("); i >= 0 { 201 if strings.HasSuffix(name, "s)") { 202 t, err := strconv.ParseFloat(name[i+2:len(name)-2], 64) 203 if err == nil { 204 if c.mode&Timestamp != 0 { 205 e.Elapsed = &t 206 } 207 } 208 } 209 name = name[:i] 210 } 211 if len(c.report) < indent { 212 // Nested deeper than expected. 213 // Treat this line as plain output. 214 return 215 } 216 // Flush reports at this indentation level or deeper. 217 c.flushReport(indent) 218 e.Test = name 219 c.testName = name 220 c.report = append(c.report, e) 221 c.output.write(origLine) 222 return 223 } 224 // === update. 225 // Finish any pending PASS/FAIL reports. 226 c.flushReport(0) 227 c.testName = name 228 229 if action == "pause" { 230 // For a pause, we want to write the pause notification before 231 // delivering the pause event, just so it doesn't look like the test 232 // is generating output immediately after being paused. 233 c.output.write(origLine) 234 } 235 c.writeEvent(e) 236 if action != "pause" { 237 c.output.write(origLine) 238 } 239 240 return 241 } 242 243 // flushReport flushes all pending PASS/FAIL reports at levels >= depth. 244 func (c *converter) flushReport(depth int) { 245 c.testName = "" 246 for len(c.report) > depth { 247 e := c.report[len(c.report)-1] 248 c.report = c.report[:len(c.report)-1] 249 c.writeEvent(e) 250 } 251 } 252 253 // Close marks the end of the go test output. 254 // It flushes any pending input and then output (only partial lines at this point) 255 // and then emits the final overall package-level pass/fail event. 256 func (c *converter) Close() error { 257 c.input.flush() 258 c.output.flush() 259 e := &event{Action: "fail"} 260 if c.passed { 261 e.Action = "pass" 262 } 263 if c.mode&Timestamp != 0 { 264 dt := time.Since(c.start).Round(1 * time.Millisecond).Seconds() 265 e.Elapsed = &dt 266 } 267 c.writeEvent(e) 268 return nil 269 } 270 271 // writeOutputEvent writes a single output event with the given bytes. 272 func (c *converter) writeOutputEvent(out []byte) { 273 c.writeEvent(&event{ 274 Action: "output", 275 Output: (*textBytes)(&out), 276 }) 277 } 278 279 // writeEvent writes a single event. 280 // It adds the package, time (if requested), and test name (if needed). 281 func (c *converter) writeEvent(e *event) { 282 e.Package = c.pkg 283 if c.mode&Timestamp != 0 { 284 t := time.Now() 285 e.Time = &t 286 } 287 if e.Test == "" { 288 e.Test = c.testName 289 } 290 js, err := json.Marshal(e) 291 if err != nil { 292 // Should not happen - event is valid for json.Marshal. 293 c.w.Write([]byte(fmt.Sprintf("testjson internal error: %v\n", err))) 294 return 295 } 296 js = append(js, '\n') 297 c.w.Write(js) 298 } 299 300 // A lineBuffer is an I/O buffer that reacts to writes by invoking 301 // input-processing callbacks on whole lines or (for long lines that 302 // have been split) line fragments. 303 // 304 // It should be initialized with b set to a buffer of length 0 but non-zero capacity, 305 // and line and part set to the desired input processors. 306 // The lineBuffer will call line(x) for any whole line x (including the final newline) 307 // that fits entirely in cap(b). It will handle input lines longer than cap(b) by 308 // calling part(x) for sections of the line. The line will be split at UTF8 boundaries, 309 // and the final call to part for a long line includes the final newline. 310 type lineBuffer struct { 311 b []byte // buffer 312 mid bool // whether we're in the middle of a long line 313 line func([]byte) // line callback 314 part func([]byte) // partial line callback 315 } 316 317 // write writes b to the buffer. 318 func (l *lineBuffer) write(b []byte) { 319 for len(b) > 0 { 320 // Copy what we can into b. 321 m := copy(l.b[len(l.b):cap(l.b)], b) 322 l.b = l.b[:len(l.b)+m] 323 b = b[m:] 324 325 // Process lines in b. 326 i := 0 327 for i < len(l.b) { 328 j := bytes.IndexByte(l.b[i:], '\n') 329 if j < 0 { 330 break 331 } 332 e := i + j + 1 333 if l.mid { 334 // Found the end of a partial line. 335 l.part(l.b[i:e]) 336 l.mid = false 337 } else { 338 // Found a whole line. 339 l.line(l.b[i:e]) 340 } 341 i = e 342 } 343 344 // Whatever's left in l.b is a line fragment. 345 if i == 0 && len(l.b) == cap(l.b) { 346 // The whole buffer is a fragment. 347 // Emit it as the beginning (or continuation) of a partial line. 348 t := trimUTF8(l.b) 349 l.part(l.b[:t]) 350 l.b = l.b[:copy(l.b, l.b[t:])] 351 l.mid = true 352 } 353 354 // There's room for more input. 355 // Slide it down in hope of completing the line. 356 if i > 0 { 357 l.b = l.b[:copy(l.b, l.b[i:])] 358 } 359 } 360 } 361 362 // flush flushes the line buffer. 363 func (l *lineBuffer) flush() { 364 if len(l.b) > 0 { 365 // Must be a line without a \n, so a partial line. 366 l.part(l.b) 367 l.b = l.b[:0] 368 } 369 } 370 371 // trimUTF8 returns a length t as close to len(b) as possible such that b[:t] 372 // does not end in the middle of a possibly-valid UTF-8 sequence. 373 // 374 // If a large text buffer must be split before position i at the latest, 375 // splitting at position trimUTF(b[:i]) avoids splitting a UTF-8 sequence. 376 func trimUTF8(b []byte) int { 377 // Scan backward to find non-continuation byte. 378 for i := 1; i < utf8.UTFMax && i <= len(b); i++ { 379 if c := b[len(b)-i]; c&0xc0 != 0x80 { 380 switch { 381 case c&0xe0 == 0xc0: 382 if i < 2 { 383 return len(b) - i 384 } 385 case c&0xf0 == 0xe0: 386 if i < 3 { 387 return len(b) - i 388 } 389 case c&0xf8 == 0xf0: 390 if i < 4 { 391 return len(b) - i 392 } 393 } 394 break 395 } 396 } 397 return len(b) 398 }