func Parse(r io.Reader, pkgName string) (*Report, error) {
	reader := bufio.NewReader(r)

	report := &Report{make([]Package, 0)}

	// keep track of tests we find
	var tests []*Test

	// keep track of benchmarks we find
	var benchmarks []*Benchmark

	// sum of tests' time, use this if current test has no result line (when it is compiled test)
	var testsTime time.Duration

	// current test
	var cur string

	// keep track if we've already seen a summary for the current test
	var seenSummary bool

	// coverage percentage report for current package
	var coveragePct string

	// stores mapping between package name and output of build failures
	var packageCaptures = map[string][]string{}

	// the name of the package which it's build failure output is being captured
	var capturedPackage string

	// capture any non-test output
	var buffers = map[string][]string{}

	// parse lines
	for {
		l, _, err := reader.ReadLine()
		if err != nil && err == io.EOF {
			break
		} else if err != nil {
			return nil, err
		}

		line := string(l)

		if strings.HasPrefix(line, "=== RUN ") {
			// new test
			cur = strings.TrimSpace(line[8:])
			tests = append(tests, &Test{
				Name:   cur,
				Result: FAIL,
				Output: make([]string, 0),
			})

			// clear the current build package, so output lines won't be added to that build
			capturedPackage = ""
			seenSummary = false
		} else if matches := regexBenchmark.FindStringSubmatch(line); len(matches) == 6 {
			bytes, _ := strconv.Atoi(matches[4])
			allocs, _ := strconv.Atoi(matches[5])

			benchmarks = append(benchmarks, &Benchmark{
				Name:     matches[1],
				Duration: parseNanoseconds(matches[3]),
				Bytes:    bytes,
				Allocs:   allocs,
			})
		} else if strings.HasPrefix(line, "=== PAUSE ") {
			continue
		} else if strings.HasPrefix(line, "=== CONT ") {
			cur = strings.TrimSpace(line[8:])
			continue
		} else if matches := regexResult.FindStringSubmatch(line); len(matches) == 6 {
			if matches[5] != "" {
				coveragePct = matches[5]
			}
			if strings.HasSuffix(matches[4], "failed]") {
				// the build of the package failed, inject a dummy test into the package
				// which indicate about the failure and contain the failure description.
				tests = append(tests, &Test{
					Name:   matches[4],
					Result: FAIL,
					Output: packageCaptures[matches[2]],
				})
			} else if matches[1] == "FAIL" && len(tests) == 0 && len(buffers[cur]) > 0 {
				// This package didn't have any tests, but it failed with some
				// output. Create a dummy test with the output.
				tests = append(tests, &Test{
					Name:   "Failure",
					Result: FAIL,
					Output: buffers[cur],
				})
				buffers[cur] = buffers[cur][0:0]
			}

			// all tests in this package are finished
			report.Packages = append(report.Packages, Package{
				Name:        matches[2],
				Duration:    parseSeconds(matches[3]),
				Tests:       tests,
				Benchmarks:  benchmarks,
				CoveragePct: coveragePct,

				Time: int(parseSeconds(matches[3]) / time.Millisecond), // deprecated
			})

			buffers[cur] = buffers[cur][0:0]
			tests = make([]*Test, 0)
			benchmarks = make([]*Benchmark, 0)
			coveragePct = ""
			cur = ""
			testsTime = 0
		} else if matches := regexStatus.FindStringSubmatch(line); len(matches) == 4 {
			cur = matches[2]
			test := findTest(tests, cur)
			if test == nil {
				continue
			}

			// test status
			if matches[1] == "PASS" {
				test.Result = PASS
			} else if matches[1] == "SKIP" {
				test.Result = SKIP
			} else {
				test.Result = FAIL
			}

			if matches := regexIndent.FindStringSubmatch(line); len(matches) == 2 {
				test.SubtestIndent = matches[1]
			}

			test.Output = buffers[cur]

			test.Name = matches[2]
			test.Duration = parseSeconds(matches[3])
			testsTime += test.Duration

			test.Time = int(test.Duration / time.Millisecond) // deprecated
		} else if matches := regexCoverage.FindStringSubmatch(line); len(matches) == 2 {
			coveragePct = matches[1]
		} else if matches := regexOutput.FindStringSubmatch(line); capturedPackage == "" && len(matches) == 3 {
			// Sub-tests start with one or more series of 4-space indents, followed by a hard tab,
			// followed by the test output
			// Top-level tests start with a hard tab.
			test := findTest(tests, cur)
			if test == nil {
				continue
			}
			test.Output = append(test.Output, matches[2])
		} else if strings.HasPrefix(line, "# ") {
			// indicates a capture of build output of a package. set the current build package.
			capturedPackage = line[2:]
		} else if capturedPackage != "" {
			// current line is build failure capture for the current built package
			packageCaptures[capturedPackage] = append(packageCaptures[capturedPackage], line)
		} else if regexSummary.MatchString(line) {
			// don't store any output after the summary
			seenSummary = true
		} else if !seenSummary {
			// buffer anything else that we didn't recognize
			buffers[cur] = append(buffers[cur], line)

			// if we have a current test, also append to its output
			test := findTest(tests, cur)
			if test != nil {
				if strings.HasPrefix(line, test.SubtestIndent+"    ") {
					test.Output = append(test.Output, strings.TrimPrefix(line, test.SubtestIndent+"    "))
				}
			}
		}
	}

	if len(tests) > 0 {
		// no result line found
		report.Packages = append(report.Packages, Package{
			Name:        pkgName,
			Duration:    testsTime,
			Time:        int(testsTime / time.Millisecond),
			Tests:       tests,
			Benchmarks:  benchmarks,
			CoveragePct: coveragePct,
		})
	}

	return report, nil
}
func Parse(r io.Reader, pkgName string) (*Report, error) {
	reader := bufio.NewReader(r)

	report := &Report{make([]Package, 0)}

	
	var tests []*Test

	
	var benchmarks []*Benchmark

	
	var testsTime time.Duration

	
	var cur string

	
	var seenSummary bool

	
	var coveragePct string

	
	var packageCaptures = map[string][]string{}

	
	var capturedPackage string

	
	var buffers = map[string][]string{}

	
	for {
		l, _, err := reader.ReadLine()
		if err != nil && err == io.EOF {
			break
		} else if err != nil {
			return nil, err
		}

		line := string(l)

		if strings.HasPrefix(line, "=== RUN ") {
			
			cur = strings.TrimSpace(line[8:])
			tests = append(tests, &Test{
				Name:   cur,
				Result: FAIL,
				Output: make([]string, 0),
			})

			
			capturedPackage = ""
			seenSummary = false
		} else if matches := regexBenchmark.FindStringSubmatch(line); len(matches) == 6 {
			bytes, _ := strconv.Atoi(matches[4])
			allocs, _ := strconv.Atoi(matches[5])

			benchmarks = append(benchmarks, &Benchmark{
				Name:     matches[1],
				Duration: parseNanoseconds(matches[3]),
				Bytes:    bytes,
				Allocs:   allocs,
			})
		} else if strings.HasPrefix(line, "=== PAUSE ") {
			continue
		} else if strings.HasPrefix(line, "=== CONT ") {
			cur = strings.TrimSpace(line[8:])
			continue
		} else if matches := regexResult.FindStringSubmatch(line); len(matches) == 6 {
			if matches[5] != "" {
				coveragePct = matches[5]
			}
			if strings.HasSuffix(matches[4], "failed]") {
				
				
				tests = append(tests, &Test{
					Name:   matches[4],
					Result: FAIL,
					Output: packageCaptures[matches[2]],
				})
			} else if matches[1] == "FAIL" && len(tests) == 0 && len(buffers[cur]) > 0 {
				
				
				tests = append(tests, &Test{
					Name:   "Failure",
					Result: FAIL,
					Output: buffers[cur],
				})
				buffers[cur] = buffers[cur][0:0]
			}

			
			report.Packages = append(report.Packages, Package{
				Name:        matches[2],
				Duration:    parseSeconds(matches[3]),
				Tests:       tests,
				Benchmarks:  benchmarks,
				CoveragePct: coveragePct,

				Time: int(parseSeconds(matches[3]) / time.Millisecond), 
			})

			buffers[cur] = buffers[cur][0:0]
			tests = make([]*Test, 0)
			benchmarks = make([]*Benchmark, 0)
			coveragePct = ""
			cur = ""
			testsTime = 0
		} else if matches := regexStatus.FindStringSubmatch(line); len(matches) == 4 {
			cur = matches[2]
			test := findTest(tests, cur)
			if test == nil {
				continue
			}

			
			if matches[1] == "PASS" {
				test.Result = PASS
			} else if matches[1] == "SKIP" {
				test.Result = SKIP
			} else {
				test.Result = FAIL
			}

			if matches := regexIndent.FindStringSubmatch(line); len(matches) == 2 {
				test.SubtestIndent = matches[1]
			}

			test.Output = buffers[cur]

			test.Name = matches[2]
			test.Duration = parseSeconds(matches[3])
			testsTime += test.Duration

			test.Time = int(test.Duration / time.Millisecond) 
		} else if matches := regexCoverage.FindStringSubmatch(line); len(matches) == 2 {
			coveragePct = matches[1]
		} else if matches := regexOutput.FindStringSubmatch(line); capturedPackage == "" && len(matches) == 3 {
			
			
			
			test := findTest(tests, cur)
			if test == nil {
				continue
			}
			test.Output = append(test.Output, matches[2])
		} else if strings.HasPrefix(line, "# ") {
			
			capturedPackage = line[2:]
		} else if capturedPackage != "" {
			
			packageCaptures[capturedPackage] = append(packageCaptures[capturedPackage], line)
		} else if regexSummary.MatchString(line) {
			
			seenSummary = true
		} else if !seenSummary {
			
			buffers[cur] = append(buffers[cur], line)

			
			test := findTest(tests, cur)
			if test != nil {
				if strings.HasPrefix(line, test.SubtestIndent+"    ") {
					test.Output = append(test.Output, strings.TrimPrefix(line, test.SubtestIndent+"    "))
				}
			}
		}
	}

	if len(tests) > 0 {
		
		report.Packages = append(report.Packages, Package{
			Name:        pkgName,
			Duration:    testsTime,
			Time:        int(testsTime / time.Millisecond),
			Tests:       tests,
			Benchmarks:  benchmarks,
			CoveragePct: coveragePct,
		})
	}

	return report, nil
}
func Parse ( r io . Reader , pkgName string ) ( * Report , error ) { reader := bufio . NewReader ( r ) report := & Report { make ( [ ] Package , 0 ) } // keep track of tests we find var tests [ ] * Test // keep track of benchmarks we find var benchmarks [ ] * Benchmark // sum of tests' time, use this if current test has no result line (when it is compiled test) var testsTime time . Duration // current test var cur string // keep track if we've already seen a summary for the current test var seenSummary bool // coverage percentage report for current package var coveragePct string // stores mapping between package name and output of build failures var packageCaptures = map [ string ] [ ] string { } // the name of the package which it's build failure output is being captured var capturedPackage string // capture any non-test output var buffers = map [ string ] [ ] string { } // parse lines for { l , _ , err := reader . ReadLine ( ) if err != nil && err == io . EOF { break } else if err != nil { return nil , err } line := string ( l ) if strings . HasPrefix ( line , " " ) { // new test cur = strings . TrimSpace ( line [ 8 : ] ) tests = append ( tests , & Test { Name : cur , Result : FAIL , Output : make ( [ ] string , 0 ) , } ) // clear the current build package, so output lines won't be added to that build capturedPackage = " " seenSummary = false } else if matches := regexBenchmark . FindStringSubmatch ( line ) ; len ( matches ) == 6 { bytes , _ := strconv . Atoi ( matches [ 4 ] ) allocs , _ := strconv . Atoi ( matches [ 5 ] ) benchmarks = append ( benchmarks , & Benchmark { Name : matches [ 1 ] , Duration : parseNanoseconds ( matches [ 3 ] ) , Bytes : bytes , Allocs : allocs , } ) } else if strings . HasPrefix ( line , " " ) { continue } else if strings . HasPrefix ( line , " " ) { cur = strings . TrimSpace ( line [ 8 : ] ) continue } else if matches := regexResult . FindStringSubmatch ( line ) ; len ( matches ) == 6 { if matches [ 5 ] != " " { coveragePct = matches [ 5 ] } if strings . HasSuffix ( matches [ 4 ] , " " ) { // the build of the package failed, inject a dummy test into the package // which indicate about the failure and contain the failure description. tests = append ( tests , & Test { Name : matches [ 4 ] , Result : FAIL , Output : packageCaptures [ matches [ 2 ] ] , } ) } else if matches [ 1 ] == " " && len ( tests ) == 0 && len ( buffers [ cur ] ) > 0 { // This package didn't have any tests, but it failed with some // output. Create a dummy test with the output. tests = append ( tests , & Test { Name : " " , Result : FAIL , Output : buffers [ cur ] , } ) buffers [ cur ] = buffers [ cur ] [ 0 : 0 ] } // all tests in this package are finished report . Packages = append ( report . Packages , Package { Name : matches [ 2 ] , Duration : parseSeconds ( matches [ 3 ] ) , Tests : tests , Benchmarks : benchmarks , CoveragePct : coveragePct , Time : int ( parseSeconds ( matches [ 3 ] ) / time . Millisecond ) , // deprecated } ) buffers [ cur ] = buffers [ cur ] [ 0 : 0 ] tests = make ( [ ] * Test , 0 ) benchmarks = make ( [ ] * Benchmark , 0 ) coveragePct = " " cur = " " testsTime = 0 } else if matches := regexStatus . FindStringSubmatch ( line ) ; len ( matches ) == 4 { cur = matches [ 2 ] test := findTest ( tests , cur ) if test == nil { continue } // test status if matches [ 1 ] == " " { test . Result = PASS } else if matches [ 1 ] == " " { test . Result = SKIP } else { test . Result = FAIL } if matches := regexIndent . FindStringSubmatch ( line ) ; len ( matches ) == 2 { test . SubtestIndent = matches [ 1 ] } test . Output = buffers [ cur ] test . Name = matches [ 2 ] test . Duration = parseSeconds ( matches [ 3 ] ) testsTime += test . Duration test . Time = int ( test . Duration / time . Millisecond ) // deprecated } else if matches := regexCoverage . FindStringSubmatch ( line ) ; len ( matches ) == 2 { coveragePct = matches [ 1 ] } else if matches := regexOutput . FindStringSubmatch ( line ) ; capturedPackage == " " && len ( matches ) == 3 { // Sub-tests start with one or more series of 4-space indents, followed by a hard tab, // followed by the test output // Top-level tests start with a hard tab. test := findTest ( tests , cur ) if test == nil { continue } test . Output = append ( test . Output , matches [ 2 ] ) } else if strings . HasPrefix ( line , " " ) { // indicates a capture of build output of a package. set the current build package. capturedPackage = line [ 2 : ] } else if capturedPackage != " " { // current line is build failure capture for the current built package packageCaptures [ capturedPackage ] = append ( packageCaptures [ capturedPackage ] , line ) } else if regexSummary . MatchString ( line ) { // don't store any output after the summary seenSummary = true } else if ! seenSummary { // buffer anything else that we didn't recognize buffers [ cur ] = append ( buffers [ cur ] , line ) // if we have a current test, also append to its output test := findTest ( tests , cur ) if test != nil { if strings . HasPrefix ( line , test . SubtestIndent + " " ) { test . Output = append ( test . Output , strings . TrimPrefix ( line , test . SubtestIndent + " " ) ) } } } } if len ( tests ) > 0 { // no result line found report . Packages = append ( report . Packages , Package { Name : pkgName , Duration : testsTime , Time : int ( testsTime / time . Millisecond ) , Tests : tests , Benchmarks : benchmarks , CoveragePct : coveragePct , } ) } return report , nil }
// Parse parses go test output from reader r and returns a report with the
// results. An optional pkgName can be given, which is used in case a package
// result line is missing.
Parse parses go test output from reader r and returns a report with the results . An optional pkgName can be given which is used in case a package result line is missing .