### Go command {#go-command}
+The `go build` and `go install` commands now accept a `-json` flag that reports
+build output and failures as structured JSON output on standard output.
+Furthermore, passing `-json` to `go test` now reports build output and failures
+in addition to test results in JSON. For details of the reporting format, see
+`go help buildjson`.
+
### Cgo {#cgo}
Cgo currently refuses to compile calls to a C function which has multiple
// -json
// Convert test output to JSON suitable for automated processing.
// See 'go doc test2json' for the encoding details.
+// Also emits build output in JSON. See 'go help buildjson'.
//
// -o file
// Compile the test binary to the named file.
//
// # Build -json encoding
//
-// The 'go build' and 'go install' commands take a -json flag that reports
-// build output and failures as structured JSON output on standard output.
+// The 'go build', 'go install', and 'go test' commands take a -json flag that
+// reports build output and failures as structured JSON output on standard
+// output.
//
// The JSON stream is a newline-separated sequence of BuildEvent objects
// corresponding to the Go struct:
// }
//
// The ImportPath field gives the package ID of the package being built.
-// This matches the Package.ImportPath field of go list -json.
+// This matches the Package.ImportPath field of go list -json and the
+// TestEvent.FailedBuild field of go test -json. Note that it does not
+// match TestEvent.Package.
//
// The Action field is one of the following:
//
// a given ImportPath. This matches the definition of the TestEvent.Output
// field produced by go test -json.
//
+// For go test -json, this struct is designed so that parsers can distinguish
+// interleaved TestEvents and BuildEvents by inspecting the Action field.
+// Furthermore, as with TestEvent, parsers can simply concatenate the Output
+// fields of all events to reconstruct the text format output, as it would
+// have appeared from go build without the -json flag.
+//
// Note that there may also be non-JSON error text on stdnard error, even
// with the -json flag. Typically, this indicates an early, serious error.
// Consumers should be robust to this.
UsageLine: "buildjson",
Short: "build -json encoding",
Long: `
-The 'go build' and 'go install' commands take a -json flag that reports
-build output and failures as structured JSON output on standard output.
+The 'go build', 'go install', and 'go test' commands take a -json flag that
+reports build output and failures as structured JSON output on standard
+output.
The JSON stream is a newline-separated sequence of BuildEvent objects
corresponding to the Go struct:
}
The ImportPath field gives the package ID of the package being built.
-This matches the Package.ImportPath field of go list -json.
+This matches the Package.ImportPath field of go list -json and the
+TestEvent.FailedBuild field of go test -json. Note that it does not
+match TestEvent.Package.
The Action field is one of the following:
a given ImportPath. This matches the definition of the TestEvent.Output
field produced by go test -json.
+For go test -json, this struct is designed so that parsers can distinguish
+interleaved TestEvents and BuildEvents by inspecting the Action field.
+Furthermore, as with TestEvent, parsers can simply concatenate the Output
+fields of all events to reconstruct the text format output, as it would
+have appeared from go build without the -json flag.
+
Note that there may also be non-JSON error text on stdnard error, even
with the -json flag. Typically, this indicates an early, serious error.
Consumers should be robust to this.
for _, p := range pkgs {
if len(p.TestGoFiles)+len(p.XTestGoFiles) > 0 {
var pmain, ptest, pxtest *load.Package
- var err error
if *listE {
sema.Acquire(ctx, 1)
wg.Add(1)
}
pmain, ptest, pxtest = load.TestPackagesAndErrors(ctx, done, pkgOpts, p, nil)
} else {
- pmain, ptest, pxtest, err = load.TestPackagesFor(ctx, pkgOpts, p, nil)
- if err != nil {
- base.Fatalf("go: can't load test package: %s", err)
+ var perr *load.Package
+ pmain, ptest, pxtest, perr = load.TestPackagesFor(ctx, pkgOpts, p, nil)
+ if perr != nil {
+ base.Fatalf("go: can't load test package: %s", perr.Error)
}
}
testPackages = append(testPackages, testPackageSet{p, pmain, ptest, pxtest})
}
// TestPackagesFor is like TestPackagesAndErrors but it returns
-// an error if the test packages or their dependencies have errors.
+// the package containing an error if the test packages or
+// their dependencies have errors.
// Only test packages without errors are returned.
-func TestPackagesFor(ctx context.Context, opts PackageOpts, p *Package, cover *TestCover) (pmain, ptest, pxtest *Package, err error) {
+func TestPackagesFor(ctx context.Context, opts PackageOpts, p *Package, cover *TestCover) (pmain, ptest, pxtest, perr *Package) {
pmain, ptest, pxtest = TestPackagesAndErrors(ctx, nil, opts, p, cover)
for _, p1 := range []*Package{ptest, pxtest, pmain} {
if p1 == nil {
continue
}
if p1.Error != nil {
- err = p1.Error
+ perr = p1
break
}
if p1.Incomplete {
ps := PackageList([]*Package{p1})
for _, p := range ps {
if p.Error != nil {
- err = p.Error
+ perr = p
break
}
}
if pxtest != nil && (pxtest.Error != nil || pxtest.Incomplete) {
pxtest = nil
}
- return pmain, ptest, pxtest, err
+ return pmain, ptest, pxtest, perr
}
// TestPackagesAndErrors returns three packages:
-json
Convert test output to JSON suitable for automated processing.
See 'go doc test2json' for the encoding details.
+ Also emits build output in JSON. See 'go help buildjson'.
-o file
Compile the test binary to the named file.
// Prepare build + run + print actions for all packages being tested.
for _, p := range pkgs {
- buildTest, runTest, printTest, err := builderTest(b, ctx, pkgOpts, p, allImports[p], writeCoverMetaAct)
+ buildTest, runTest, printTest, perr, err := builderTest(b, ctx, pkgOpts, p, allImports[p], writeCoverMetaAct)
if err != nil {
str := err.Error()
if p.ImportPath != "" {
- base.Errorf("# %s\n%s", p.ImportPath, str)
+ load.DefaultPrinter().Errorf(perr, "# %s\n%s", p.ImportPath, str)
} else {
- base.Errorf("%s", str)
+ load.DefaultPrinter().Errorf(perr, "%s", str)
}
- fmt.Printf("FAIL\t%s [setup failed]\n", p.ImportPath)
+ var stdout io.Writer = os.Stdout
+ if testJSON {
+ json := test2json.NewConverter(stdout, p.ImportPath, test2json.Timestamp)
+ defer func() {
+ json.Exited(err)
+ json.Close()
+ }()
+ json.SetFailedBuild(perr.Desc())
+ stdout = json
+ }
+ fmt.Fprintf(stdout, "FAIL\t%s [setup failed]\n", p.ImportPath)
+ base.SetExitStatus(1)
continue
}
builds = append(builds, buildTest)
"update",
}
-func builderTest(b *work.Builder, ctx context.Context, pkgOpts load.PackageOpts, p *load.Package, imported bool, writeCoverMetaAct *work.Action) (buildAction, runAction, printAction *work.Action, err error) {
+func builderTest(b *work.Builder, ctx context.Context, pkgOpts load.PackageOpts, p *load.Package, imported bool, writeCoverMetaAct *work.Action) (buildAction, runAction, printAction *work.Action, perr *load.Package, err error) {
if len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 {
if cfg.BuildCover && cfg.Experiment.CoverageRedesign {
if p.Internal.Cover.GenMeta {
Package: p,
IgnoreFail: true, // print even if test failed
}
- return build, run, print, nil
+ return build, run, print, nil, nil
}
// Build Package structs describing:
Paths: cfg.BuildCoverPkg,
}
}
- pmain, ptest, pxtest, err := load.TestPackagesFor(ctx, pkgOpts, p, cover)
- if err != nil {
- return nil, nil, nil, err
+ pmain, ptest, pxtest, perr := load.TestPackagesFor(ctx, pkgOpts, p, cover)
+ if perr != nil {
+ return nil, nil, nil, perr, perr.Error
}
// If imported is true then this package is imported by some
testDir := b.NewObjdir()
if err := b.BackgroundShell().Mkdir(testDir); err != nil {
- return nil, nil, nil, err
+ return nil, nil, nil, nil, err
}
pmain.Dir = testDir
// writeTestmain writes _testmain.go,
// using the test description gathered in t.
if err := os.WriteFile(testDir+"_testmain.go", *pmain.Internal.TestmainGo, 0666); err != nil {
- return nil, nil, nil, err
+ return nil, nil, nil, nil, err
}
}
}
}
- return buildAction, runAction, printAction, nil
+ return buildAction, runAction, printAction, nil, nil
}
func addTestVet(b *work.Builder, p *load.Package, runAction, installAction *work.Action) {
var stdout io.Writer = os.Stdout
var err error
+ var json *test2json.Converter
if testJSON {
- json := test2json.NewConverter(lockedStdout{}, a.Package.ImportPath, test2json.Timestamp)
+ json = test2json.NewConverter(lockedStdout{}, a.Package.ImportPath, test2json.Timestamp)
defer func() {
json.Exited(err)
json.Close()
if a.Failed != nil {
// We were unable to build the binary.
+ if json != nil && a.Failed.Package != nil {
+ json.SetFailedBuild(a.Failed.Package.Desc())
+ }
a.Failed = nil
fmt.Fprintf(stdout, "FAIL\t%s [build failed]\n", a.Package.ImportPath)
// Tell the JSON converter that this was a failure, not a passing run.
import (
"cmd/go/internal/base"
+ "cmd/go/internal/cfg"
"cmd/go/internal/cmdflag"
"cmd/go/internal/work"
"errors"
injectedFlags = append(injectedFlags, "-test.v=test2json")
delete(addFromGOFLAGS, "v")
delete(addFromGOFLAGS, "test.v")
+
+ cfg.BuildJSON = true
}
// Inject flags from GOFLAGS before the explicit command-line arguments.
root := &work.Action{Mode: "go vet"}
for _, p := range pkgs {
- _, ptest, pxtest, err := load.TestPackagesFor(ctx, pkgOpts, p, nil)
- if err != nil {
- base.Errorf("%v", err)
+ _, ptest, pxtest, perr := load.TestPackagesFor(ctx, pkgOpts, p, nil)
+ if perr != nil {
+ base.Errorf("%v", perr.Error)
continue
}
if len(ptest.GoFiles) == 0 && len(ptest.CgoFiles) == 0 && pxtest == nil {
--- /dev/null
+[short] skip
+
+# Test a build error directly in a test file.
+! go test -json -o=$devnull ./builderror
+stdout '"ImportPath":"m/builderror \[m/builderror\.test\]","Action":"build-output","Output":"# m/builderror \[m/builderror.test\]\\n"'
+stdout '"ImportPath":"m/builderror \[m/builderror\.test\]","Action":"build-output","Output":"builderror/main_test.go:3:11: undefined: y\\n"'
+stdout '"ImportPath":"m/builderror \[m/builderror\.test\]","Action":"build-fail"'
+stdout '"Action":"start","Package":"m/builderror"'
+stdout '"Action":"output","Package":"m/builderror","Output":"FAIL\\tm/builderror \[build failed\]\\n"'
+stdout '"Action":"fail","Package":"m/builderror","Elapsed":.*,"FailedBuild":"m/builderror \[m/builderror\.test\]"'
+! stderr '.'
+
+# Test a build error in an imported package. Make sure it's attributed to the right package.
+! go test -json -o=$devnull ./builderror2
+stdout '"ImportPath":"m/builderror2/x","Action":"build-output","Output":"# m/builderror2/x\\n"'
+stdout '"ImportPath":"m/builderror2/x","Action":"build-output","Output":"builderror2/x/main.go:3:11: undefined: y\\n"'
+stdout '"ImportPath":"m/builderror2/x","Action":"build-fail"'
+stdout '"Action":"start","Package":"m/builderror2"'
+stdout '"Action":"output","Package":"m/builderror2","Output":"FAIL\\tm/builderror2 \[build failed\]\\n"'
+stdout '"Action":"fail","Package":"m/builderror2","Elapsed":.*,"FailedBuild":"m/builderror2/x"'
+! stderr '.'
+
+# Test a loading error in a test file
+# TODO(#65335): ImportPath attribution is weird
+! go test -json -o=$devnull ./loaderror
+stdout '"ImportPath":"x","Action":"build-output","Output":"# m/loaderror\\n"'
+stdout '"ImportPath":"x","Action":"build-output","Output":".*package x is not in std.*"'
+stdout '"ImportPath":"x","Action":"build-fail"'
+stdout '"Action":"start","Package":"m/loaderror"'
+stdout '"Action":"output","Package":"m/loaderror","Output":"FAIL\\tm/loaderror \[setup failed\]\\n"'
+stdout '"Action":"fail","Package":"m/loaderror","Elapsed":.*,"FailedBuild":"x"'
+! stderr '.'
+
+# Test a vet error
+! go test -json -o=$devnull ./veterror
+stdout '"ImportPath":"m/veterror \[m/veterror.test\]","Action":"build-output","Output":"# m/veterror\\n"'
+stdout '"ImportPath":"m/veterror \[m/veterror.test\]","Action":"build-output","Output":"# \[m/veterror\]\\n"'
+stdout '"ImportPath":"m/veterror \[m/veterror.test\]","Action":"build-output","Output":"veterror/main_test.go:9:9: fmt.Printf format %s reads arg #1, but call has 0 args\\n"'
+stdout '"ImportPath":"m/veterror \[m/veterror.test\]","Action":"build-fail"'
+stdout '"Action":"start","Package":"m/veterror"'
+stdout '"Action":"output","Package":"m/veterror","Output":"FAIL\\tm/veterror \[build failed\]\\n"'
+stdout '"Action":"fail","Package":"m/veterror","Elapsed":.*,"FailedBuild":"m/veterror \[m/veterror.test\]"'
+! stderr '.'
+
+-- go.mod --
+module m
+go 1.21
+-- builderror/main_test.go --
+package builderror
+
+const x = y
+-- builderror2/x/main.go --
+package x
+
+const x = y
+-- builderror2/main_test.go --
+package builderror2
+
+import _ "m/builderror2/x"
+-- loaderror/main_test.go --
+// A bad import causes a "[setup failed]" message from cmd/go because
+// it fails in package graph setup, before it can even get to the
+// build.
+//
+// "[setup failed]" can also occur with various low-level failures in
+// cmd/go, like failing to create a temporary directory.
+
+package loaderror
+
+import _ "x"
+-- veterror/main_test.go --
+package veterror
+
+import (
+ "fmt"
+ "testing"
+)
+
+func TestVetError(t *testing.T) {
+ fmt.Printf("%s")
+}
// event is the JSON struct we emit.
type event struct {
- Time *time.Time `json:",omitempty"`
- Action string
- Package string `json:",omitempty"`
- Test string `json:",omitempty"`
- Elapsed *float64 `json:",omitempty"`
- Output *textBytes `json:",omitempty"`
+ Time *time.Time `json:",omitempty"`
+ Action string
+ Package string `json:",omitempty"`
+ Test string `json:",omitempty"`
+ Elapsed *float64 `json:",omitempty"`
+ Output *textBytes `json:",omitempty"`
+ FailedBuild string `json:",omitempty"`
}
// textBytes is a hack to get JSON to emit a []byte as a string
input lineBuffer // input buffer
output lineBuffer // output buffer
needMarker bool // require ^V marker to introduce test framing line
+
+ // failedBuild is set to the package ID of the cause of a build failure,
+ // if that's what caused this test to fail.
+ failedBuild string
}
// inBuffer and outBuffer are the input and output buffer sizes.
}
}
+// SetFailedBuild sets the package ID that is the root cause of a build failure
+// for this test. This will be reported in the final "fail" event's FailedBuild
+// field.
+func (c *Converter) SetFailedBuild(pkgID string) {
+ c.failedBuild = pkgID
+}
+
const marker = byte(0x16) // ^V
var (
dt := time.Since(c.start).Round(1 * time.Millisecond).Seconds()
e.Elapsed = &dt
}
+ if c.result == "fail" {
+ e.FailedBuild = c.failedBuild
+ }
c.writeEvent(e)
}
return nil
// corresponding to the Go struct:
//
// type TestEvent struct {
-// Time time.Time // encodes as an RFC3339-format string
-// Action string
-// Package string
-// Test string
-// Elapsed float64 // seconds
-// Output string
+// Time time.Time // encodes as an RFC3339-format string
+// Action string
+// Package string
+// Test string
+// Elapsed float64 // seconds
+// Output string
+// FailedBuild string
// }
//
// The Time field holds the time the event happened.
// the concatenation of the Output fields of all output events is the exact
// output of the test execution.
//
+// The FailedBuild field is set for Action == "fail" if the test failure was
+// caused by a build failure. It contains the package ID of the package that
+// failed to build. This matches the ImportPath field of the "go list" output,
+// as well as the BuildEvent.ImportPath field as emitted by "go build -json".
+//
// When a benchmark runs, it typically produces a single line of output
// giving timing results. That line is reported in an event with Action == "output"
// and no Test field. If a benchmark logs output or reports a failure