github.com/tiagovtristao/plz@v13.4.0+incompatible/src/test/go_results.go (about)

     1  // Parser for output from Go's testing package.
     2  //
     3  // This is a fairly straightforward microformat so pretty easy to parse ourselves.
     4  // There's at least one package out there to convert it to JUnit XML but not worth
     5  // the complexity of getting that installed as a standalone tool.
     6  
     7  package test
     8  
     9  import (
    10  	"bytes"
    11  	"fmt"
    12  	"regexp"
    13  	"strconv"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/thought-machine/please/src/core"
    18  )
    19  
    20  // Not sure what the -6 suffixes are about.
    21  var testStart = regexp.MustCompile(`^=== RUN (.*)(?:-6)?$`)
    22  var testResult = regexp.MustCompile(`^ *--- (PASS|FAIL|SKIP): (.*)(?:-6)? \(([0-9]+\.[0-9]+)s\)$`)
    23  
    24  func parseGoTestResults(data []byte) (core.TestSuite, error) {
    25  	results := core.TestSuite{}
    26  	lines := bytes.Split(data, []byte{'\n'})
    27  	testsStarted := map[string]bool{}
    28  	var suiteDuration time.Duration
    29  	testOutput := make([]string, 0)
    30  	for i, line := range lines {
    31  		testStartMatches := testStart.FindSubmatch(line)
    32  		testResultMatches := testResult.FindSubmatch(line)
    33  		if testStartMatches != nil {
    34  			testsStarted[strings.TrimSpace(string(testStartMatches[1]))] = true
    35  		} else if testResultMatches != nil {
    36  			testName := strings.TrimSpace(string(testResultMatches[2]))
    37  			if !testsStarted[testName] {
    38  				continue
    39  			}
    40  			f, _ := strconv.ParseFloat(string(testResultMatches[3]), 64)
    41  			duration := time.Duration(f * float64(time.Second))
    42  			suiteDuration += duration
    43  			testCase := core.TestCase{
    44  				Name: testName,
    45  			}
    46  			if bytes.Equal(testResultMatches[1], []byte("PASS")) {
    47  				testCase.Executions = append(testCase.Executions, core.TestExecution{
    48  					Duration: &duration,
    49  					Stderr:   strings.Join(testOutput, ""),
    50  				})
    51  			} else if bytes.Equal(testResultMatches[1], []byte("SKIP")) {
    52  				i++ // Following line has the reason for being skipped
    53  				testCase.Executions = append(testCase.Executions, core.TestExecution{
    54  					Skip: &core.TestResultSkip{
    55  						Message: string(bytes.TrimSpace(lines[i])),
    56  					},
    57  					Stderr:   strings.Join(testOutput, ""),
    58  					Duration: &duration,
    59  				})
    60  			} else {
    61  				output := ""
    62  				for j := i + 1; j < len(lines) && !bytes.HasPrefix(lines[j], []byte("===")); j++ {
    63  					output += string(lines[j]) + "\n"
    64  				}
    65  				testCase.Executions = append(testCase.Executions, core.TestExecution{
    66  					Failure: &core.TestResultFailure{
    67  						Traceback: output,
    68  					},
    69  					Stderr:   strings.Join(testOutput, ""),
    70  					Duration: &duration,
    71  				})
    72  			}
    73  			results.TestCases = append(results.TestCases, testCase)
    74  			testOutput = make([]string, 0)
    75  		} else if bytes.Equal(line, []byte("PASS")) {
    76  			// Do nothing, all's well.
    77  		} else if bytes.Equal(line, []byte("FAIL")) {
    78  			if results.Failures() == 0 {
    79  				return results, fmt.Errorf("Test indicated final failure but no failures found yet")
    80  			}
    81  		} else {
    82  			testOutput = append(testOutput, string(line), "\n")
    83  		}
    84  	}
    85  	results.Duration = suiteDuration
    86  	return results, nil
    87  }