@@ -6,6 +6,7 @@ package main
6
6
7
7
import (
8
8
"bytes"
9
+ "encoding/json"
9
10
"flag"
10
11
"fmt"
11
12
"io"
@@ -41,6 +42,7 @@ func cmdtest() {
41
42
"Special exception: if the string begins with '!', the match is inverted." )
42
43
flag .BoolVar (& t .msan , "msan" , false , "run in memory sanitizer builder mode" )
43
44
flag .BoolVar (& t .asan , "asan" , false , "run in address sanitizer builder mode" )
45
+ flag .BoolVar (& t .json , "json" , false , "report test results in JSON" )
44
46
45
47
xflagparse (- 1 ) // any number of args
46
48
if noRebuild {
@@ -70,6 +72,7 @@ type tester struct {
70
72
short bool
71
73
cgoEnabled bool
72
74
partial bool
75
+ json bool
73
76
74
77
tests []distTest // use addTest to extend
75
78
testNames map [string ]bool
@@ -212,12 +215,14 @@ func (t *tester) run() {
212
215
}
213
216
}
214
217
215
- if err := t .maybeLogMetadata (); err != nil {
216
- t .failed = true
217
- if t .keepGoing {
218
- log .Printf ("Failed logging metadata: %v" , err )
219
- } else {
220
- fatalf ("Failed logging metadata: %v" , err )
218
+ if ! t .json {
219
+ if err := t .maybeLogMetadata (); err != nil {
220
+ t .failed = true
221
+ if t .keepGoing {
222
+ log .Printf ("Failed logging metadata: %v" , err )
223
+ } else {
224
+ fatalf ("Failed logging metadata: %v" , err )
225
+ }
221
226
}
222
227
}
223
228
@@ -240,13 +245,17 @@ func (t *tester) run() {
240
245
t .runPending (nil )
241
246
timelog ("end" , "dist test" )
242
247
248
+ if ! t .json {
249
+ if t .failed {
250
+ fmt .Println ("\n FAILED" )
251
+ } else if t .partial {
252
+ fmt .Println ("\n ALL TESTS PASSED (some were excluded)" )
253
+ } else {
254
+ fmt .Println ("\n ALL TESTS PASSED" )
255
+ }
256
+ }
243
257
if t .failed {
244
- fmt .Println ("\n FAILED" )
245
258
xexit (1 )
246
- } else if t .partial {
247
- fmt .Println ("\n ALL TESTS PASSED (some were excluded)" )
248
- } else {
249
- fmt .Println ("\n ALL TESTS PASSED" )
250
259
}
251
260
}
252
261
@@ -302,7 +311,8 @@ type goTest struct {
302
311
runOnHost bool // When cross-compiling, run this test on the host instead of guest
303
312
304
313
// variant, if non-empty, is a name used to distinguish different
305
- // configurations of the same test package(s).
314
+ // configurations of the same test package(s). If set and sharded is false,
315
+ // the Package field in test2json output is rewritten to pkg:variant.
306
316
variant string
307
317
// sharded indicates that variant is used solely for sharding and that
308
318
// the set of test names run by each variant of a package is non-overlapping.
@@ -335,7 +345,31 @@ func (opts *goTest) bgCommand(t *tester, stdout, stderr io.Writer) *exec.Cmd {
335
345
336
346
cmd := exec .Command (goCmd , args ... )
337
347
setupCmd (cmd )
338
- cmd .Stdout = stdout
348
+ if t .json && opts .variant != "" && ! opts .sharded {
349
+ // Rewrite Package in the JSON output to be pkg:variant. For sharded
350
+ // variants, pkg.TestName is already unambiguous, so we don't need to
351
+ // rewrite the Package field.
352
+ if len (opts .pkgs ) != 0 {
353
+ panic ("cannot combine multiple packages with variants" )
354
+ }
355
+ // We only want to process JSON on the child's stdout. Ideally if
356
+ // stdout==stderr, we would also use the same testJSONFilter for
357
+ // cmd.Stdout and cmd.Stderr in order to keep the underlying
358
+ // interleaving of writes, but then it would see even partial writes
359
+ // interleaved, which would corrupt the JSON. So, we only process
360
+ // cmd.Stdout. This has another consequence though: if stdout==stderr,
361
+ // we have to serialize Writes in case the Writer is not concurrent
362
+ // safe. If we were just passing stdout/stderr through to exec, it would
363
+ // do this for us, but since we're wrapping stdout, we have to do it
364
+ // ourselves.
365
+ if stdout == stderr {
366
+ stdout = & lockedWriter {w : stdout }
367
+ stderr = stdout
368
+ }
369
+ cmd .Stdout = & testJSONFilter {w : stdout , variant : opts .variant }
370
+ } else {
371
+ cmd .Stdout = stdout
372
+ }
339
373
cmd .Stderr = stderr
340
374
341
375
return cmd
@@ -403,6 +437,9 @@ func (opts *goTest) buildArgs(t *tester) (goCmd string, build, run, pkgs, testFl
403
437
if opts .cpu != "" {
404
438
run = append (run , "-cpu=" + opts .cpu )
405
439
}
440
+ if t .json {
441
+ run = append (run , "-json" )
442
+ }
406
443
407
444
if opts .gcflags != "" {
408
445
build = append (build , "-gcflags=all=" + opts .gcflags )
@@ -948,7 +985,7 @@ func (t *tester) registerTest(name, heading string, test *goTest, opts ...regist
948
985
if skipFunc != nil {
949
986
msg , skip := skipFunc (dt )
950
987
if skip {
951
- fmt . Println ( msg )
988
+ t . printSkip ( test , msg )
952
989
return nil
953
990
}
954
991
}
@@ -959,6 +996,34 @@ func (t *tester) registerTest(name, heading string, test *goTest, opts ...regist
959
996
})
960
997
}
961
998
999
+ func (t * tester ) printSkip (test * goTest , msg string ) {
1000
+ if ! t .json {
1001
+ fmt .Println (msg )
1002
+ return
1003
+ }
1004
+ type event struct {
1005
+ Time time.Time
1006
+ Action string
1007
+ Package string
1008
+ Output string `json:",omitempty"`
1009
+ }
1010
+ out := json .NewEncoder (os .Stdout )
1011
+ for _ , pkg := range test .packages () {
1012
+ variantName := pkg
1013
+ if test .variant != "" {
1014
+ variantName += ":" + test .variant
1015
+ }
1016
+ ev := event {Time : time .Now (), Package : variantName , Action : "start" }
1017
+ out .Encode (ev )
1018
+ ev .Action = "output"
1019
+ ev .Output = msg
1020
+ out .Encode (ev )
1021
+ ev .Action = "skip"
1022
+ ev .Output = ""
1023
+ out .Encode (ev )
1024
+ }
1025
+ }
1026
+
962
1027
// dirCmd constructs a Cmd intended to be run in the foreground.
963
1028
// The command will be run in dir, and Stdout and Stderr will go to os.Stdout
964
1029
// and os.Stderr.
@@ -1005,6 +1070,9 @@ func (t *tester) iOS() bool {
1005
1070
}
1006
1071
1007
1072
func (t * tester ) out (v string ) {
1073
+ if t .json {
1074
+ return
1075
+ }
1008
1076
if t .banner == "" {
1009
1077
return
1010
1078
}
0 commit comments