design/2981: benchmark state

Not only a test, but a benchmark can also be failed or skipped.

Change-Id: I1070a890aee4a579e577f6e670a5ed092f256efd
Reviewed-on: https://go-review.googlesource.com/15705
Reviewed-by: Andrew Gerrand <adg@golang.org>
diff --git a/design/2981-go-test-json.md b/design/2981-go-test-json.md
index 9555345..1654aaf 100644
--- a/design/2981-go-test-json.md
+++ b/design/2981-go-test-json.md
@@ -56,14 +56,12 @@
     *   `-json -n`: not supported
     *   `-json -x`: not supported
 *   In `testing` package
-    *   Add `type TestResult` and `type TestState`.
-    *   Add `func Test(f func(*T)) TestResult`
-        to be consistent with `func Benchmark(f func(*B)) BenchmarkResult`.
-        This is not required for JSON output.
-    *   Add `Name`, `Log` and `Procs` fields to `BenchmarkResult`.
+    *   Add `type State` which is enum (`PASS`, `FAIL`, `SKIP`).
+    *   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.
 
@@ -71,33 +69,24 @@
 
 ```go
 
-// TestState is one of terminal test states.
+// State is one of test/benchmark execution states.
 // Implements fmt.Stringer, json.Marshaler and json.Unmarshaler.
-type TestState int
+type State int
 
 const (
-    PASS TestState = iota
+    PASS State = iota
     FAIL
     SKIP
 )
 
-// The results of a test or an example.
-type TestResult struct {
-    Name  string
-    State TestState
-    T     time.Duration // The total time taken.
-    Log   string        // The log created by calling (*T).Log and (*T).Logf.
-}
-
-// Test runs a test function and returns results.
-func Test(f func(*T)) TestResult
-
 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.
@@ -110,14 +99,15 @@
     CoveredPackages  []string
 }
 
-// result is used for test binary JSON output format.
-// It is not added to the public API.
+// JSONResult is used for test binary JSON output format.
 //
 // Each time a test/benchmark completes, the test binary emits one result
 // in unindented JSON format to stdout, surrounded by '\n'.
-type result struct {
-  Test      *TestResult      `json:",omitempty"`
-  Benchmark *BenchmarkResult `json:",omitempty"`
+type JSONResult struct {
+  // BenchmarkResult contains fields used by both benchmarks and tests,
+  // such as Name and State.
+  BenchmarkResult
+
   Coverage  *CoverageResult  `json:",omitempty"`
 }
 ```
@@ -127,38 +117,30 @@
 
 ```json
 {
-    "Test": {
-        "Name": "TestFoo",
-        "State": "PASS",
-        "T": 1000000
-    }
+    "Name": "TestFoo",
+    "State": "PASS",
+    "T": 1000000
 }
 Random string written directly to os.Stdout.
 {
-    "Test": {
-        "Name": "TestBar",
-        "State": "PASS",
-        "T": 1000000,
-        "Log": "some test output\n"
-    }
+    "Name": "TestBar",
+    "State": "PASS",
+    "T": 1000000,
+    "Log": "some test output\n"
 }
 {
-    "Test": {
-        "Name": "Example1",
-        "State": "PASS",
-        "T": 1000000,
-    }
+    "Name": "Example1",
+    "State": "PASS",
+    "T": 1000000,
 }
 {
-    "Benchmark": {
-        "Name": "BenchmarkBar",
-        "State": "PASS",
-        "T": 1000000,
-        "N": 1000,
-        "Bytes": 0,
-        "MemAllocs": 0,
-        "MemBytes": 0
-    }
+    "Name": "BenchmarkBar",
+    "State": "PASS",
+    "T": 1000000,
+    "N": 1000,
+    "Bytes": 100,
+    "MemAllocs": 10,
+    "MemBytes": 10
 }
 {
     "Coverage": {
@@ -181,11 +163,9 @@
 type TestResult struct {
   Package string // package of the test binary.
 
-  Test      *TestResult      `json:",omitempty"`
-  Benchmark *BenchmarkResult `json:",omitempty"`
-  Coverage  *CoverageResult  `json:",omitempty"`
-  Stdout    string           `json:",omitempty"` // Unrecognized stdout of the test binary.
-  Stderr    string           `json:",omitempty"` // Stderr output line 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.
 }
 ```
 
@@ -195,7 +175,7 @@
 ```json
 {
     "Package": "example.com/foobar",
-    "Test": {
+    "Result": {
         "Name": "TestFoo",
         "State": "PASS",
         "T": 1000000
@@ -207,7 +187,7 @@
 }
 {
     "Package": "example.com/foobar",
-    "Test": {
+    "Result": {
         "Name": "TestBar",
         "State": "PASS",
         "T": 1000000,
@@ -216,16 +196,17 @@
 }
 {
     "Package": "example.com/foobar",
-    "Test": {
+    "Result": {
         "Name": "Example1",
         "State": "PASS",
-        "T": 1000000,
+        "T": 1000000
     }
 }
 {
     "Package": "example.com/foobar",
-    "Benchmark": {
+    "Result": {
         "Name": "BenchmarkBar",
+        "State": "PASS",
         "Procs": 8,
         "T": 1000000,
         "N": 1000,
@@ -236,13 +217,15 @@
 }
 {
     "Package": "example.com/foobar",
-    "Coverage": {
-        "Mode": "set",
-        "TotalStatements":  1000,
-        "ActiveStatements": 900,
-        "CoveredPackages": [
-            "example.com/foobar"
-        ]
+    "Result": {
+        "Coverage": {
+            "Mode": "set",
+            "TotalStatements":  1000,
+            "ActiveStatements": 900,
+            "CoveredPackages": [
+                "example.com/foobar"
+            ]
+        }
     }
 }
 
@@ -250,8 +233,9 @@
 
 ## Rationale
 
-*   A test binary surrounds `testing.result` JSON with `\n` to handle situation
-    when a string without a trailing `\n` is printed directly to `os.Stdout`.
+*   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.
 
@@ -263,6 +247,24 @@
 
 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`,
+        `Status`, `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
@@ -298,12 +300,10 @@
 
 Implementation steps:
 
-1.  Add new fields to `testing.BenchmarkResult`.
+1.  Add `type Status` and add new fields to `testing.BenchmarkResult`.
     Modify `testing.(*B).launch` to fill the new fields.
-1.  Add `type TestResult`, `type TestStatus` and
-    `func Test(f func(*T)) TestResult` to package `testing`.
-    Modify `testing.tRunner` to create `TestResult`.
-1.  Add `-test.json` flag and `type CoverageResult` to the `testing` package.
+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.
@@ -311,7 +311,7 @@
     If specified, pass `-test.json` to test binaries.
 
     For each line in a test binary output, try to parse it as
-    `"cmd/go".TestResult` in JSON format, and print a JSON object.
+    `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.