blob: 7393bda10863ec0d7e0acb0c4408b0bb7486bd0a [file] [log] [blame] [view]
# Proposal: `-json` flag in `go test`
Author(s): Nodir Turakulov <nodir@google.com>
_With initial input by Russ Cox, Caleb Spare, Andrew Gerrand and Minux Ma._
Last updated: 2015-10-16
Discussion at https://golang.org/issue/2981.
* [Abstract](#abstract)
* [Background](#background)
* [Proposal](#proposal)
* [`testing` package](#testing-package)
* [`go test`](#go-test)
* [Rationale](#rationale)
* [Compatibility](#compatibility)
* [Implementation](#implementation)
## Abstract
Add `-json` flag to `go test`.
When specified, `go test` stdout is JSON.
## Background
There is a clear need in parsing test and benchmark results by third party
tools, see feedback in https://golang.org/issue/2981.
Currently `go test` output format is suited for humans, but not computers.
Also a change to the current format may break existing programs that parse
`go test` output.
Currently, under certain conditions, `go test` streams test/benchmark results
so a user can see them as they happen.
This proposal attempts to preserve streaming capability in the `-json` mode, so
third party tools interpreting `go test` output can stream results too.
`-json` flag was originally proposed by Russ Cox in
https://golang.org/issue/2981 in 2012.
This proposal differs from the original:
* supports streaming
* `go test` JSON output contains unrecognized test binary output.
* minimal changes in `testing` package.
* output is not indented
## Proposal
I propose the following user-visible changes:
* add `-json` flag to `go test`
* `-json`: all `go test` stdout is JSON objects containing
test binary artifacts, separated by newline.
Format below.
* `-json -v`: verbose messages are printed to stderr,
so stdout contains only JSON.
* `-json -n`: not supported
* `-json -x`: not supported
* In `testing` package
* Add `type State` which is enum
* Add `Name`, `Log`, `State` and `Procs` fields to `BenchmarkResult`.
* Add `type CoverageResult`.
* In type `Cover`, change `CoveredPackages` field type from `string` to
`[]string`. This type is not covered by Go 1 compatibility guidelines.
* Add `type JSONResult` for JSON output.
Type definitions and details below.
### `testing` package
```go
// State is one of test/benchmark execution states.
// Implements fmt.Stringer, json.Marshaler and json.Unmarshaler.
type State int
const (
RUN State = iota
PASS
FAIL
SKIP
)
type BenchmarkResult struct {
Name string
State State
Procs int // The value of runtime.GOMAXPROCS for this benchmark run.
Log string // The log created by calling (*B).Log and (*B).Logf.
// existing fields
// make them `json:",omitempty"`
}
// CoverageResult is aggregated code coverage info.
// It is used for `go test` JSON output.
// To get full coverage info, use -coverprofile flag in go test.
type CoverageResult struct {
Mode string
TotalStatements int64
ActiveStatements int64
CoveredPackages []string
}
// JSONResult is used for test binary JSON output format.
//
// Each time a test/benchmark completes, the test binary emits one result
// in JSON format to stdout, surrounded by '\n'.
type JSONResult struct {
// BenchmarkResult contains fields used by both benchmarks and tests,
// such as Name and State.
BenchmarkResult
Coverage *CoverageResult `json:",omitempty"`
}
```
Example of a test binary stdout (JSON output is made indented for the
convenience of the reader. It will be unindented in fact):
```json
{
"Name": "TestFoo",
"State": "RUN",
}
Random string written directly to os.Stdout.
{
"Name": "TestFoo",
"State": "PASS",
"T": 1000000
}
{
"Name": "TestBar",
"State": "RUN",
}
{
"Name": "TestBar",
"State": "PASS",
"T": 1000000,
"Log": "some test output\n"
}
{
"Name": "Example1",
"State": "RUN",
}
{
"Name": "Example1",
"State": "PASS",
"T": 1000000,
}
{
"Name": "BenchmarkBar",
"State": "RUN",
}
{
"Name": "BenchmarkBar",
"State": "PASS",
"T": 1000000,
"N": 1000,
"Bytes": 100,
"MemAllocs": 10,
"MemBytes": 10
}
{
"Coverage": {
"Mode": "set",
"TotalStatements": 1000,
"ActiveStatements": 900,
"CoveredPackages": [
"example.com/foobar"
]
}
}
```
### `go test`
`go test` JSON output format:
```go
// TestResult contains one output line of a test binary.
type TestResult struct {
Package string // package of the test binary.
Result *testing.JSONResult `json:",omitempty"`
Stdout string `json:",omitempty"` // Unrecognized stdout of the test binary.
Stderr string `json:",omitempty"` // Stderr output line of the test binary.
}
```
Example `go test -json` output:
```json
{
"Package": "example.com/foobar",
"Result": {
"Name": "TestFoo",
"State": "Run",
}
}
{
"Package": "example.com/foobar",
"Stdout": "Random string written directly to os.Stdout."
}
{
"Package": "example.com/foobar",
"Result": {
"Name": "TestFoo",
"State": "PASS",
"T": 1000000
}
}
{
"Package": "example.com/foobar",
"Result": {
"Name": "TestBar",
"State": "Run",
}
}
{
"Package": "example.com/foobar",
"Result": {
"Name": "TestBar",
"State": "PASS",
"T": 1000000,
"Log": "some test output\n"
}
}
{
"Package": "example.com/foobar",
"Result": {
"Name": "Example1",
"State": "Run",
}
}
{
"Package": "example.com/foobar",
"Result": {
"Name": "Example1",
"State": "PASS",
"T": 1000000
}
}
{
"Package": "example.com/foobar",
"Result": {
"Name": "BenchmarkBar",
"State": "Run",
}
}
{
"Package": "example.com/foobar",
"Result": {
"Name": "BenchmarkBar",
"State": "PASS",
"Procs": 8,
"T": 1000000,
"N": 1000,
}
}
{
"Package": "example.com/foobar",
"Result": {
"Coverage": {
"Mode": "set",
"TotalStatements": 1000,
"ActiveStatements": 900,
"CoveredPackages": [
"example.com/foobar"
]
}
}
}
```
## Rationale
* A test binary surrounds `testing.JSONResult` JSON with `\n` to handle the
situation when a string without a trailing `\n` is printed directly to
`os.Stdout`.
* A test binary always streams results so we don't loose them if the binary
panics.
Alternatives:
* Add `-format` and `-benchformat` flags proposed in
https://github.com/golang/go/issues/12826. This is simpler to implement
by moving the burden of output parsing to third party programs.
Trade offs:
* `testing.JSONResult` is used for tests, examples and benchmarks.
A third party tool would have to determine the type of the result by the
prefix of `"Name"` property, e.g. tests always start with `"Test"`.
This is a trade off for simplicity of `testing` package API.
Alternatives:
* add `type TestResult`, which together with
`BenchmarkResult` would have duplicated fields, such as `Name`,
`State`, `Procs`, `T`, `Log`.
We cannot add `type CommonResult` with common fields and embed it in
`TestResult` and `BenchmarkResult` because it would break backwards
compatibility of `BenchmarkResult`.
* Duplicate fields in `TestResult` but make `TestResult` and any other
JSON-output-related types internal.
The problem is that third party tool authors would have to write structs
for JSON parsing themselves.
* I propose to make `-json` mutually exclusive with `-n` and `-x` flags.
This is a trade off for `go test` output format simplicity.
Supporting `-json` with `-n`/`-x` flags would require a new field in
`TestResult` that would contain commands that have been run. Note that
we cannot print commands to stdout because stdout must be valid JSON.
Supporting `-json` with `-n`/`-x` flags would also raise the question
whether the field must be specific to commands or it should contain anything
`build.go` prints to stdout.
At this time `-n` and `-x` are the only flags that cause `build.go`
to print to stdout, so we can avoid the problem for now.
If we add more output to `build.go` in future, we can add
`BuildOutput string` field to `TestResult` for arbitrary `build.go` output.
I propose not to add `BuildOutput` now because `-n` affects `go test` too.
For example, `go test -n` prints a command to run the test binary, which
should not be a part of `BuildOutput` (because it is not build).
* `go test` always streams and does not aggregate results into one JSON
object.
This is a trade off for `go test -json` output format simplicity.
## Compatibility
The only backwards incompatibility is changing `testing.Cover.CoveredPackages`
field type, but `testing.Cover` is not covered by Go 1 compatibility
guidelines.
## Implementation
Most of the work would be done by the author of this proposal.
Implementation steps:
1. Add `type State` and add new fields to `testing.BenchmarkResult`.
Modify `testing.(*B).launch` to fill the new fields.
1. Add `-test.json` flag, `type CoverageResult` and `type JSONResult` to the
`testing` package.
Modify `(*T).report`, `RunBenchmarks`, `coverReport` and `runExample`
functions to print JSON if `-test.json` is specified.
If `-test.verbose` is specified, print verbose messages to stderr.
1. Add `-json` flag to `go test`.
If specified, pass `-test.json` to test binaries.
For each line in a test binary output, try to parse it as
`testing.JSONResult`, and print a `TestResult`.
The goal is to get agreement on this proposal and to complete the work
before the 1.6 freeze date.
[testStreamOutput]: https://github.com/golang/go/blob/0b248cea169a261cd0c2db8c014269cca5a170c4/src/cmd/go/test.go#L361-L369