github.com/thiagoyeds/go-cloud@v0.26.0/internal/testing/test-summary/test-summary.go (about)

     1  // Copyright 2019 The Go Cloud Development Kit Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Summarizes the output of go test.
    16  // Run like so:
    17  //    go test  -json ./... | test-summary
    18  package main
    19  
    20  import (
    21  	"bufio"
    22  	"encoding/json"
    23  	"flag"
    24  	"fmt"
    25  	"io"
    26  	"log"
    27  	"os"
    28  	"path/filepath"
    29  	"strings"
    30  	"time"
    31  )
    32  
    33  var (
    34  	progress = flag.Bool("progress", false, "display test progress")
    35  	verbose  = flag.Bool("verbose", false, "display all test output")
    36  )
    37  
    38  // TestEvent is copied from "go doc test2json".
    39  type TestEvent struct {
    40  	Time    time.Time // encodes as an RFC3339-format string
    41  	Action  string
    42  	Package string
    43  	Test    string
    44  	Elapsed float64 // seconds
    45  	Output  string
    46  }
    47  
    48  func main() {
    49  	flag.Parse()
    50  	s, fails, err := run(os.Stdin)
    51  	if err != nil {
    52  		log.Fatal(err)
    53  	}
    54  	fmt.Println(s)
    55  	if fails {
    56  		os.Exit(1)
    57  	}
    58  }
    59  
    60  func run(r io.Reader) (msg string, failures bool, err error) {
    61  	counts := map[string]int{}
    62  	scanner := bufio.NewScanner(bufio.NewReader(r))
    63  
    64  	// Collects tests that failed.
    65  	var failedTests []string
    66  
    67  	// Stores output produced by each test.
    68  	testOutputs := map[string][]string{}
    69  
    70  	start := time.Now()
    71  	for scanner.Scan() {
    72  		// When the build fails, go test -json doesn't emit a valid JSON value, only
    73  		// a line of output starting with FAIL. Report a more reasonable error in
    74  		// this case.
    75  		if strings.HasPrefix(scanner.Text(), "FAIL") {
    76  			return "", true, fmt.Errorf("No test output: %q", scanner.Text())
    77  		}
    78  
    79  		var event TestEvent
    80  		if err := json.Unmarshal(scanner.Bytes(), &event); err != nil {
    81  			return "", false, fmt.Errorf("%q: %v", scanner.Text(), err)
    82  		}
    83  		testpath := filepath.Join(event.Package, event.Test)
    84  
    85  		// The Test field, if non-empty, specifies the test, example, or benchmark
    86  		// function that caused the event. Events for the overall package test do
    87  		// not set Test.
    88  		if event.Action == "fail" && event.Test != "" {
    89  			failedTests = append(failedTests, testpath)
    90  		}
    91  
    92  		if event.Action == "output" {
    93  			if *verbose {
    94  				fmt.Print(event.Output)
    95  			}
    96  			testOutputs[testpath] = append(testOutputs[testpath], event.Output)
    97  		}
    98  
    99  		// We don't want to count package passes/fails because these don't
   100  		// represent specific tests being run. However, skips of an entire package
   101  		// are not duplicated with individual test skips.
   102  		if event.Test != "" || event.Action == "skip" {
   103  			counts[event.Action]++
   104  		}
   105  
   106  		// For failed tests, print all the output we collected for them before
   107  		// the "fail" event.
   108  		if event.Action == "fail" {
   109  			fmt.Println(strings.Join(testOutputs[testpath], ""))
   110  		}
   111  
   112  		if *progress {
   113  			// Only print progress for fail events for packages and tests, or
   114  			// pass events for packages only (not individual tests, since this is
   115  			// too noisy).
   116  			if event.Action == "fail" || (event.Test == "" && event.Action == "pass") {
   117  				fmt.Printf("%s %s (%.2fs)\n", event.Action, testpath, event.Elapsed)
   118  			}
   119  		}
   120  	}
   121  	if err := scanner.Err(); err != nil {
   122  		return "", false, err
   123  	}
   124  	p := counts["pass"]
   125  	f := counts["fail"]
   126  	s := counts["skip"]
   127  
   128  	summary := fmt.Sprintf("ran %d; passed %d; failed %d; skipped %d (in %.1f sec)", p+f+s, p, f, s, time.Since(start).Seconds())
   129  	if len(failedTests) > 0 {
   130  		var sb strings.Builder
   131  		sb.WriteString("Failures (reporting up to 10):\n")
   132  		for i := 0; i < len(failedTests) && i < 10; i++ {
   133  			fmt.Fprintf(&sb, "  %s\n", failedTests[i])
   134  		}
   135  		if len(failedTests) > 10 {
   136  			sb.WriteString("  ...\n")
   137  		}
   138  		sb.WriteString(summary)
   139  		summary = sb.String()
   140  	}
   141  
   142  	return summary, f > 0, nil
   143  }