| 143 | 144 |
new file mode 100755 |
| ... | ... |
@@ -0,0 +1,24 @@ |
| 0 |
+#!/bin/bash |
|
| 1 |
+ |
|
| 2 |
+# This command runs any exposed integration tests for the developer tools |
|
| 3 |
+ |
|
| 4 |
+set -o errexit |
|
| 5 |
+set -o nounset |
|
| 6 |
+set -o pipefail |
|
| 7 |
+ |
|
| 8 |
+STARTTIME=$(date +%s) |
|
| 9 |
+OS_ROOT=$(dirname "${BASH_SOURCE}")/..
|
|
| 10 |
+cd "${OS_ROOT}"
|
|
| 11 |
+source "${OS_ROOT}/hack/util.sh"
|
|
| 12 |
+source "${OS_ROOT}/hack/cmd_util.sh"
|
|
| 13 |
+os::log::install_errexit |
|
| 14 |
+ |
|
| 15 |
+for tool in ${OS_ROOT}/tools/*; do
|
|
| 16 |
+ test_file=${tool}/test/integration.sh
|
|
| 17 |
+ if [ -e ${test_file} ]; then
|
|
| 18 |
+ # if the tool exposes an integration test, run it |
|
| 19 |
+ os::cmd::expect_success "${test_file}"
|
|
| 20 |
+ fi |
|
| 21 |
+done |
|
| 22 |
+ |
|
| 23 |
+echo "test-tools: ok" |
|
| 0 | 24 |
\ No newline at end of file |
| 0 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,47 @@ |
| 0 |
+# junitreport |
|
| 1 |
+ |
|
| 2 |
+`junitreport` is a tool that allows for the consumption of test output in order to create jUnit XML. |
|
| 3 |
+ |
|
| 4 |
+## Installation |
|
| 5 |
+ |
|
| 6 |
+In order to build and install `junitreport`, from the root of the OpenShift Origin repository, run `hack/build-go.sh tools/junitreport`. |
|
| 7 |
+ |
|
| 8 |
+## Usage |
|
| 9 |
+ |
|
| 10 |
+`junitreport` can read the output of different types of tests. Specify which output is being read with `--type=<type>`. Supported test output types currently include `'gotest'`, for `go test` output. The default test type is `'gotest'`. |
|
| 11 |
+ |
|
| 12 |
+`junitreport` can output flat or nested test suites. To choose which type of output to use, set `--suites=<type>` to either `'flat'` or `'nested'`. The default suite output structure is `'flat'`. When creating nested test suites, `junitreport` will use `/` as the delimeter between suite names: `github.com/maintainer/repository/suite` will be parsed as a hierarchy of `github.com`, `github.com/maintainer`, *etc.* If you are requesting nested test suite output but do not want the root suite(s) to be as general as `github.com`, for example, set `--roots=<root suite names>` to be a comma-delimited list of the names of the suites you wish to use as roots. If the parser encounters a package outside of those roots, it will ignore it. This allows a user to provide a root suite and only collect data for children of that root from a larger data set. |
|
| 13 |
+ |
|
| 14 |
+Ensure that the output you are feeding `junitreport` is free of extraneous text - any lines that are not test/suite declarations, metadata, or results are interpreted as test output. Text that you do not expect to see in Jenkins, for example, while looking at the output of a failed test should not be included in the input to `junitreport`. |
|
| 15 |
+ |
|
| 16 |
+Currently, `junitreport` does not support the parsing of parallel test output. |
|
| 17 |
+ |
|
| 18 |
+### Examples |
|
| 19 |
+ |
|
| 20 |
+To parse the output of `go test` into a flat collection of test suites: |
|
| 21 |
+ |
|
| 22 |
+```sh |
|
| 23 |
+ |
|
| 24 |
+$ go test -v -cover ./... | junitreport > report.xml |
|
| 25 |
+``` |
|
| 26 |
+ |
|
| 27 |
+To parse the output of `go test` into a nested collection of test suites rooted at `github.com/maintainer`: |
|
| 28 |
+ |
|
| 29 |
+```sh |
|
| 30 |
+ |
|
| 31 |
+$ go test -v -cover ./... | junitreport --suites=nested --roots=github.com/maintainer > report.xml |
|
| 32 |
+``` |
|
| 33 |
+ |
|
| 34 |
+### Testing |
|
| 35 |
+ |
|
| 36 |
+`junitreport` has unit tests as well as integration tests. To run the unit tests from the `junitreport` root directory: |
|
| 37 |
+ |
|
| 38 |
+```sh |
|
| 39 |
+$ go test -v -cover ./... |
|
| 40 |
+``` |
|
| 41 |
+ |
|
| 42 |
+To run the integration tests from the `junitreport` root directory: |
|
| 43 |
+ |
|
| 44 |
+```sh |
|
| 45 |
+$ test/integration.sh |
|
| 46 |
+``` |
|
| 0 | 47 |
\ No newline at end of file |
| 1 | 48 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,134 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "flag" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "io" |
|
| 6 |
+ "os" |
|
| 7 |
+ "strings" |
|
| 8 |
+ |
|
| 9 |
+ "github.com/openshift/origin/tools/junitreport/pkg/cmd" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+var ( |
|
| 13 |
+ // parserType is a flag that holds the type of parser to use |
|
| 14 |
+ parserType string |
|
| 15 |
+ |
|
| 16 |
+ // builderType is a flag that holds the type of builder to use |
|
| 17 |
+ builderType string |
|
| 18 |
+ |
|
| 19 |
+ // rootSuites is a flag that holds the comma-delimited list of root suite names |
|
| 20 |
+ rootSuites string |
|
| 21 |
+ |
|
| 22 |
+ // testOutputFile is a flag that holds the path to the file containing test output |
|
| 23 |
+ testOutputFile string |
|
| 24 |
+) |
|
| 25 |
+ |
|
| 26 |
+const ( |
|
| 27 |
+ defaultParserType = "gotest" |
|
| 28 |
+ defaultBuilderType = "flat" |
|
| 29 |
+ defaultTestOutputFile = "/dev/stdin" |
|
| 30 |
+) |
|
| 31 |
+ |
|
| 32 |
+func init() {
|
|
| 33 |
+ flag.StringVar(&parserType, "type", defaultParserType, "which type of test output to parse") |
|
| 34 |
+ flag.StringVar(&builderType, "suites", defaultBuilderType, "which test suite structure to use") |
|
| 35 |
+ flag.StringVar(&rootSuites, "roots", "", "comma-delimited list of root suite names") |
|
| 36 |
+ flag.StringVar(&testOutputFile, "f", defaultTestOutputFile, "the path to the file containing test output to consume") |
|
| 37 |
+} |
|
| 38 |
+ |
|
| 39 |
+const ( |
|
| 40 |
+ junitReportUsageLong = `Consume test output to create jUnit XML files and summarize jUnit XML files. |
|
| 41 |
+ |
|
| 42 |
+%[1]s consumes test output through Stdin and creates jUnit XML files. Currently, only the output of 'go test' |
|
| 43 |
+is supported. jUnit XML can be build with nested or flat test suites. Sub-trees of test suites can be selected |
|
| 44 |
+when using the nested test-suites representation to only build XML for some subset of the test output. This |
|
| 45 |
+parser is greedy, so all output not directly related to a test suite is considered test case output. |
|
| 46 |
+` |
|
| 47 |
+ |
|
| 48 |
+ junitReportUsage = `Usage: |
|
| 49 |
+ %[1]s [--type=TEST-OUTPUT-TYPE] [--suites=SUITE-TYPE] [-f=FILE] |
|
| 50 |
+ %[1]s [-f=FILE] summarize |
|
| 51 |
+` |
|
| 52 |
+ |
|
| 53 |
+ junitReportExamples = `Examples: |
|
| 54 |
+ # Consume 'go test' output to create a jUnit XML file |
|
| 55 |
+ $ go test -v -cover ./... | %[1]s > report.xml |
|
| 56 |
+ |
|
| 57 |
+ # Consume 'go test' output from a file to create a jUnit XML file |
|
| 58 |
+ $ %[1]s -f testoutput.txt > report.xml |
|
| 59 |
+ |
|
| 60 |
+ # Consume 'go test' output to create a jUnit XML file with nested test suites |
|
| 61 |
+ $ go test -v -cover ./... | junitreport --suites=nested > report.xml |
|
| 62 |
+ |
|
| 63 |
+ # Consume 'go test' output to create a jUnit XML file with nested test suites rooted at 'github.com/maintainer' |
|
| 64 |
+ $ go test -v -cover ./... | junitreport --suites=nested --roots=github.com/maintainer > report.xml |
|
| 65 |
+ |
|
| 66 |
+ # Describe failures and skipped tests in an existing jUnit XML file |
|
| 67 |
+ $ cat report.xml | %[1]s summarize |
|
| 68 |
+` |
|
| 69 |
+) |
|
| 70 |
+ |
|
| 71 |
+func main() {
|
|
| 72 |
+ flag.Usage = func() {
|
|
| 73 |
+ fmt.Fprintf(os.Stderr, junitReportUsageLong+"\n", os.Args[0]) |
|
| 74 |
+ fmt.Fprintf(os.Stderr, junitReportUsage+"\n", os.Args[0]) |
|
| 75 |
+ fmt.Fprintf(os.Stderr, junitReportExamples+"\n", os.Args[0]) |
|
| 76 |
+ fmt.Fprintln(os.Stderr, "Options:") |
|
| 77 |
+ flag.PrintDefaults() |
|
| 78 |
+ os.Exit(2) |
|
| 79 |
+ } |
|
| 80 |
+ |
|
| 81 |
+ flag.Parse() |
|
| 82 |
+ |
|
| 83 |
+ var rootSuiteNames []string |
|
| 84 |
+ if len(rootSuites) > 0 {
|
|
| 85 |
+ rootSuiteNames = strings.Split(rootSuites, ",") |
|
| 86 |
+ } |
|
| 87 |
+ |
|
| 88 |
+ var input io.Reader |
|
| 89 |
+ if testOutputFile == defaultTestOutputFile {
|
|
| 90 |
+ input = os.Stdin |
|
| 91 |
+ } else {
|
|
| 92 |
+ file, err := os.Open(testOutputFile) |
|
| 93 |
+ if err != nil {
|
|
| 94 |
+ fmt.Fprintf(os.Stderr, "Error reading input file: %v\n", err) |
|
| 95 |
+ } |
|
| 96 |
+ defer file.Close() |
|
| 97 |
+ input = file |
|
| 98 |
+ } |
|
| 99 |
+ |
|
| 100 |
+ arguments := flag.Args() |
|
| 101 |
+ // If we are asked to summarize an XML file, that is all we do |
|
| 102 |
+ if len(arguments) == 1 && arguments[0] == "summarize" {
|
|
| 103 |
+ summary, err := cmd.Summarize(input) |
|
| 104 |
+ if err != nil {
|
|
| 105 |
+ fmt.Fprintf(os.Stderr, "Error summarizing jUnit XML file: %v\n", err) |
|
| 106 |
+ os.Exit(1) |
|
| 107 |
+ } |
|
| 108 |
+ fmt.Fprint(os.Stdout, summary) |
|
| 109 |
+ os.Exit(0) |
|
| 110 |
+ } |
|
| 111 |
+ if len(arguments) > 1 {
|
|
| 112 |
+ fmt.Fprintf(os.Stderr, "Incorrect usage of %[1]s, see '%[1]s --help' for more details.\n", os.Args[0]) |
|
| 113 |
+ os.Exit(1) |
|
| 114 |
+ } |
|
| 115 |
+ |
|
| 116 |
+ // Otherwise, we get ready to parse and generate XML output. |
|
| 117 |
+ options := cmd.JUnitReportOptions{
|
|
| 118 |
+ Input: input, |
|
| 119 |
+ Output: os.Stdout, |
|
| 120 |
+ } |
|
| 121 |
+ |
|
| 122 |
+ err := options.Complete(builderType, parserType, rootSuiteNames) |
|
| 123 |
+ if err != nil {
|
|
| 124 |
+ fmt.Fprintf(os.Stderr, "Error parsing flags: %v\n", err) |
|
| 125 |
+ os.Exit(1) |
|
| 126 |
+ } |
|
| 127 |
+ |
|
| 128 |
+ err = options.Run() |
|
| 129 |
+ if err != nil {
|
|
| 130 |
+ fmt.Fprintf(os.Stderr, "Error generating output: %v\n", err) |
|
| 131 |
+ os.Exit(1) |
|
| 132 |
+ } |
|
| 133 |
+} |
| 0 | 134 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,37 @@ |
| 0 |
+package api |
|
| 1 |
+ |
|
| 2 |
+import "fmt" |
|
| 3 |
+ |
|
| 4 |
+// This file implements Stringer for the API types for ease of debugging |
|
| 5 |
+ |
|
| 6 |
+func (t *TestSuites) String() string {
|
|
| 7 |
+ return fmt.Sprintf("Test Suites with suites: %s.", t.Suites)
|
|
| 8 |
+} |
|
| 9 |
+ |
|
| 10 |
+func (t *TestSuite) String() string {
|
|
| 11 |
+ childDescriptions := []string{}
|
|
| 12 |
+ for _, child := range t.Children {
|
|
| 13 |
+ childDescriptions = append(childDescriptions, child.String()) |
|
| 14 |
+ } |
|
| 15 |
+ return fmt.Sprintf("Test Suite %q with properties: %s, %d test cases, of which %d failed and %d were skipped: %s, and children: %s.", t.Name, t.Properties, t.NumTests, t.NumFailed, t.NumSkipped, t.TestCases, childDescriptions)
|
|
| 16 |
+} |
|
| 17 |
+ |
|
| 18 |
+func (t *TestCase) String() string {
|
|
| 19 |
+ var result, message, output string |
|
| 20 |
+ result = "passed" |
|
| 21 |
+ if t.SkipMessage != nil {
|
|
| 22 |
+ result = "skipped" |
|
| 23 |
+ message = t.SkipMessage.Message |
|
| 24 |
+ } |
|
| 25 |
+ if t.FailureOutput != nil {
|
|
| 26 |
+ result = "failed" |
|
| 27 |
+ message = t.FailureOutput.Message |
|
| 28 |
+ output = t.FailureOutput.Output |
|
| 29 |
+ } |
|
| 30 |
+ |
|
| 31 |
+ return fmt.Sprintf("Test Case %q %s after %f seconds with message %q and output %q.", t.Name, result, t.Duration, message, output)
|
|
| 32 |
+} |
|
| 33 |
+ |
|
| 34 |
+func (p *TestSuiteProperty) String() string {
|
|
| 35 |
+ return fmt.Sprintf("%q=%q", p.Name, p.Value)
|
|
| 36 |
+} |
| 0 | 37 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,30 @@ |
| 0 |
+package api |
|
| 1 |
+ |
|
| 2 |
+import "time" |
|
| 3 |
+ |
|
| 4 |
+// SetDuration sets the runtime duration of the test case |
|
| 5 |
+func (t *TestCase) SetDuration(duration string) error {
|
|
| 6 |
+ parsedDuration, err := time.ParseDuration(duration) |
|
| 7 |
+ if err != nil {
|
|
| 8 |
+ return err |
|
| 9 |
+ } |
|
| 10 |
+ |
|
| 11 |
+ // we round to the millisecond on duration |
|
| 12 |
+ t.Duration = float64(int(parsedDuration.Seconds()*1000)) / 1000 |
|
| 13 |
+ return nil |
|
| 14 |
+} |
|
| 15 |
+ |
|
| 16 |
+// MarkSkipped marks the test as skipped with the given message |
|
| 17 |
+func (t *TestCase) MarkSkipped(message string) {
|
|
| 18 |
+ t.SkipMessage = &SkipMessage{
|
|
| 19 |
+ Message: message, |
|
| 20 |
+ } |
|
| 21 |
+} |
|
| 22 |
+ |
|
| 23 |
+// MarkFailed marks the test as failed with the given message and output |
|
| 24 |
+func (t *TestCase) MarkFailed(message, output string) {
|
|
| 25 |
+ t.FailureOutput = &FailureOutput{
|
|
| 26 |
+ Message: message, |
|
| 27 |
+ Output: output, |
|
| 28 |
+ } |
|
| 29 |
+} |
| 0 | 30 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,41 @@ |
| 0 |
+package api |
|
| 1 |
+ |
|
| 2 |
+import "time" |
|
| 3 |
+ |
|
| 4 |
+// AddProperty adds a property to the test suite |
|
| 5 |
+func (t *TestSuite) AddProperty(name, value string) {
|
|
| 6 |
+ t.Properties = append(t.Properties, &TestSuiteProperty{Name: name, Value: value})
|
|
| 7 |
+} |
|
| 8 |
+ |
|
| 9 |
+// AddTestCase adds a test case to the test suite and updates test suite metrics as necessary |
|
| 10 |
+func (t *TestSuite) AddTestCase(testCase *TestCase) {
|
|
| 11 |
+ t.NumTests += 1 |
|
| 12 |
+ |
|
| 13 |
+ if testCase.SkipMessage != nil {
|
|
| 14 |
+ t.NumSkipped += 1 |
|
| 15 |
+ } |
|
| 16 |
+ |
|
| 17 |
+ if testCase.FailureOutput != nil {
|
|
| 18 |
+ t.NumFailed += 1 |
|
| 19 |
+ } |
|
| 20 |
+ |
|
| 21 |
+ t.Duration += testCase.Duration |
|
| 22 |
+ // we round to the millisecond on duration |
|
| 23 |
+ t.Duration = float64(int(t.Duration*1000)) / 1000 |
|
| 24 |
+ |
|
| 25 |
+ t.TestCases = append(t.TestCases, testCase) |
|
| 26 |
+} |
|
| 27 |
+ |
|
| 28 |
+// SetDuration sets the duration of the test suite if this value is not calculated by aggregating the durations |
|
| 29 |
+// of all of the substituent test cases. This should *not* be used if the total duration of the test suite is |
|
| 30 |
+// calculated as that sum, as AddTestCase will handle that case. |
|
| 31 |
+func (t *TestSuite) SetDuration(duration string) error {
|
|
| 32 |
+ parsedDuration, err := time.ParseDuration(duration) |
|
| 33 |
+ if err != nil {
|
|
| 34 |
+ return err |
|
| 35 |
+ } |
|
| 36 |
+ |
|
| 37 |
+ // we round to the millisecond on duration |
|
| 38 |
+ t.Duration = float64(int(parsedDuration.Seconds()*1000)) / 1000 |
|
| 39 |
+ return nil |
|
| 40 |
+} |
| 0 | 41 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,98 @@ |
| 0 |
+package api |
|
| 1 |
+ |
|
| 2 |
+import "encoding/xml" |
|
| 3 |
+ |
|
| 4 |
+// The below types are directly marshalled into XML. The types correspond to jUnit |
|
| 5 |
+// XML schema, but do not contain all valid fields. For instance, the class name |
|
| 6 |
+// field for test cases is omitted, as this concept does not directly apply to Go. |
|
| 7 |
+// For XML specifications see http://help.catchsoftware.com/display/ET/JUnit+Format |
|
| 8 |
+ |
|
| 9 |
+// TestSuites represents a flat collection of jUnit test suites. |
|
| 10 |
+type TestSuites struct {
|
|
| 11 |
+ XMLName xml.Name `xml:"testsuites"` |
|
| 12 |
+ |
|
| 13 |
+ // Suites are the jUnit test suites held in this collection |
|
| 14 |
+ Suites []*TestSuite `xml:"testsuite"` |
|
| 15 |
+} |
|
| 16 |
+ |
|
| 17 |
+// TestSuite represents a single jUnit test suite, potentially holding child suites. |
|
| 18 |
+type TestSuite struct {
|
|
| 19 |
+ XMLName xml.Name `xml:"testsuite"` |
|
| 20 |
+ |
|
| 21 |
+ // Name is the name of the test suite |
|
| 22 |
+ Name string `xml:"name,attr"` |
|
| 23 |
+ |
|
| 24 |
+ // NumTests records the number of tests in the TestSuite |
|
| 25 |
+ NumTests uint `xml:"tests,attr"` |
|
| 26 |
+ |
|
| 27 |
+ // NumSkipped records the number of skipped tests in the suite |
|
| 28 |
+ NumSkipped uint `xml:"skipped,attr"` |
|
| 29 |
+ |
|
| 30 |
+ // NumFailed records the number of failed tests in the suite |
|
| 31 |
+ NumFailed uint `xml:"failures,attr"` |
|
| 32 |
+ |
|
| 33 |
+ // Duration is the time taken in seconds to run all tests in the suite |
|
| 34 |
+ Duration float64 `xml:"time,attr"` |
|
| 35 |
+ |
|
| 36 |
+ // Properties holds other properties of the test suite as a mapping of name to value |
|
| 37 |
+ Properties []*TestSuiteProperty `xml:"properties,omitempty"` |
|
| 38 |
+ |
|
| 39 |
+ // TestCases are the test cases contained in the test suite |
|
| 40 |
+ TestCases []*TestCase `xml:"testcase"` |
|
| 41 |
+ |
|
| 42 |
+ // Children holds nested test suites |
|
| 43 |
+ Children []*TestSuite `xml:"testsuite"` |
|
| 44 |
+} |
|
| 45 |
+ |
|
| 46 |
+// TestSuiteProperty contains a mapping of a property name to a value |
|
| 47 |
+type TestSuiteProperty struct {
|
|
| 48 |
+ XMLName xml.Name `xml:"propery"` |
|
| 49 |
+ |
|
| 50 |
+ Name string `xml:"name,attr"` |
|
| 51 |
+ Value string `xml:"value,attr"` |
|
| 52 |
+} |
|
| 53 |
+ |
|
| 54 |
+// TestCase represents a jUnit test case |
|
| 55 |
+type TestCase struct {
|
|
| 56 |
+ XMLName xml.Name `xml:"testcase"` |
|
| 57 |
+ |
|
| 58 |
+ // Name is the name of the test case |
|
| 59 |
+ Name string `xml:"name,attr"` |
|
| 60 |
+ |
|
| 61 |
+ // Duration is the time taken in seconds to run the test |
|
| 62 |
+ Duration float64 `xml:"time,attr"` |
|
| 63 |
+ |
|
| 64 |
+ // SkipMessage holds the reason why the test was skipped |
|
| 65 |
+ SkipMessage *SkipMessage `xml:"skipped"` |
|
| 66 |
+ |
|
| 67 |
+ // FailureOutput holds the output from a failing test |
|
| 68 |
+ FailureOutput *FailureOutput `xml:"failure"` |
|
| 69 |
+} |
|
| 70 |
+ |
|
| 71 |
+// SkipMessage holds a message explaining why a test was skipped |
|
| 72 |
+type SkipMessage struct {
|
|
| 73 |
+ XMLName xml.Name `xml:"skipped"` |
|
| 74 |
+ |
|
| 75 |
+ // Message explains why the test was skipped |
|
| 76 |
+ Message string `xml:"message,attr"` |
|
| 77 |
+} |
|
| 78 |
+ |
|
| 79 |
+// FailureOutput holds the output from a failing test |
|
| 80 |
+type FailureOutput struct {
|
|
| 81 |
+ XMLName xml.Name `xml:"failure"` |
|
| 82 |
+ |
|
| 83 |
+ // Message holds the failure message from the test |
|
| 84 |
+ Message string `xml:"message,attr"` |
|
| 85 |
+ |
|
| 86 |
+ // Output holds verbose failure output from the test |
|
| 87 |
+ Output string `xml:",chardata"` |
|
| 88 |
+} |
|
| 89 |
+ |
|
| 90 |
+// TestResult is the result of a test case |
|
| 91 |
+type TestResult string |
|
| 92 |
+ |
|
| 93 |
+const ( |
|
| 94 |
+ TestResultPass TestResult = "pass" |
|
| 95 |
+ TestResultSkip TestResult = "skip" |
|
| 96 |
+ TestResultFail TestResult = "fail" |
|
| 97 |
+) |
| 0 | 98 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,30 @@ |
| 0 |
+package flat |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/openshift/origin/tools/junitreport/pkg/api" |
|
| 4 |
+ "github.com/openshift/origin/tools/junitreport/pkg/builder" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+// NewTestSuitesBuilder returns a new flat test suites builder. All test suites consumed |
|
| 8 |
+// by this builder will be added to a flat list of suites - no suites will be children of other suites |
|
| 9 |
+func NewTestSuitesBuilder() builder.TestSuitesBuilder {
|
|
| 10 |
+ return &flatTestSuitesBuilder{
|
|
| 11 |
+ testSuites: &api.TestSuites{},
|
|
| 12 |
+ } |
|
| 13 |
+} |
|
| 14 |
+ |
|
| 15 |
+// flatTestSuitesBuilder is a test suites builder that does not nest suites |
|
| 16 |
+type flatTestSuitesBuilder struct {
|
|
| 17 |
+ testSuites *api.TestSuites |
|
| 18 |
+} |
|
| 19 |
+ |
|
| 20 |
+// AddSuite adds a test suite to the test suites collection being built |
|
| 21 |
+func (b *flatTestSuitesBuilder) AddSuite(suite *api.TestSuite) error {
|
|
| 22 |
+ b.testSuites.Suites = append(b.testSuites.Suites, suite) |
|
| 23 |
+ return nil |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+// Build releases the test suites collection being built at whatever current state it is in |
|
| 27 |
+func (b *flatTestSuitesBuilder) Build() *api.TestSuites {
|
|
| 28 |
+ return b.testSuites |
|
| 29 |
+} |
| 0 | 30 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,75 @@ |
| 0 |
+package flat |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "reflect" |
|
| 4 |
+ "testing" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/openshift/origin/tools/junitreport/pkg/api" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func TestAddSuite(t *testing.T) {
|
|
| 10 |
+ var testCases = []struct {
|
|
| 11 |
+ name string |
|
| 12 |
+ seedSuites *api.TestSuites |
|
| 13 |
+ suitesToAdd []*api.TestSuite |
|
| 14 |
+ expectedSuites *api.TestSuites |
|
| 15 |
+ }{
|
|
| 16 |
+ {
|
|
| 17 |
+ name: "empty", |
|
| 18 |
+ suitesToAdd: []*api.TestSuite{
|
|
| 19 |
+ {
|
|
| 20 |
+ Name: "testSuite", |
|
| 21 |
+ }, |
|
| 22 |
+ }, |
|
| 23 |
+ expectedSuites: &api.TestSuites{
|
|
| 24 |
+ Suites: []*api.TestSuite{
|
|
| 25 |
+ {
|
|
| 26 |
+ Name: "testSuite", |
|
| 27 |
+ }, |
|
| 28 |
+ }, |
|
| 29 |
+ }, |
|
| 30 |
+ }, |
|
| 31 |
+ {
|
|
| 32 |
+ name: "populated", |
|
| 33 |
+ seedSuites: &api.TestSuites{
|
|
| 34 |
+ Suites: []*api.TestSuite{
|
|
| 35 |
+ {
|
|
| 36 |
+ Name: "testSuite", |
|
| 37 |
+ }, |
|
| 38 |
+ }, |
|
| 39 |
+ }, |
|
| 40 |
+ suitesToAdd: []*api.TestSuite{
|
|
| 41 |
+ {
|
|
| 42 |
+ Name: "testSuite2", |
|
| 43 |
+ }, |
|
| 44 |
+ }, |
|
| 45 |
+ expectedSuites: &api.TestSuites{
|
|
| 46 |
+ Suites: []*api.TestSuite{
|
|
| 47 |
+ {
|
|
| 48 |
+ Name: "testSuite", |
|
| 49 |
+ }, |
|
| 50 |
+ {
|
|
| 51 |
+ Name: "testSuite2", |
|
| 52 |
+ }, |
|
| 53 |
+ }, |
|
| 54 |
+ }, |
|
| 55 |
+ }, |
|
| 56 |
+ } |
|
| 57 |
+ |
|
| 58 |
+ for _, testCase := range testCases {
|
|
| 59 |
+ builder := NewTestSuitesBuilder() |
|
| 60 |
+ if testCase.seedSuites != nil {
|
|
| 61 |
+ builder.(*flatTestSuitesBuilder).testSuites = testCase.seedSuites |
|
| 62 |
+ } |
|
| 63 |
+ |
|
| 64 |
+ for _, suite := range testCase.suitesToAdd {
|
|
| 65 |
+ if err := builder.AddSuite(suite); err != nil {
|
|
| 66 |
+ t.Errorf("%s: unexpected error adding test suite: %v", testCase.name, err)
|
|
| 67 |
+ } |
|
| 68 |
+ } |
|
| 69 |
+ |
|
| 70 |
+ if expected, actual := testCase.expectedSuites, builder.Build(); !reflect.DeepEqual(expected, actual) {
|
|
| 71 |
+ t.Errorf("%s: did not correctly add suites:\n\texpected:\n\t%v,\n\tgot\n\t%v", testCase.name, expected, actual)
|
|
| 72 |
+ } |
|
| 73 |
+ } |
|
| 74 |
+} |
| 0 | 75 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,12 @@ |
| 0 |
+package builder |
|
| 1 |
+ |
|
| 2 |
+import "github.com/openshift/origin/tools/junitreport/pkg/api" |
|
| 3 |
+ |
|
| 4 |
+// TestSuitesBuilder knows how to aggregate data to form a collection of test suites. |
|
| 5 |
+type TestSuitesBuilder interface {
|
|
| 6 |
+ // AddSuite adds a test suite to the collection |
|
| 7 |
+ AddSuite(suite *api.TestSuite) error |
|
| 8 |
+ |
|
| 9 |
+ // Build retuns the built structure |
|
| 10 |
+ Build() *api.TestSuites |
|
| 11 |
+} |
| 0 | 12 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,181 @@ |
| 0 |
+package nested |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "strings" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/openshift/origin/tools/junitreport/pkg/api" |
|
| 6 |
+ "github.com/openshift/origin/tools/junitreport/pkg/builder" |
|
| 7 |
+ "github.com/openshift/origin/tools/junitreport/pkg/errors" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+// NewTestSuitesBuilder returns a new nested test suites builder. All test suites consumed by |
|
| 11 |
+// this builder will be added to a multitree of suites rooted at the suites with the given names. |
|
| 12 |
+func NewTestSuitesBuilder(rootSuiteNames []string) builder.TestSuitesBuilder {
|
|
| 13 |
+ rootSuites := []*api.TestSuite{}
|
|
| 14 |
+ for _, name := range rootSuiteNames {
|
|
| 15 |
+ rootSuites = append(rootSuites, &api.TestSuite{Name: name})
|
|
| 16 |
+ } |
|
| 17 |
+ |
|
| 18 |
+ return &nestedTestSuitesBuilder{
|
|
| 19 |
+ restrictedRoots: len(rootSuites) > 0, // i given they are the only roots allowed |
|
| 20 |
+ testSuites: &api.TestSuites{
|
|
| 21 |
+ Suites: rootSuites, |
|
| 22 |
+ }, |
|
| 23 |
+ } |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+const ( |
|
| 27 |
+ // TestSuiteNameDelimiter is the default delimeter for test suite names |
|
| 28 |
+ TestSuiteNameDelimiter = "/" |
|
| 29 |
+) |
|
| 30 |
+ |
|
| 31 |
+// nestedTestSuitesBuilder is a test suites builder that nests suites under a root suite |
|
| 32 |
+type nestedTestSuitesBuilder struct {
|
|
| 33 |
+ // restrictedRoots determines if the builder is able to add new roots to the tree or if all |
|
| 34 |
+ // new suits are to be added only if they are leaves of the original set of roots created |
|
| 35 |
+ // by the constructor |
|
| 36 |
+ restrictedRoots bool |
|
| 37 |
+ |
|
| 38 |
+ testSuites *api.TestSuites |
|
| 39 |
+} |
|
| 40 |
+ |
|
| 41 |
+// AddSuite adds a test suite to the test suites collection being built if the suite is not in |
|
| 42 |
+// the collection, otherwise it overwrites the current record of the suite in the collection. In |
|
| 43 |
+// both cases, it updates the metrics of any parent suites to include those of the new suite. If |
|
| 44 |
+// the parent of the test suite to be added is found in the collection, the test suite is added |
|
| 45 |
+// as a child of that suite. Otherwise, parent suites are created by successively removing one |
|
| 46 |
+// layer of package specificity until the root name is found. For instance, if the suite named |
|
| 47 |
+// "root/package/subpackage/subsubpackage" were to be added to an empty collection, the suites |
|
| 48 |
+// named "root", "root/package", and "root/package/subpackage" would be created and added first, |
|
| 49 |
+// then the suite could be added as a child of the latter parent package. If roots are restricted, |
|
| 50 |
+// then test suites to be added are asssumed to be nested under one of the root suites created by |
|
| 51 |
+// the constructor method and the attempted addition of a suite not rooted in those suites will |
|
| 52 |
+// fail silently to allow for selective tree-building given a root. |
|
| 53 |
+func (b *nestedTestSuitesBuilder) AddSuite(suite *api.TestSuite) error {
|
|
| 54 |
+ if recordedSuite := b.findSuite(suite.Name); recordedSuite != nil {
|
|
| 55 |
+ // if we are trying to add a suite that already exists, we just need to overwrite our |
|
| 56 |
+ // current record with the data in the new suite to be added |
|
| 57 |
+ recordedSuite.NumTests = suite.NumTests |
|
| 58 |
+ recordedSuite.NumSkipped = suite.NumSkipped |
|
| 59 |
+ recordedSuite.NumFailed = suite.NumFailed |
|
| 60 |
+ recordedSuite.Duration = suite.Duration |
|
| 61 |
+ recordedSuite.Properties = suite.Properties |
|
| 62 |
+ recordedSuite.TestCases = suite.TestCases |
|
| 63 |
+ recordedSuite.Children = suite.Children |
|
| 64 |
+ return nil |
|
| 65 |
+ } |
|
| 66 |
+ |
|
| 67 |
+ if err := b.addToParent(suite); err != nil {
|
|
| 68 |
+ if errors.IsSuiteOutOfBoundsError(err) {
|
|
| 69 |
+ // if we were trying to add something out of bounds, we ignore the request but do not |
|
| 70 |
+ // throw an error so we can selectively build sub-trees with a set of specified roots |
|
| 71 |
+ return nil |
|
| 72 |
+ } |
|
| 73 |
+ return err |
|
| 74 |
+ } |
|
| 75 |
+ |
|
| 76 |
+ b.updateMetrics(suite) |
|
| 77 |
+ |
|
| 78 |
+ return nil |
|
| 79 |
+} |
|
| 80 |
+ |
|
| 81 |
+// addToParent will find or create the parent for the test suite and add the given suite as a child |
|
| 82 |
+func (b *nestedTestSuitesBuilder) addToParent(child *api.TestSuite) error {
|
|
| 83 |
+ name := child.Name |
|
| 84 |
+ if !b.isChildOfRoots(name) && b.restrictedRoots {
|
|
| 85 |
+ // if we were asked to add a new test suite that isn't a child of any current root, |
|
| 86 |
+ // and we aren't allowed to add new roots, we can't fulfill this request |
|
| 87 |
+ return errors.NewSuiteOutOfBoundsError(name) |
|
| 88 |
+ } |
|
| 89 |
+ |
|
| 90 |
+ parentName := getParentName(name) |
|
| 91 |
+ if len(parentName) == 0 {
|
|
| 92 |
+ // this suite does not have a parent, we just need to add it as a root |
|
| 93 |
+ b.testSuites.Suites = append(b.testSuites.Suites, child) |
|
| 94 |
+ return nil |
|
| 95 |
+ } |
|
| 96 |
+ |
|
| 97 |
+ parent := b.findSuite(parentName) |
|
| 98 |
+ if parent == nil {
|
|
| 99 |
+ // no parent is currently registered, we need to create it and add it to the tree |
|
| 100 |
+ parent = &api.TestSuite{
|
|
| 101 |
+ Name: parentName, |
|
| 102 |
+ Children: []*api.TestSuite{child},
|
|
| 103 |
+ } |
|
| 104 |
+ |
|
| 105 |
+ return b.addToParent(parent) |
|
| 106 |
+ } |
|
| 107 |
+ |
|
| 108 |
+ parent.Children = append(parent.Children, child) |
|
| 109 |
+ return nil |
|
| 110 |
+} |
|
| 111 |
+ |
|
| 112 |
+// getParentName returns the name of the parent package, if it exists in the multitree |
|
| 113 |
+func getParentName(name string) string {
|
|
| 114 |
+ if !strings.Contains(name, TestSuiteNameDelimiter) {
|
|
| 115 |
+ return "" |
|
| 116 |
+ } |
|
| 117 |
+ |
|
| 118 |
+ delimeterIndex := strings.LastIndex(name, TestSuiteNameDelimiter) |
|
| 119 |
+ return name[0:delimeterIndex] |
|
| 120 |
+} |
|
| 121 |
+ |
|
| 122 |
+func (b *nestedTestSuitesBuilder) isChildOfRoots(name string) bool {
|
|
| 123 |
+ for _, rootSuite := range b.testSuites.Suites {
|
|
| 124 |
+ if strings.HasPrefix(name, rootSuite.Name) {
|
|
| 125 |
+ return true |
|
| 126 |
+ } |
|
| 127 |
+ } |
|
| 128 |
+ return false |
|
| 129 |
+} |
|
| 130 |
+ |
|
| 131 |
+// findSuite finds a test suite in a collection of test suites |
|
| 132 |
+func (b *nestedTestSuitesBuilder) findSuite(name string) *api.TestSuite {
|
|
| 133 |
+ return findSuite(b.testSuites.Suites, name) |
|
| 134 |
+} |
|
| 135 |
+ |
|
| 136 |
+// findSuite walks a test suite tree to find a test suite with the given name |
|
| 137 |
+func findSuite(suites []*api.TestSuite, name string) *api.TestSuite {
|
|
| 138 |
+ for _, suite := range suites {
|
|
| 139 |
+ if suite.Name == name {
|
|
| 140 |
+ return suite |
|
| 141 |
+ } |
|
| 142 |
+ |
|
| 143 |
+ if strings.HasPrefix(name, suite.Name) {
|
|
| 144 |
+ return findSuite(suite.Children, name) |
|
| 145 |
+ } |
|
| 146 |
+ } |
|
| 147 |
+ |
|
| 148 |
+ return nil |
|
| 149 |
+} |
|
| 150 |
+ |
|
| 151 |
+// updateMetrics updates the metrics for all parents of a test suite |
|
| 152 |
+func (b *nestedTestSuitesBuilder) updateMetrics(newSuite *api.TestSuite) {
|
|
| 153 |
+ updateMetrics(b.testSuites.Suites, newSuite) |
|
| 154 |
+} |
|
| 155 |
+ |
|
| 156 |
+// updateMetrics walks a test suite tree to update metrics of parents of the given suite |
|
| 157 |
+func updateMetrics(suites []*api.TestSuite, newSuite *api.TestSuite) {
|
|
| 158 |
+ for _, suite := range suites {
|
|
| 159 |
+ if suite.Name == newSuite.Name || !strings.HasPrefix(newSuite.Name, suite.Name) {
|
|
| 160 |
+ // if we're considering the suite itself or another suite that is not a pure |
|
| 161 |
+ // prefix of the new suite, we are not considering a parent suite and therefore |
|
| 162 |
+ // do not need to update any metrics |
|
| 163 |
+ continue |
|
| 164 |
+ } |
|
| 165 |
+ |
|
| 166 |
+ suite.NumTests += newSuite.NumTests |
|
| 167 |
+ suite.NumSkipped += newSuite.NumSkipped |
|
| 168 |
+ suite.NumFailed += newSuite.NumFailed |
|
| 169 |
+ suite.Duration += newSuite.Duration |
|
| 170 |
+ // we round to the millisecond on duration |
|
| 171 |
+ suite.Duration = float64(int(suite.Duration*1000)) / 1000 |
|
| 172 |
+ |
|
| 173 |
+ updateMetrics(suite.Children, newSuite) |
|
| 174 |
+ } |
|
| 175 |
+} |
|
| 176 |
+ |
|
| 177 |
+// Build releases the test suites collection being built at whatever current state it is in |
|
| 178 |
+func (b *nestedTestSuitesBuilder) Build() *api.TestSuites {
|
|
| 179 |
+ return b.testSuites |
|
| 180 |
+} |
| 0 | 181 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,182 @@ |
| 0 |
+package nested |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "reflect" |
|
| 4 |
+ "testing" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/openshift/origin/tools/junitreport/pkg/api" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func TestGetParentName(t *testing.T) {
|
|
| 10 |
+ var testCases = []struct {
|
|
| 11 |
+ name string |
|
| 12 |
+ testName string |
|
| 13 |
+ expectedParentName string |
|
| 14 |
+ }{
|
|
| 15 |
+ {
|
|
| 16 |
+ name: "no parent", |
|
| 17 |
+ testName: "root", |
|
| 18 |
+ expectedParentName: "", |
|
| 19 |
+ }, |
|
| 20 |
+ {
|
|
| 21 |
+ name: "one parent", |
|
| 22 |
+ testName: "root/package", |
|
| 23 |
+ expectedParentName: "root", |
|
| 24 |
+ }, |
|
| 25 |
+ {
|
|
| 26 |
+ name: "many parents", |
|
| 27 |
+ testName: "root/package/subpackage/etc", |
|
| 28 |
+ expectedParentName: "root/package/subpackage", |
|
| 29 |
+ }, |
|
| 30 |
+ } |
|
| 31 |
+ |
|
| 32 |
+ for _, testCase := range testCases {
|
|
| 33 |
+ if actual, expected := getParentName(testCase.testName), testCase.expectedParentName; actual != expected {
|
|
| 34 |
+ t.Errorf("%s: did not get correct parent name for test name: expected: %q, got %q", testCase.name, expected, actual)
|
|
| 35 |
+ } |
|
| 36 |
+ } |
|
| 37 |
+} |
|
| 38 |
+ |
|
| 39 |
+func TestAddSuite(t *testing.T) {
|
|
| 40 |
+ var testCases = []struct {
|
|
| 41 |
+ name string |
|
| 42 |
+ rootSuiteNames []string |
|
| 43 |
+ seedSuites *api.TestSuites |
|
| 44 |
+ suiteToAdd *api.TestSuite |
|
| 45 |
+ expectedSuites *api.TestSuites |
|
| 46 |
+ }{
|
|
| 47 |
+ {
|
|
| 48 |
+ name: "empty adding root", |
|
| 49 |
+ suiteToAdd: &api.TestSuite{
|
|
| 50 |
+ Name: "root", |
|
| 51 |
+ }, |
|
| 52 |
+ expectedSuites: &api.TestSuites{
|
|
| 53 |
+ Suites: []*api.TestSuite{
|
|
| 54 |
+ {
|
|
| 55 |
+ Name: "root", |
|
| 56 |
+ }, |
|
| 57 |
+ }, |
|
| 58 |
+ }, |
|
| 59 |
+ }, |
|
| 60 |
+ {
|
|
| 61 |
+ name: "empty adding child", |
|
| 62 |
+ suiteToAdd: &api.TestSuite{
|
|
| 63 |
+ Name: "root/child", |
|
| 64 |
+ }, |
|
| 65 |
+ expectedSuites: &api.TestSuites{
|
|
| 66 |
+ Suites: []*api.TestSuite{
|
|
| 67 |
+ {
|
|
| 68 |
+ Name: "root", |
|
| 69 |
+ Children: []*api.TestSuite{
|
|
| 70 |
+ {
|
|
| 71 |
+ Name: "root/child", |
|
| 72 |
+ }, |
|
| 73 |
+ }, |
|
| 74 |
+ }, |
|
| 75 |
+ }, |
|
| 76 |
+ }, |
|
| 77 |
+ }, |
|
| 78 |
+ {
|
|
| 79 |
+ name: "empty with bounds, adding out of bounds", |
|
| 80 |
+ rootSuiteNames: []string{"someotherroot"},
|
|
| 81 |
+ suiteToAdd: &api.TestSuite{
|
|
| 82 |
+ Name: "root/child", |
|
| 83 |
+ }, |
|
| 84 |
+ expectedSuites: &api.TestSuites{
|
|
| 85 |
+ Suites: []*api.TestSuite{
|
|
| 86 |
+ {
|
|
| 87 |
+ Name: "someotherroot", |
|
| 88 |
+ }, |
|
| 89 |
+ }, |
|
| 90 |
+ }, |
|
| 91 |
+ }, |
|
| 92 |
+ {
|
|
| 93 |
+ name: "populated adding child", |
|
| 94 |
+ seedSuites: &api.TestSuites{
|
|
| 95 |
+ Suites: []*api.TestSuite{
|
|
| 96 |
+ {
|
|
| 97 |
+ Name: "root", |
|
| 98 |
+ }, |
|
| 99 |
+ }, |
|
| 100 |
+ }, |
|
| 101 |
+ suiteToAdd: &api.TestSuite{
|
|
| 102 |
+ Name: "root/child", |
|
| 103 |
+ }, |
|
| 104 |
+ expectedSuites: &api.TestSuites{
|
|
| 105 |
+ Suites: []*api.TestSuite{
|
|
| 106 |
+ {
|
|
| 107 |
+ Name: "root", |
|
| 108 |
+ Children: []*api.TestSuite{
|
|
| 109 |
+ {
|
|
| 110 |
+ Name: "root/child", |
|
| 111 |
+ }, |
|
| 112 |
+ }, |
|
| 113 |
+ }, |
|
| 114 |
+ }, |
|
| 115 |
+ }, |
|
| 116 |
+ }, |
|
| 117 |
+ {
|
|
| 118 |
+ name: "empty with bounds, adding in bounds", |
|
| 119 |
+ rootSuiteNames: []string{"root"},
|
|
| 120 |
+ suiteToAdd: &api.TestSuite{
|
|
| 121 |
+ Name: "root/child/grandchild", |
|
| 122 |
+ }, |
|
| 123 |
+ expectedSuites: &api.TestSuites{
|
|
| 124 |
+ Suites: []*api.TestSuite{
|
|
| 125 |
+ {
|
|
| 126 |
+ Name: "root", |
|
| 127 |
+ Children: []*api.TestSuite{
|
|
| 128 |
+ {
|
|
| 129 |
+ Name: "root/child", |
|
| 130 |
+ Children: []*api.TestSuite{
|
|
| 131 |
+ {
|
|
| 132 |
+ Name: "root/child/grandchild", |
|
| 133 |
+ }, |
|
| 134 |
+ }, |
|
| 135 |
+ }, |
|
| 136 |
+ }, |
|
| 137 |
+ }, |
|
| 138 |
+ }, |
|
| 139 |
+ }, |
|
| 140 |
+ }, |
|
| 141 |
+ {
|
|
| 142 |
+ name: "populated overwriting record", |
|
| 143 |
+ seedSuites: &api.TestSuites{
|
|
| 144 |
+ Suites: []*api.TestSuite{
|
|
| 145 |
+ {
|
|
| 146 |
+ Name: "root", |
|
| 147 |
+ NumTests: 3, |
|
| 148 |
+ }, |
|
| 149 |
+ }, |
|
| 150 |
+ }, |
|
| 151 |
+ suiteToAdd: &api.TestSuite{
|
|
| 152 |
+ Name: "root", |
|
| 153 |
+ NumTests: 4, |
|
| 154 |
+ }, |
|
| 155 |
+ expectedSuites: &api.TestSuites{
|
|
| 156 |
+ Suites: []*api.TestSuite{
|
|
| 157 |
+ {
|
|
| 158 |
+ Name: "root", |
|
| 159 |
+ NumTests: 4, |
|
| 160 |
+ }, |
|
| 161 |
+ }, |
|
| 162 |
+ }, |
|
| 163 |
+ }, |
|
| 164 |
+ } |
|
| 165 |
+ |
|
| 166 |
+ for _, testCase := range testCases {
|
|
| 167 |
+ builder := NewTestSuitesBuilder(testCase.rootSuiteNames) |
|
| 168 |
+ |
|
| 169 |
+ if testCase.seedSuites != nil {
|
|
| 170 |
+ builder.(*nestedTestSuitesBuilder).testSuites = testCase.seedSuites |
|
| 171 |
+ } |
|
| 172 |
+ |
|
| 173 |
+ if err := builder.AddSuite(testCase.suiteToAdd); err != nil {
|
|
| 174 |
+ t.Errorf("%s: unexpected error adding suite to suites: %v", testCase.name, err)
|
|
| 175 |
+ } |
|
| 176 |
+ |
|
| 177 |
+ if actual, expected := builder.Build(), testCase.expectedSuites; !reflect.DeepEqual(actual, expected) {
|
|
| 178 |
+ t.Errorf("%s: did not get correct test suites after addition of test suite:\n\texpected:\n\t%s,\n\tgot\n\t%s", testCase.name, expected, actual)
|
|
| 179 |
+ } |
|
| 180 |
+ } |
|
| 181 |
+} |
| 0 | 182 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,112 @@ |
| 0 |
+package cmd |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bufio" |
|
| 4 |
+ "encoding/xml" |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "io" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/openshift/origin/tools/junitreport/pkg/builder" |
|
| 9 |
+ "github.com/openshift/origin/tools/junitreport/pkg/builder/flat" |
|
| 10 |
+ "github.com/openshift/origin/tools/junitreport/pkg/builder/nested" |
|
| 11 |
+ "github.com/openshift/origin/tools/junitreport/pkg/parser" |
|
| 12 |
+ "github.com/openshift/origin/tools/junitreport/pkg/parser/gotest" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+type testSuitesBuilderType string |
|
| 16 |
+ |
|
| 17 |
+const ( |
|
| 18 |
+ flatBuilderType testSuitesBuilderType = "flat" |
|
| 19 |
+ nestedBuilderType testSuitesBuilderType = "nested" |
|
| 20 |
+) |
|
| 21 |
+ |
|
| 22 |
+var supportedBuilderTypes = []testSuitesBuilderType{flatBuilderType, nestedBuilderType}
|
|
| 23 |
+ |
|
| 24 |
+type testParserType string |
|
| 25 |
+ |
|
| 26 |
+const ( |
|
| 27 |
+ goTestParserType testParserType = "gotest" |
|
| 28 |
+) |
|
| 29 |
+ |
|
| 30 |
+var supportedTestParserTypes = []testParserType{goTestParserType}
|
|
| 31 |
+ |
|
| 32 |
+type JUnitReportOptions struct {
|
|
| 33 |
+ // BuilderType is the type of test suites builder to use |
|
| 34 |
+ BuilderType testSuitesBuilderType |
|
| 35 |
+ |
|
| 36 |
+ // RootSuiteNames is a list of root suites to be used for nested test suite output if |
|
| 37 |
+ // the root suite is to be more specific than the suite name without any suite delimeters |
|
| 38 |
+ // i.e. if `github.com/owner/repo` is to be used instead of `github.com` |
|
| 39 |
+ RootSuiteNames []string |
|
| 40 |
+ |
|
| 41 |
+ // ParserType is the parser type that will be used to parse test output |
|
| 42 |
+ ParserType testParserType |
|
| 43 |
+ |
|
| 44 |
+ // Input is the reader for the test output to be parsed |
|
| 45 |
+ Input io.Reader |
|
| 46 |
+ |
|
| 47 |
+ // Output is the writer for the file to which the XML is written |
|
| 48 |
+ Output io.Writer |
|
| 49 |
+} |
|
| 50 |
+ |
|
| 51 |
+func (o *JUnitReportOptions) Complete(builderType, parserType string, rootSuiteNames []string) error {
|
|
| 52 |
+ switch testSuitesBuilderType(builderType) {
|
|
| 53 |
+ case flatBuilderType: |
|
| 54 |
+ o.BuilderType = flatBuilderType |
|
| 55 |
+ case nestedBuilderType: |
|
| 56 |
+ o.BuilderType = nestedBuilderType |
|
| 57 |
+ default: |
|
| 58 |
+ return fmt.Errorf("unrecognized test suites builder type: got %s, expected one of %v", builderType, supportedBuilderTypes)
|
|
| 59 |
+ } |
|
| 60 |
+ |
|
| 61 |
+ switch testParserType(parserType) {
|
|
| 62 |
+ case goTestParserType: |
|
| 63 |
+ o.ParserType = goTestParserType |
|
| 64 |
+ default: |
|
| 65 |
+ return fmt.Errorf("unrecognized test parser type: got %s, expected one of %v", parserType, supportedTestParserTypes)
|
|
| 66 |
+ } |
|
| 67 |
+ |
|
| 68 |
+ o.RootSuiteNames = rootSuiteNames |
|
| 69 |
+ |
|
| 70 |
+ return nil |
|
| 71 |
+} |
|
| 72 |
+ |
|
| 73 |
+func (o *JUnitReportOptions) Run() error {
|
|
| 74 |
+ var builder builder.TestSuitesBuilder |
|
| 75 |
+ switch o.BuilderType {
|
|
| 76 |
+ case flatBuilderType: |
|
| 77 |
+ builder = flat.NewTestSuitesBuilder() |
|
| 78 |
+ case nestedBuilderType: |
|
| 79 |
+ builder = nested.NewTestSuitesBuilder(o.RootSuiteNames) |
|
| 80 |
+ } |
|
| 81 |
+ |
|
| 82 |
+ var testParser parser.TestOutputParser |
|
| 83 |
+ switch o.ParserType {
|
|
| 84 |
+ case goTestParserType: |
|
| 85 |
+ testParser = gotest.NewParser(builder) |
|
| 86 |
+ } |
|
| 87 |
+ |
|
| 88 |
+ testSuites, err := testParser.Parse(bufio.NewScanner(o.Input)) |
|
| 89 |
+ if err != nil {
|
|
| 90 |
+ return err |
|
| 91 |
+ } |
|
| 92 |
+ |
|
| 93 |
+ _, err = io.WriteString(o.Output, xml.Header) |
|
| 94 |
+ if err != nil {
|
|
| 95 |
+ return fmt.Errorf("error writing XML header to file: %v", err)
|
|
| 96 |
+ } |
|
| 97 |
+ |
|
| 98 |
+ encoder := xml.NewEncoder(o.Output) |
|
| 99 |
+ encoder.Indent("", "\t") // no prefix, indent with tabs
|
|
| 100 |
+ |
|
| 101 |
+ if err := encoder.Encode(testSuites); err != nil {
|
|
| 102 |
+ return fmt.Errorf("error encoding test suites to XML: %v", err)
|
|
| 103 |
+ } |
|
| 104 |
+ |
|
| 105 |
+ _, err = io.WriteString(o.Output, "\n") |
|
| 106 |
+ if err != nil {
|
|
| 107 |
+ return fmt.Errorf("error writing last newline to file: %v", err)
|
|
| 108 |
+ } |
|
| 109 |
+ |
|
| 110 |
+ return nil |
|
| 111 |
+} |
| 0 | 112 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,56 @@ |
| 0 |
+package cmd |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "encoding/xml" |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "io" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/openshift/origin/tools/junitreport/pkg/api" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+// Summarize reads the input into a TestSuites structure and summarizes the tests contained within, |
|
| 12 |
+// bringing attention to tests that did not succeed. |
|
| 13 |
+func Summarize(input io.Reader) (string, error) {
|
|
| 14 |
+ var testSuites api.TestSuites |
|
| 15 |
+ if err := xml.NewDecoder(input).Decode(&testSuites); err != nil {
|
|
| 16 |
+ return "", err |
|
| 17 |
+ } |
|
| 18 |
+ |
|
| 19 |
+ var summary bytes.Buffer |
|
| 20 |
+ var numTests, numFailed, numSkipped uint |
|
| 21 |
+ var duration float64 |
|
| 22 |
+ for _, testSuite := range testSuites.Suites {
|
|
| 23 |
+ numTests += testSuite.NumTests |
|
| 24 |
+ numFailed += testSuite.NumFailed |
|
| 25 |
+ numSkipped += testSuite.NumSkipped |
|
| 26 |
+ duration += testSuite.Duration |
|
| 27 |
+ } |
|
| 28 |
+ |
|
| 29 |
+ verb := "were" |
|
| 30 |
+ if numSkipped == 1 {
|
|
| 31 |
+ verb = "was" |
|
| 32 |
+ } |
|
| 33 |
+ summary.WriteString(fmt.Sprintf("Of %d tests executed in %.3fs, %d succeeded, %d failed, and %d %s skipped.\n\n", numTests, duration, (numTests - numFailed - numSkipped), numFailed, numSkipped, verb))
|
|
| 34 |
+ |
|
| 35 |
+ for _, testSuite := range testSuites.Suites {
|
|
| 36 |
+ summarizeTests(testSuite, &summary) |
|
| 37 |
+ } |
|
| 38 |
+ |
|
| 39 |
+ return summary.String(), nil |
|
| 40 |
+} |
|
| 41 |
+ |
|
| 42 |
+func summarizeTests(testSuite *api.TestSuite, summary *bytes.Buffer) {
|
|
| 43 |
+ for _, testCase := range testSuite.TestCases {
|
|
| 44 |
+ if testCase.FailureOutput != nil {
|
|
| 45 |
+ summary.WriteString(fmt.Sprintf("In suite %q, test case %q failed:\n%s\n\n", testSuite.Name, testCase.Name, testCase.FailureOutput.Output))
|
|
| 46 |
+ } |
|
| 47 |
+ if testCase.SkipMessage != nil {
|
|
| 48 |
+ summary.WriteString(fmt.Sprintf("In suite %q, test case %q was skipped:\n%s\n\n", testSuite.Name, testCase.Name, testCase.SkipMessage.Message))
|
|
| 49 |
+ } |
|
| 50 |
+ } |
|
| 51 |
+ |
|
| 52 |
+ for _, childSuite := range testSuite.Children {
|
|
| 53 |
+ summarizeTests(childSuite, summary) |
|
| 54 |
+ } |
|
| 55 |
+} |
| 0 | 56 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,31 @@ |
| 0 |
+package errors |
|
| 1 |
+ |
|
| 2 |
+import "fmt" |
|
| 3 |
+ |
|
| 4 |
+// NewSuiteOutOfBoundsError returns a new SuiteOutOfBounds error for the given suite name |
|
| 5 |
+func NewSuiteOutOfBoundsError(name string) error {
|
|
| 6 |
+ return &suiteOutOfBoundsError{
|
|
| 7 |
+ suiteName: name, |
|
| 8 |
+ } |
|
| 9 |
+} |
|
| 10 |
+ |
|
| 11 |
+// suiteOutOfBoundsError describes the failure to place a test suite into a test suite tree because the suite |
|
| 12 |
+// in question is not a child of any suite in the tree |
|
| 13 |
+type suiteOutOfBoundsError struct {
|
|
| 14 |
+ suiteName string |
|
| 15 |
+} |
|
| 16 |
+ |
|
| 17 |
+func (e *suiteOutOfBoundsError) Error() string {
|
|
| 18 |
+ return fmt.Sprintf("the test suite %q could not be placed under any existing roots in the tree", e.suiteName)
|
|
| 19 |
+} |
|
| 20 |
+ |
|
| 21 |
+// IsSuiteOutOfBoundsError determines if the given error was raised because a suite could not be placed |
|
| 22 |
+// in the test suite tree |
|
| 23 |
+func IsSuiteOutOfBoundsError(err error) bool {
|
|
| 24 |
+ if err == nil {
|
|
| 25 |
+ return false |
|
| 26 |
+ } |
|
| 27 |
+ |
|
| 28 |
+ _, ok := err.(*suiteOutOfBoundsError) |
|
| 29 |
+ return ok |
|
| 30 |
+} |
| 0 | 31 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,130 @@ |
| 0 |
+package gotest |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "regexp" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/openshift/origin/tools/junitreport/pkg/api" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+func newTestDataParser() testDataParser {
|
|
| 9 |
+ return testDataParser{
|
|
| 10 |
+ // testStartPattern matches the line in verbose `go test` output that marks the declaration of a test. |
|
| 11 |
+ // The first submatch of this regex is the name of the test |
|
| 12 |
+ testStartPattern: regexp.MustCompile(`=== RUN\s+(.+)$`), |
|
| 13 |
+ |
|
| 14 |
+ // testResultPattern matches the line in verbose `go test` output that marks the result of a test. |
|
| 15 |
+ // The first submatch of this regex is the result of the test (PASS, FAIL, or SKIP) |
|
| 16 |
+ // The second submatch of this regex is the name of the test |
|
| 17 |
+ // The third submatch of this regex is the time taken in seconds for the test to finish |
|
| 18 |
+ testResultPattern: regexp.MustCompile(`--- (PASS|FAIL|SKIP):\s+(.+)\s+\((\d+\.\d+)(s| seconds)\)`), |
|
| 19 |
+ } |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+type testDataParser struct {
|
|
| 23 |
+ testStartPattern *regexp.Regexp |
|
| 24 |
+ testResultPattern *regexp.Regexp |
|
| 25 |
+} |
|
| 26 |
+ |
|
| 27 |
+// MarksBeginning determines if the line marks the begining of a test case |
|
| 28 |
+func (p *testDataParser) MarksBeginning(line string) bool {
|
|
| 29 |
+ return p.testStartPattern.MatchString(line) |
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+// ExtractName extracts the name of the test case from test output line |
|
| 33 |
+func (p *testDataParser) ExtractName(line string) (string, bool) {
|
|
| 34 |
+ if matches := p.testStartPattern.FindStringSubmatch(line); len(matches) > 0 && len(matches[1]) > 0 {
|
|
| 35 |
+ return matches[1], true |
|
| 36 |
+ } |
|
| 37 |
+ |
|
| 38 |
+ if matches := p.testResultPattern.FindStringSubmatch(line); len(matches) > 1 && len(matches[2]) > 0 {
|
|
| 39 |
+ return matches[2], true |
|
| 40 |
+ } |
|
| 41 |
+ |
|
| 42 |
+ return "", false |
|
| 43 |
+} |
|
| 44 |
+ |
|
| 45 |
+// ExtractResult extracts the test result from a test output line |
|
| 46 |
+func (p *testDataParser) ExtractResult(line string) (api.TestResult, bool) {
|
|
| 47 |
+ if matches := p.testResultPattern.FindStringSubmatch(line); len(matches) > 0 && len(matches[1]) > 0 {
|
|
| 48 |
+ switch matches[1] {
|
|
| 49 |
+ case "PASS": |
|
| 50 |
+ return api.TestResultPass, true |
|
| 51 |
+ case "SKIP": |
|
| 52 |
+ return api.TestResultSkip, true |
|
| 53 |
+ case "FAIL": |
|
| 54 |
+ return api.TestResultFail, true |
|
| 55 |
+ } |
|
| 56 |
+ } |
|
| 57 |
+ return "", false |
|
| 58 |
+} |
|
| 59 |
+ |
|
| 60 |
+// ExtractDuration extracts the test duration from a test output line |
|
| 61 |
+func (p *testDataParser) ExtractDuration(line string) (string, bool) {
|
|
| 62 |
+ if matches := p.testResultPattern.FindStringSubmatch(line); len(matches) > 2 && len(matches[3]) > 0 {
|
|
| 63 |
+ return matches[3] + "s", true |
|
| 64 |
+ } |
|
| 65 |
+ return "", false |
|
| 66 |
+} |
|
| 67 |
+ |
|
| 68 |
+func newTestSuiteDataParser() testSuiteDataParser {
|
|
| 69 |
+ return testSuiteDataParser{
|
|
| 70 |
+ // coverageOutputPattern matches coverage output on a single line. |
|
| 71 |
+ // The first submatch of this regex is the percent coverage |
|
| 72 |
+ coverageOutputPattern: regexp.MustCompile(`coverage:\s+(\d+\.\d+)\% of statements`), |
|
| 73 |
+ |
|
| 74 |
+ // packageResultPattern matches the `go test` output for the end of a package. |
|
| 75 |
+ // The first submatch of this regex matches the result of the test (ok or FAIL) |
|
| 76 |
+ // The second submatch of this regex matches the name of the package |
|
| 77 |
+ // The third submatch of this regex matches the time taken in seconds for tests in the package to finish |
|
| 78 |
+ // The sixth (optional) submatch of this regex is the percent coverage |
|
| 79 |
+ packageResultPattern: regexp.MustCompile(`(ok|FAIL)\s+(.+)[\s\t]+(\d+\.\d+(s| seconds))([\s\t]+coverage:\s+(\d+\.\d+)\% of statements)?`), |
|
| 80 |
+ } |
|
| 81 |
+} |
|
| 82 |
+ |
|
| 83 |
+type testSuiteDataParser struct {
|
|
| 84 |
+ coverageOutputPattern *regexp.Regexp |
|
| 85 |
+ packageResultPattern *regexp.Regexp |
|
| 86 |
+} |
|
| 87 |
+ |
|
| 88 |
+// ExtractName extracts the name of the test suite from a test output line |
|
| 89 |
+func (p *testSuiteDataParser) ExtractName(line string) (string, bool) {
|
|
| 90 |
+ if matches := p.packageResultPattern.FindStringSubmatch(line); len(matches) > 1 && len(matches[2]) > 0 {
|
|
| 91 |
+ return matches[2], true |
|
| 92 |
+ } |
|
| 93 |
+ return "", false |
|
| 94 |
+} |
|
| 95 |
+ |
|
| 96 |
+// ExtractDuration extracts the package duration from a test output line |
|
| 97 |
+func (p *testSuiteDataParser) ExtractDuration(line string) (string, bool) {
|
|
| 98 |
+ if resultMatches := p.packageResultPattern.FindStringSubmatch(line); len(resultMatches) > 2 && len(resultMatches[3]) > 0 {
|
|
| 99 |
+ return resultMatches[3], true |
|
| 100 |
+ } |
|
| 101 |
+ return "", false |
|
| 102 |
+} |
|
| 103 |
+ |
|
| 104 |
+const ( |
|
| 105 |
+ coveragePropertyName string = "coverage.statements.pct" |
|
| 106 |
+) |
|
| 107 |
+ |
|
| 108 |
+// ExtractProperties extracts any metadata properties of the test suite from a test output line |
|
| 109 |
+func (p *testSuiteDataParser) ExtractProperties(line string) (map[string]string, bool) {
|
|
| 110 |
+ // the only test suite properties that Go testing can create are coverage values, which can either |
|
| 111 |
+ // be present on their own line or in the package result line |
|
| 112 |
+ if matches := p.coverageOutputPattern.FindStringSubmatch(line); len(matches) > 0 && len(matches[1]) > 0 {
|
|
| 113 |
+ return map[string]string{
|
|
| 114 |
+ coveragePropertyName: matches[1], |
|
| 115 |
+ }, true |
|
| 116 |
+ } |
|
| 117 |
+ |
|
| 118 |
+ if resultMatches := p.packageResultPattern.FindStringSubmatch(line); len(resultMatches) > 5 && len(resultMatches[6]) > 0 {
|
|
| 119 |
+ return map[string]string{
|
|
| 120 |
+ coveragePropertyName: resultMatches[6], |
|
| 121 |
+ }, true |
|
| 122 |
+ } |
|
| 123 |
+ return map[string]string{}, false
|
|
| 124 |
+} |
|
| 125 |
+ |
|
| 126 |
+// MarksCompletion determines if the line marks the completion of a test suite |
|
| 127 |
+func (p *testSuiteDataParser) MarksCompletion(line string) bool {
|
|
| 128 |
+ return p.packageResultPattern.MatchString(line) |
|
| 129 |
+} |
| 0 | 130 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,306 @@ |
| 0 |
+package gotest |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "reflect" |
|
| 4 |
+ "testing" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/openshift/origin/tools/junitreport/pkg/api" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func TestMarksTestBeginning(t *testing.T) {
|
|
| 10 |
+ var testCases = []struct {
|
|
| 11 |
+ name string |
|
| 12 |
+ testLine string |
|
| 13 |
+ }{
|
|
| 14 |
+ {
|
|
| 15 |
+ name: "basic", |
|
| 16 |
+ testLine: "=== RUN TestName", |
|
| 17 |
+ }, |
|
| 18 |
+ {
|
|
| 19 |
+ name: "numeric", |
|
| 20 |
+ testLine: "=== RUN 1234", |
|
| 21 |
+ }, |
|
| 22 |
+ {
|
|
| 23 |
+ name: "url", |
|
| 24 |
+ testLine: "=== RUN github.com/maintainer/repository/package/file", |
|
| 25 |
+ }, |
|
| 26 |
+ {
|
|
| 27 |
+ name: "failed print", |
|
| 28 |
+ testLine: "some other text=== RUN github.com/maintainer/repository/package/file", |
|
| 29 |
+ }, |
|
| 30 |
+ } |
|
| 31 |
+ |
|
| 32 |
+ for _, testCase := range testCases {
|
|
| 33 |
+ parser := newTestDataParser() |
|
| 34 |
+ |
|
| 35 |
+ if !parser.MarksBeginning(testCase.testLine) {
|
|
| 36 |
+ t.Errorf("%s: did not correctly determine that line %q marked test beginning", testCase.name, testCase.testLine)
|
|
| 37 |
+ } |
|
| 38 |
+ } |
|
| 39 |
+} |
|
| 40 |
+ |
|
| 41 |
+func TestExtractTestName(t *testing.T) {
|
|
| 42 |
+ var testCases = []struct {
|
|
| 43 |
+ name string |
|
| 44 |
+ testLine string |
|
| 45 |
+ expectedName string |
|
| 46 |
+ }{
|
|
| 47 |
+ {
|
|
| 48 |
+ name: "basic start", |
|
| 49 |
+ testLine: "=== RUN TestName", |
|
| 50 |
+ expectedName: "TestName", |
|
| 51 |
+ }, |
|
| 52 |
+ {
|
|
| 53 |
+ name: "numeric", |
|
| 54 |
+ testLine: "=== RUN 1234", |
|
| 55 |
+ expectedName: "1234", |
|
| 56 |
+ }, |
|
| 57 |
+ {
|
|
| 58 |
+ name: "url", |
|
| 59 |
+ testLine: "=== RUN github.com/maintainer/repository/package/file", |
|
| 60 |
+ expectedName: "github.com/maintainer/repository/package/file", |
|
| 61 |
+ }, |
|
| 62 |
+ {
|
|
| 63 |
+ name: "basic end", |
|
| 64 |
+ testLine: "--- PASS: Test (0.10 seconds)", |
|
| 65 |
+ expectedName: "Test", |
|
| 66 |
+ }, |
|
| 67 |
+ {
|
|
| 68 |
+ name: "go1.5.1 timing", |
|
| 69 |
+ testLine: "--- PASS: TestTwo (0.03s)", |
|
| 70 |
+ expectedName: "TestTwo", |
|
| 71 |
+ }, |
|
| 72 |
+ {
|
|
| 73 |
+ name: "skip", |
|
| 74 |
+ testLine: "--- SKIP: Test (0.10 seconds)", |
|
| 75 |
+ expectedName: "Test", |
|
| 76 |
+ }, |
|
| 77 |
+ {
|
|
| 78 |
+ name: "fail", |
|
| 79 |
+ testLine: "--- FAIL: Test (0.10 seconds)", |
|
| 80 |
+ expectedName: "Test", |
|
| 81 |
+ }, |
|
| 82 |
+ {
|
|
| 83 |
+ name: "failed print", |
|
| 84 |
+ testLine: "some other text--- FAIL: Test (0.10 seconds)", |
|
| 85 |
+ expectedName: "Test", |
|
| 86 |
+ }, |
|
| 87 |
+ } |
|
| 88 |
+ |
|
| 89 |
+ for _, testCase := range testCases {
|
|
| 90 |
+ parser := newTestDataParser() |
|
| 91 |
+ |
|
| 92 |
+ actual, contained := parser.ExtractName(testCase.testLine) |
|
| 93 |
+ if !contained {
|
|
| 94 |
+ t.Errorf("%s: failed to extract name from line %q", testCase.name, testCase.testLine)
|
|
| 95 |
+ } |
|
| 96 |
+ if testCase.expectedName != actual {
|
|
| 97 |
+ t.Errorf("%s: did not correctly extract name from line %q: expected %q, got %q", testCase.name, testCase.testLine, testCase.expectedName, actual)
|
|
| 98 |
+ } |
|
| 99 |
+ } |
|
| 100 |
+} |
|
| 101 |
+ |
|
| 102 |
+func TestExtractResult(t *testing.T) {
|
|
| 103 |
+ var testCases = []struct {
|
|
| 104 |
+ name string |
|
| 105 |
+ testLine string |
|
| 106 |
+ expectedResult api.TestResult |
|
| 107 |
+ }{
|
|
| 108 |
+ {
|
|
| 109 |
+ name: "basic", |
|
| 110 |
+ testLine: "--- PASS: Test (0.10 seconds)", |
|
| 111 |
+ expectedResult: api.TestResultPass, |
|
| 112 |
+ }, |
|
| 113 |
+ {
|
|
| 114 |
+ name: "go1.5.1 timing", |
|
| 115 |
+ testLine: "--- PASS: TestTwo (0.03s)", |
|
| 116 |
+ expectedResult: api.TestResultPass, |
|
| 117 |
+ }, |
|
| 118 |
+ {
|
|
| 119 |
+ name: "skip", |
|
| 120 |
+ testLine: "--- SKIP: Test (0.10 seconds)", |
|
| 121 |
+ expectedResult: api.TestResultSkip, |
|
| 122 |
+ }, |
|
| 123 |
+ {
|
|
| 124 |
+ name: "fail", |
|
| 125 |
+ testLine: "--- FAIL: Test (0.10 seconds)", |
|
| 126 |
+ expectedResult: api.TestResultFail, |
|
| 127 |
+ }, |
|
| 128 |
+ {
|
|
| 129 |
+ name: "failed print", |
|
| 130 |
+ testLine: "some other text--- FAIL: Test (0.10 seconds)", |
|
| 131 |
+ expectedResult: api.TestResultFail, |
|
| 132 |
+ }, |
|
| 133 |
+ } |
|
| 134 |
+ |
|
| 135 |
+ for _, testCase := range testCases {
|
|
| 136 |
+ parser := newTestDataParser() |
|
| 137 |
+ |
|
| 138 |
+ actual, contained := parser.ExtractResult(testCase.testLine) |
|
| 139 |
+ if !contained {
|
|
| 140 |
+ t.Errorf("%s: failed to extract result from line %q", testCase.name, testCase.testLine)
|
|
| 141 |
+ } |
|
| 142 |
+ if testCase.expectedResult != actual {
|
|
| 143 |
+ t.Errorf("%s: did not correctly extract result from line %q: expected %q, got %q", testCase.name, testCase.testLine, testCase.expectedResult, actual)
|
|
| 144 |
+ } |
|
| 145 |
+ } |
|
| 146 |
+} |
|
| 147 |
+ |
|
| 148 |
+func TestExtractDuration(t *testing.T) {
|
|
| 149 |
+ var testCases = []struct {
|
|
| 150 |
+ name string |
|
| 151 |
+ testLine string |
|
| 152 |
+ expectedDuration string |
|
| 153 |
+ }{
|
|
| 154 |
+ {
|
|
| 155 |
+ name: "basic", |
|
| 156 |
+ testLine: "--- PASS: Test (0.10 seconds)", |
|
| 157 |
+ expectedDuration: "0.10s", // we make the conversion to time.Duration-parseable units internally |
|
| 158 |
+ }, |
|
| 159 |
+ {
|
|
| 160 |
+ name: "go1.5.1 timing", |
|
| 161 |
+ testLine: "--- PASS: TestTwo (0.03s)", |
|
| 162 |
+ expectedDuration: "0.03s", |
|
| 163 |
+ }, |
|
| 164 |
+ {
|
|
| 165 |
+ name: "failed print", |
|
| 166 |
+ testLine: "some other text--- PASS: TestTwo (0.03s)", |
|
| 167 |
+ expectedDuration: "0.03s", |
|
| 168 |
+ }, |
|
| 169 |
+ } |
|
| 170 |
+ |
|
| 171 |
+ for _, testCase := range testCases {
|
|
| 172 |
+ parser := newTestDataParser() |
|
| 173 |
+ |
|
| 174 |
+ actual, contained := parser.ExtractDuration(testCase.testLine) |
|
| 175 |
+ if !contained {
|
|
| 176 |
+ t.Errorf("%s: failed to extract duration from line %q", testCase.name, testCase.testLine)
|
|
| 177 |
+ } |
|
| 178 |
+ if testCase.expectedDuration != actual {
|
|
| 179 |
+ t.Errorf("%s: did not correctly extract duration from line %q: expected %q, got %q", testCase.name, testCase.testLine, testCase.expectedDuration, actual)
|
|
| 180 |
+ } |
|
| 181 |
+ } |
|
| 182 |
+} |
|
| 183 |
+ |
|
| 184 |
+func TestExtractSuiteName(t *testing.T) {
|
|
| 185 |
+ var testCases = []struct {
|
|
| 186 |
+ name string |
|
| 187 |
+ testLine string |
|
| 188 |
+ expectedName string |
|
| 189 |
+ }{
|
|
| 190 |
+ {
|
|
| 191 |
+ name: "basic", |
|
| 192 |
+ testLine: "ok package/name 0.160s", |
|
| 193 |
+ expectedName: "package/name", |
|
| 194 |
+ }, |
|
| 195 |
+ {
|
|
| 196 |
+ name: "go 1.5.1", |
|
| 197 |
+ testLine: "ok package/name 0.160s", |
|
| 198 |
+ expectedName: "package/name", |
|
| 199 |
+ }, |
|
| 200 |
+ {
|
|
| 201 |
+ name: "numeric", |
|
| 202 |
+ testLine: "ok 1234 0.160s", |
|
| 203 |
+ expectedName: "1234", |
|
| 204 |
+ }, |
|
| 205 |
+ {
|
|
| 206 |
+ name: "url", |
|
| 207 |
+ testLine: "ok github.com/maintainer/repository/package/file 0.160s", |
|
| 208 |
+ expectedName: "github.com/maintainer/repository/package/file", |
|
| 209 |
+ }, |
|
| 210 |
+ {
|
|
| 211 |
+ name: "with coverage", |
|
| 212 |
+ testLine: `ok package/name 0.400s coverage: 10.0% of statements`, |
|
| 213 |
+ expectedName: "package/name", |
|
| 214 |
+ }, |
|
| 215 |
+ {
|
|
| 216 |
+ name: "failed print", |
|
| 217 |
+ testLine: `some other textok package/name 0.400s coverage: 10.0% of statements`, |
|
| 218 |
+ expectedName: "package/name", |
|
| 219 |
+ }, |
|
| 220 |
+ } |
|
| 221 |
+ |
|
| 222 |
+ for _, testCase := range testCases {
|
|
| 223 |
+ parser := newTestSuiteDataParser() |
|
| 224 |
+ |
|
| 225 |
+ actual, contained := parser.ExtractName(testCase.testLine) |
|
| 226 |
+ if !contained {
|
|
| 227 |
+ t.Errorf("%s: failed to extract name from line %q", testCase.name, testCase.testLine)
|
|
| 228 |
+ } |
|
| 229 |
+ if testCase.expectedName != actual {
|
|
| 230 |
+ t.Errorf("%s: did not correctly extract suite name from line %q: expected %q, got %q", testCase.name, testCase.testLine, testCase.expectedName, actual)
|
|
| 231 |
+ } |
|
| 232 |
+ } |
|
| 233 |
+} |
|
| 234 |
+ |
|
| 235 |
+func TestSuiteProperties(t *testing.T) {
|
|
| 236 |
+ var testCases = []struct {
|
|
| 237 |
+ name string |
|
| 238 |
+ testLine string |
|
| 239 |
+ expectedProperties map[string]string |
|
| 240 |
+ }{
|
|
| 241 |
+ {
|
|
| 242 |
+ name: "basic", |
|
| 243 |
+ testLine: `coverage: 10.0% of statements`, |
|
| 244 |
+ expectedProperties: map[string]string{coveragePropertyName: "10.0"},
|
|
| 245 |
+ }, |
|
| 246 |
+ {
|
|
| 247 |
+ name: "with package declaration", |
|
| 248 |
+ testLine: `ok package/name 0.400s coverage: 10.0% of statements`, |
|
| 249 |
+ expectedProperties: map[string]string{coveragePropertyName: "10.0"},
|
|
| 250 |
+ }, |
|
| 251 |
+ {
|
|
| 252 |
+ name: "failed print", |
|
| 253 |
+ testLine: `some other textcoverage: 10.0% of statements`, |
|
| 254 |
+ expectedProperties: map[string]string{coveragePropertyName: "10.0"},
|
|
| 255 |
+ }, |
|
| 256 |
+ } |
|
| 257 |
+ |
|
| 258 |
+ for _, testCase := range testCases {
|
|
| 259 |
+ parser := newTestSuiteDataParser() |
|
| 260 |
+ |
|
| 261 |
+ actual, contained := parser.ExtractProperties(testCase.testLine) |
|
| 262 |
+ if !contained {
|
|
| 263 |
+ t.Errorf("%s: failed to extract properties from line %q", testCase.name, testCase.testLine)
|
|
| 264 |
+ } |
|
| 265 |
+ if !reflect.DeepEqual(testCase.expectedProperties, actual) {
|
|
| 266 |
+ t.Errorf("%s: did not correctly extract properties from line %q: expected %q, got %q", testCase.name, testCase.testLine, testCase.expectedProperties, actual)
|
|
| 267 |
+ } |
|
| 268 |
+ } |
|
| 269 |
+} |
|
| 270 |
+ |
|
| 271 |
+func TestMarksCompletion(t *testing.T) {
|
|
| 272 |
+ var testCases = []struct {
|
|
| 273 |
+ name string |
|
| 274 |
+ testLine string |
|
| 275 |
+ }{
|
|
| 276 |
+ {
|
|
| 277 |
+ name: "basic", |
|
| 278 |
+ testLine: "ok package/name 0.160s", |
|
| 279 |
+ }, |
|
| 280 |
+ {
|
|
| 281 |
+ name: "numeric", |
|
| 282 |
+ testLine: "ok 1234 0.160s", |
|
| 283 |
+ }, |
|
| 284 |
+ {
|
|
| 285 |
+ name: "url", |
|
| 286 |
+ testLine: "ok github.com/maintainer/repository/package/file 0.160s", |
|
| 287 |
+ }, |
|
| 288 |
+ {
|
|
| 289 |
+ name: "with coverage", |
|
| 290 |
+ testLine: `ok package/name 0.400s coverage: 10.0% of statements`, |
|
| 291 |
+ }, |
|
| 292 |
+ {
|
|
| 293 |
+ name: "failed print", |
|
| 294 |
+ testLine: `some other textok package/name 0.400s coverage: 10.0% of statements`, |
|
| 295 |
+ }, |
|
| 296 |
+ } |
|
| 297 |
+ |
|
| 298 |
+ for _, testCase := range testCases {
|
|
| 299 |
+ parser := newTestSuiteDataParser() |
|
| 300 |
+ |
|
| 301 |
+ if !parser.MarksCompletion(testCase.testLine) {
|
|
| 302 |
+ t.Errorf("%s: did not correctly determine that line %q marked the end of a suite", testCase.name, testCase.testLine)
|
|
| 303 |
+ } |
|
| 304 |
+ } |
|
| 305 |
+} |
| 0 | 306 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,106 @@ |
| 0 |
+package gotest |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bufio" |
|
| 4 |
+ "strings" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/openshift/origin/tools/junitreport/pkg/api" |
|
| 7 |
+ "github.com/openshift/origin/tools/junitreport/pkg/builder" |
|
| 8 |
+ "github.com/openshift/origin/tools/junitreport/pkg/parser" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+// NewParser returns a new parser that's capable of parsing Go unit test output |
|
| 12 |
+func NewParser(builder builder.TestSuitesBuilder) parser.TestOutputParser {
|
|
| 13 |
+ return &testOutputParser{
|
|
| 14 |
+ builder: builder, |
|
| 15 |
+ testParser: newTestDataParser(), |
|
| 16 |
+ suiteParser: newTestSuiteDataParser(), |
|
| 17 |
+ } |
|
| 18 |
+} |
|
| 19 |
+ |
|
| 20 |
+type testOutputParser struct {
|
|
| 21 |
+ builder builder.TestSuitesBuilder |
|
| 22 |
+ testParser testDataParser |
|
| 23 |
+ suiteParser testSuiteDataParser |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+// Parse parses `go test -v` output into test suites. Test output from `go test -v` is not bookmarked for packages, so |
|
| 27 |
+// the parsing strategy is to advance line-by-line, building up a slice of test cases until a package declaration is found, |
|
| 28 |
+// at which point all tests cases are added to that package and the process can start again. |
|
| 29 |
+func (p *testOutputParser) Parse(input *bufio.Scanner) (*api.TestSuites, error) {
|
|
| 30 |
+ currentSuite := &api.TestSuite{}
|
|
| 31 |
+ var currentTest *api.TestCase |
|
| 32 |
+ var currentTestResult api.TestResult |
|
| 33 |
+ var currentTestOutput []string |
|
| 34 |
+ |
|
| 35 |
+ for input.Scan() {
|
|
| 36 |
+ line := input.Text() |
|
| 37 |
+ isTestOutput := true |
|
| 38 |
+ |
|
| 39 |
+ if p.testParser.MarksBeginning(line) || p.suiteParser.MarksCompletion(line) {
|
|
| 40 |
+ if currentTest != nil {
|
|
| 41 |
+ // we can't mark the test as failed or skipped until we have all of the test output, which we don't know |
|
| 42 |
+ // we have until we see the next test or the beginning of suite output, so we add it here |
|
| 43 |
+ output := strings.Join(currentTestOutput, "\n") |
|
| 44 |
+ switch currentTestResult {
|
|
| 45 |
+ case api.TestResultSkip: |
|
| 46 |
+ currentTest.MarkSkipped(output) |
|
| 47 |
+ case api.TestResultFail: |
|
| 48 |
+ currentTest.MarkFailed("", output)
|
|
| 49 |
+ } |
|
| 50 |
+ |
|
| 51 |
+ currentSuite.AddTestCase(currentTest) |
|
| 52 |
+ } |
|
| 53 |
+ currentTest = &api.TestCase{}
|
|
| 54 |
+ currentTestResult = api.TestResultFail |
|
| 55 |
+ currentTestOutput = []string{}
|
|
| 56 |
+ } |
|
| 57 |
+ |
|
| 58 |
+ if name, matched := p.testParser.ExtractName(line); matched {
|
|
| 59 |
+ currentTest.Name = name |
|
| 60 |
+ } |
|
| 61 |
+ |
|
| 62 |
+ if result, matched := p.testParser.ExtractResult(line); matched {
|
|
| 63 |
+ currentTestResult = result |
|
| 64 |
+ } |
|
| 65 |
+ |
|
| 66 |
+ if duration, matched := p.testParser.ExtractDuration(line); matched {
|
|
| 67 |
+ if err := currentTest.SetDuration(duration); err != nil {
|
|
| 68 |
+ return nil, err |
|
| 69 |
+ } |
|
| 70 |
+ } |
|
| 71 |
+ |
|
| 72 |
+ if properties, matched := p.suiteParser.ExtractProperties(line); matched {
|
|
| 73 |
+ for name := range properties {
|
|
| 74 |
+ currentSuite.AddProperty(name, properties[name]) |
|
| 75 |
+ } |
|
| 76 |
+ isTestOutput = false |
|
| 77 |
+ } |
|
| 78 |
+ |
|
| 79 |
+ if name, matched := p.suiteParser.ExtractName(line); matched {
|
|
| 80 |
+ currentSuite.Name = name |
|
| 81 |
+ isTestOutput = false |
|
| 82 |
+ } |
|
| 83 |
+ |
|
| 84 |
+ if duration, matched := p.suiteParser.ExtractDuration(line); matched {
|
|
| 85 |
+ if err := currentSuite.SetDuration(duration); err != nil {
|
|
| 86 |
+ return nil, err |
|
| 87 |
+ } |
|
| 88 |
+ } |
|
| 89 |
+ |
|
| 90 |
+ if p.suiteParser.MarksCompletion(line) {
|
|
| 91 |
+ p.builder.AddSuite(currentSuite) |
|
| 92 |
+ |
|
| 93 |
+ currentSuite = &api.TestSuite{}
|
|
| 94 |
+ currentTest = nil |
|
| 95 |
+ isTestOutput = false |
|
| 96 |
+ } |
|
| 97 |
+ |
|
| 98 |
+ // we want to associate any line not directly related to a test suite with a test case to ensure we capture all output |
|
| 99 |
+ if isTestOutput {
|
|
| 100 |
+ currentTestOutput = append(currentTestOutput, line) |
|
| 101 |
+ } |
|
| 102 |
+ } |
|
| 103 |
+ |
|
| 104 |
+ return p.builder.Build(), nil |
|
| 105 |
+} |
| 0 | 106 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,369 @@ |
| 0 |
+package gotest |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bufio" |
|
| 4 |
+ "os" |
|
| 5 |
+ "reflect" |
|
| 6 |
+ "testing" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/openshift/origin/tools/junitreport/pkg/api" |
|
| 9 |
+ "github.com/openshift/origin/tools/junitreport/pkg/builder/flat" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+// TestFlatParse tests that parsing the `go test` output in the test directory with a flat builder works as expected |
|
| 13 |
+func TestFlatParse(t *testing.T) {
|
|
| 14 |
+ var testCases = []struct {
|
|
| 15 |
+ name string |
|
| 16 |
+ testFile string |
|
| 17 |
+ expectedSuites *api.TestSuites |
|
| 18 |
+ }{
|
|
| 19 |
+ {
|
|
| 20 |
+ name: "basic", |
|
| 21 |
+ testFile: "1.txt", |
|
| 22 |
+ expectedSuites: &api.TestSuites{
|
|
| 23 |
+ Suites: []*api.TestSuite{
|
|
| 24 |
+ {
|
|
| 25 |
+ Name: "package/name", |
|
| 26 |
+ NumTests: 2, |
|
| 27 |
+ Duration: 0.16, |
|
| 28 |
+ TestCases: []*api.TestCase{
|
|
| 29 |
+ {
|
|
| 30 |
+ Name: "TestOne", |
|
| 31 |
+ Duration: 0.06, |
|
| 32 |
+ }, |
|
| 33 |
+ {
|
|
| 34 |
+ Name: "TestTwo", |
|
| 35 |
+ Duration: 0.1, |
|
| 36 |
+ }, |
|
| 37 |
+ }, |
|
| 38 |
+ }, |
|
| 39 |
+ }, |
|
| 40 |
+ }, |
|
| 41 |
+ }, |
|
| 42 |
+ {
|
|
| 43 |
+ name: "failure", |
|
| 44 |
+ testFile: "2.txt", |
|
| 45 |
+ expectedSuites: &api.TestSuites{
|
|
| 46 |
+ Suites: []*api.TestSuite{
|
|
| 47 |
+ {
|
|
| 48 |
+ Name: "package/name", |
|
| 49 |
+ NumTests: 2, |
|
| 50 |
+ NumFailed: 1, |
|
| 51 |
+ Duration: 0.15, |
|
| 52 |
+ TestCases: []*api.TestCase{
|
|
| 53 |
+ {
|
|
| 54 |
+ Name: "TestOne", |
|
| 55 |
+ Duration: 0.02, |
|
| 56 |
+ FailureOutput: &api.FailureOutput{
|
|
| 57 |
+ Output: `=== RUN TestOne |
|
| 58 |
+--- FAIL: TestOne (0.02 seconds) |
|
| 59 |
+ file_test.go:11: Error message |
|
| 60 |
+ file_test.go:11: Longer |
|
| 61 |
+ error |
|
| 62 |
+ message.`, |
|
| 63 |
+ }, |
|
| 64 |
+ }, |
|
| 65 |
+ {
|
|
| 66 |
+ Name: "TestTwo", |
|
| 67 |
+ Duration: 0.13, |
|
| 68 |
+ }, |
|
| 69 |
+ }, |
|
| 70 |
+ }, |
|
| 71 |
+ }, |
|
| 72 |
+ }, |
|
| 73 |
+ }, |
|
| 74 |
+ {
|
|
| 75 |
+ name: "skip", |
|
| 76 |
+ testFile: "3.txt", |
|
| 77 |
+ expectedSuites: &api.TestSuites{
|
|
| 78 |
+ Suites: []*api.TestSuite{
|
|
| 79 |
+ {
|
|
| 80 |
+ Name: "package/name", |
|
| 81 |
+ NumTests: 2, |
|
| 82 |
+ NumSkipped: 1, |
|
| 83 |
+ Duration: 0.15, |
|
| 84 |
+ TestCases: []*api.TestCase{
|
|
| 85 |
+ {
|
|
| 86 |
+ Name: "TestOne", |
|
| 87 |
+ Duration: 0.02, |
|
| 88 |
+ SkipMessage: &api.SkipMessage{
|
|
| 89 |
+ Message: `=== RUN TestOne |
|
| 90 |
+--- SKIP: TestOne (0.02 seconds) |
|
| 91 |
+ file_test.go:11: Skip message`, |
|
| 92 |
+ }, |
|
| 93 |
+ }, |
|
| 94 |
+ {
|
|
| 95 |
+ Name: "TestTwo", |
|
| 96 |
+ Duration: 0.13, |
|
| 97 |
+ }, |
|
| 98 |
+ }, |
|
| 99 |
+ }, |
|
| 100 |
+ }, |
|
| 101 |
+ }, |
|
| 102 |
+ }, |
|
| 103 |
+ {
|
|
| 104 |
+ name: "go 1.4", |
|
| 105 |
+ testFile: "4.txt", |
|
| 106 |
+ expectedSuites: &api.TestSuites{
|
|
| 107 |
+ Suites: []*api.TestSuite{
|
|
| 108 |
+ {
|
|
| 109 |
+ Name: "package/name", |
|
| 110 |
+ NumTests: 2, |
|
| 111 |
+ Duration: 0.16, |
|
| 112 |
+ TestCases: []*api.TestCase{
|
|
| 113 |
+ {
|
|
| 114 |
+ Name: "TestOne", |
|
| 115 |
+ Duration: 0.06, |
|
| 116 |
+ }, |
|
| 117 |
+ {
|
|
| 118 |
+ Name: "TestTwo", |
|
| 119 |
+ Duration: 0.1, |
|
| 120 |
+ }, |
|
| 121 |
+ }, |
|
| 122 |
+ }, |
|
| 123 |
+ }, |
|
| 124 |
+ }, |
|
| 125 |
+ }, |
|
| 126 |
+ {
|
|
| 127 |
+ name: "multiple suites", |
|
| 128 |
+ testFile: "5.txt", |
|
| 129 |
+ expectedSuites: &api.TestSuites{
|
|
| 130 |
+ Suites: []*api.TestSuite{
|
|
| 131 |
+ {
|
|
| 132 |
+ Name: "package/name1", |
|
| 133 |
+ NumTests: 2, |
|
| 134 |
+ Duration: 0.16, |
|
| 135 |
+ TestCases: []*api.TestCase{
|
|
| 136 |
+ {
|
|
| 137 |
+ Name: "TestOne", |
|
| 138 |
+ Duration: 0.06, |
|
| 139 |
+ }, |
|
| 140 |
+ {
|
|
| 141 |
+ Name: "TestTwo", |
|
| 142 |
+ Duration: 0.1, |
|
| 143 |
+ }, |
|
| 144 |
+ }, |
|
| 145 |
+ }, |
|
| 146 |
+ {
|
|
| 147 |
+ Name: "package/name2", |
|
| 148 |
+ NumTests: 2, |
|
| 149 |
+ Duration: 0.15, |
|
| 150 |
+ NumFailed: 1, |
|
| 151 |
+ TestCases: []*api.TestCase{
|
|
| 152 |
+ {
|
|
| 153 |
+ Name: "TestOne", |
|
| 154 |
+ Duration: 0.02, |
|
| 155 |
+ FailureOutput: &api.FailureOutput{
|
|
| 156 |
+ Output: `=== RUN TestOne |
|
| 157 |
+--- FAIL: TestOne (0.02 seconds) |
|
| 158 |
+ file_test.go:11: Error message |
|
| 159 |
+ file_test.go:11: Longer |
|
| 160 |
+ error |
|
| 161 |
+ message.`, |
|
| 162 |
+ }, |
|
| 163 |
+ }, |
|
| 164 |
+ {
|
|
| 165 |
+ Name: "TestTwo", |
|
| 166 |
+ Duration: 0.13, |
|
| 167 |
+ }, |
|
| 168 |
+ }, |
|
| 169 |
+ }, |
|
| 170 |
+ }, |
|
| 171 |
+ }, |
|
| 172 |
+ }, |
|
| 173 |
+ {
|
|
| 174 |
+ name: "coverage statement", |
|
| 175 |
+ testFile: "6.txt", |
|
| 176 |
+ expectedSuites: &api.TestSuites{
|
|
| 177 |
+ Suites: []*api.TestSuite{
|
|
| 178 |
+ {
|
|
| 179 |
+ Name: "package/name", |
|
| 180 |
+ NumTests: 2, |
|
| 181 |
+ Duration: 0.16, |
|
| 182 |
+ Properties: []*api.TestSuiteProperty{
|
|
| 183 |
+ {
|
|
| 184 |
+ Name: "coverage.statements.pct", |
|
| 185 |
+ Value: "13.37", |
|
| 186 |
+ }, |
|
| 187 |
+ }, |
|
| 188 |
+ TestCases: []*api.TestCase{
|
|
| 189 |
+ {
|
|
| 190 |
+ Name: "TestOne", |
|
| 191 |
+ Duration: 0.06, |
|
| 192 |
+ }, |
|
| 193 |
+ {
|
|
| 194 |
+ Name: "TestTwo", |
|
| 195 |
+ Duration: 0.1, |
|
| 196 |
+ }, |
|
| 197 |
+ }, |
|
| 198 |
+ }, |
|
| 199 |
+ }, |
|
| 200 |
+ }, |
|
| 201 |
+ }, |
|
| 202 |
+ {
|
|
| 203 |
+ name: "coverage statement in package result", |
|
| 204 |
+ testFile: "7.txt", |
|
| 205 |
+ expectedSuites: &api.TestSuites{
|
|
| 206 |
+ Suites: []*api.TestSuite{
|
|
| 207 |
+ {
|
|
| 208 |
+ Name: "package/name", |
|
| 209 |
+ NumTests: 2, |
|
| 210 |
+ Duration: 0.16, |
|
| 211 |
+ Properties: []*api.TestSuiteProperty{
|
|
| 212 |
+ {
|
|
| 213 |
+ Name: "coverage.statements.pct", |
|
| 214 |
+ Value: "10.0", |
|
| 215 |
+ }, |
|
| 216 |
+ }, |
|
| 217 |
+ TestCases: []*api.TestCase{
|
|
| 218 |
+ {
|
|
| 219 |
+ Name: "TestOne", |
|
| 220 |
+ Duration: 0.06, |
|
| 221 |
+ }, |
|
| 222 |
+ {
|
|
| 223 |
+ Name: "TestTwo", |
|
| 224 |
+ Duration: 0.1, |
|
| 225 |
+ }, |
|
| 226 |
+ }, |
|
| 227 |
+ }, |
|
| 228 |
+ }, |
|
| 229 |
+ }, |
|
| 230 |
+ }, |
|
| 231 |
+ {
|
|
| 232 |
+ name: "go 1.5", |
|
| 233 |
+ testFile: "8.txt", |
|
| 234 |
+ expectedSuites: &api.TestSuites{
|
|
| 235 |
+ Suites: []*api.TestSuite{
|
|
| 236 |
+ {
|
|
| 237 |
+ Name: "package/name", |
|
| 238 |
+ NumTests: 2, |
|
| 239 |
+ Duration: 0.05, |
|
| 240 |
+ TestCases: []*api.TestCase{
|
|
| 241 |
+ {
|
|
| 242 |
+ Name: "TestOne", |
|
| 243 |
+ Duration: 0.02, |
|
| 244 |
+ }, |
|
| 245 |
+ {
|
|
| 246 |
+ Name: "TestTwo", |
|
| 247 |
+ Duration: 0.03, |
|
| 248 |
+ }, |
|
| 249 |
+ }, |
|
| 250 |
+ }, |
|
| 251 |
+ }, |
|
| 252 |
+ }, |
|
| 253 |
+ }, |
|
| 254 |
+ {
|
|
| 255 |
+ name: "nested ", |
|
| 256 |
+ testFile: "9.txt", |
|
| 257 |
+ expectedSuites: &api.TestSuites{
|
|
| 258 |
+ Suites: []*api.TestSuite{
|
|
| 259 |
+ {
|
|
| 260 |
+ Name: "package/name", |
|
| 261 |
+ NumTests: 2, |
|
| 262 |
+ Duration: 0.05, |
|
| 263 |
+ TestCases: []*api.TestCase{
|
|
| 264 |
+ {
|
|
| 265 |
+ Name: "TestOne", |
|
| 266 |
+ Duration: 0.02, |
|
| 267 |
+ }, |
|
| 268 |
+ {
|
|
| 269 |
+ Name: "TestTwo", |
|
| 270 |
+ Duration: 0.03, |
|
| 271 |
+ }, |
|
| 272 |
+ }, |
|
| 273 |
+ }, |
|
| 274 |
+ {
|
|
| 275 |
+ Name: "package/name/nested", |
|
| 276 |
+ NumTests: 2, |
|
| 277 |
+ NumFailed: 1, |
|
| 278 |
+ NumSkipped: 1, |
|
| 279 |
+ Duration: 0.05, |
|
| 280 |
+ TestCases: []*api.TestCase{
|
|
| 281 |
+ {
|
|
| 282 |
+ Name: "TestOne", |
|
| 283 |
+ Duration: 0.02, |
|
| 284 |
+ FailureOutput: &api.FailureOutput{
|
|
| 285 |
+ Output: `=== RUN TestOne |
|
| 286 |
+--- FAIL: TestOne (0.02 seconds) |
|
| 287 |
+ file_test.go:11: Error message |
|
| 288 |
+ file_test.go:11: Longer |
|
| 289 |
+ error |
|
| 290 |
+ message.`, |
|
| 291 |
+ }, |
|
| 292 |
+ }, |
|
| 293 |
+ {
|
|
| 294 |
+ Name: "TestTwo", |
|
| 295 |
+ Duration: 0.03, |
|
| 296 |
+ SkipMessage: &api.SkipMessage{
|
|
| 297 |
+ Message: `=== RUN TestTwo |
|
| 298 |
+--- SKIP: TestTwo (0.03 seconds) |
|
| 299 |
+ file_test.go:11: Skip message |
|
| 300 |
+PASS`, |
|
| 301 |
+ }, |
|
| 302 |
+ }, |
|
| 303 |
+ }, |
|
| 304 |
+ }, |
|
| 305 |
+ {
|
|
| 306 |
+ Name: "package/other/nested", |
|
| 307 |
+ NumTests: 2, |
|
| 308 |
+ Duration: 0.3, |
|
| 309 |
+ TestCases: []*api.TestCase{
|
|
| 310 |
+ {
|
|
| 311 |
+ Name: "TestOne", |
|
| 312 |
+ Duration: 0.1, |
|
| 313 |
+ }, |
|
| 314 |
+ {
|
|
| 315 |
+ Name: "TestTwo", |
|
| 316 |
+ Duration: 0.2, |
|
| 317 |
+ }, |
|
| 318 |
+ }, |
|
| 319 |
+ }, |
|
| 320 |
+ }, |
|
| 321 |
+ }, |
|
| 322 |
+ }, |
|
| 323 |
+ {
|
|
| 324 |
+ name: "test case timing doesn't add to test suite timing", |
|
| 325 |
+ testFile: "10.txt", |
|
| 326 |
+ expectedSuites: &api.TestSuites{
|
|
| 327 |
+ Suites: []*api.TestSuite{
|
|
| 328 |
+ {
|
|
| 329 |
+ Name: "package/name", |
|
| 330 |
+ NumTests: 2, |
|
| 331 |
+ Duration: 2.16, |
|
| 332 |
+ TestCases: []*api.TestCase{
|
|
| 333 |
+ {
|
|
| 334 |
+ Name: "TestOne", |
|
| 335 |
+ Duration: 0.06, |
|
| 336 |
+ }, |
|
| 337 |
+ {
|
|
| 338 |
+ Name: "TestTwo", |
|
| 339 |
+ Duration: 0.1, |
|
| 340 |
+ }, |
|
| 341 |
+ }, |
|
| 342 |
+ }, |
|
| 343 |
+ }, |
|
| 344 |
+ }, |
|
| 345 |
+ }, |
|
| 346 |
+ } |
|
| 347 |
+ |
|
| 348 |
+ for _, testCase := range testCases {
|
|
| 349 |
+ parser := NewParser(flat.NewTestSuitesBuilder()) |
|
| 350 |
+ |
|
| 351 |
+ testFile := "./../../../test/gotest/testdata/" + testCase.testFile |
|
| 352 |
+ |
|
| 353 |
+ reader, err := os.Open(testFile) |
|
| 354 |
+ if err != nil {
|
|
| 355 |
+ t.Errorf("%s: unexpected error opening file %q: %v", testCase.name, testFile, err)
|
|
| 356 |
+ continue |
|
| 357 |
+ } |
|
| 358 |
+ testSuites, err := parser.Parse(bufio.NewScanner(reader)) |
|
| 359 |
+ if err != nil {
|
|
| 360 |
+ t.Errorf("%s: unexpected error parsing file: %v", testCase.name, err)
|
|
| 361 |
+ continue |
|
| 362 |
+ } |
|
| 363 |
+ |
|
| 364 |
+ if !reflect.DeepEqual(testSuites, testCase.expectedSuites) {
|
|
| 365 |
+ t.Errorf("%s: did not produce the correct test suites from file:\n\texpected:\n\t%v,\n\tgot\n\t%v", testCase.name, testCase.expectedSuites, testSuites)
|
|
| 366 |
+ } |
|
| 367 |
+ } |
|
| 368 |
+} |
| 0 | 369 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,486 @@ |
| 0 |
+package gotest |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bufio" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "os" |
|
| 6 |
+ "reflect" |
|
| 7 |
+ "testing" |
|
| 8 |
+ |
|
| 9 |
+ "k8s.io/kubernetes/pkg/util" |
|
| 10 |
+ |
|
| 11 |
+ "github.com/openshift/origin/tools/junitreport/pkg/api" |
|
| 12 |
+ "github.com/openshift/origin/tools/junitreport/pkg/builder/nested" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+// TestNestedParse tests that parsing the `go test` output in the test directory with a nested builder works as expected |
|
| 16 |
+func TestNestedParse(t *testing.T) {
|
|
| 17 |
+ var testCases = []struct {
|
|
| 18 |
+ name string |
|
| 19 |
+ testFile string |
|
| 20 |
+ rootSuiteNames []string |
|
| 21 |
+ expectedSuites *api.TestSuites |
|
| 22 |
+ }{
|
|
| 23 |
+ {
|
|
| 24 |
+ name: "basic", |
|
| 25 |
+ testFile: "1.txt", |
|
| 26 |
+ expectedSuites: &api.TestSuites{
|
|
| 27 |
+ Suites: []*api.TestSuite{
|
|
| 28 |
+ {
|
|
| 29 |
+ Name: "package", |
|
| 30 |
+ NumTests: 2, |
|
| 31 |
+ Duration: 0.16, |
|
| 32 |
+ Children: []*api.TestSuite{
|
|
| 33 |
+ {
|
|
| 34 |
+ Name: "package/name", |
|
| 35 |
+ NumTests: 2, |
|
| 36 |
+ Duration: 0.16, |
|
| 37 |
+ TestCases: []*api.TestCase{
|
|
| 38 |
+ {
|
|
| 39 |
+ Name: "TestOne", |
|
| 40 |
+ Duration: 0.06, |
|
| 41 |
+ }, |
|
| 42 |
+ {
|
|
| 43 |
+ Name: "TestTwo", |
|
| 44 |
+ Duration: 0.1, |
|
| 45 |
+ }, |
|
| 46 |
+ }, |
|
| 47 |
+ }, |
|
| 48 |
+ }, |
|
| 49 |
+ }, |
|
| 50 |
+ }, |
|
| 51 |
+ }, |
|
| 52 |
+ }, |
|
| 53 |
+ {
|
|
| 54 |
+ name: "basic with restricted root", |
|
| 55 |
+ testFile: "1.txt", |
|
| 56 |
+ rootSuiteNames: []string{"package/name"},
|
|
| 57 |
+ expectedSuites: &api.TestSuites{
|
|
| 58 |
+ Suites: []*api.TestSuite{
|
|
| 59 |
+ {
|
|
| 60 |
+ Name: "package/name", |
|
| 61 |
+ NumTests: 2, |
|
| 62 |
+ Duration: 0.16, |
|
| 63 |
+ TestCases: []*api.TestCase{
|
|
| 64 |
+ {
|
|
| 65 |
+ Name: "TestOne", |
|
| 66 |
+ Duration: 0.06, |
|
| 67 |
+ }, |
|
| 68 |
+ {
|
|
| 69 |
+ Name: "TestTwo", |
|
| 70 |
+ Duration: 0.1, |
|
| 71 |
+ }, |
|
| 72 |
+ }, |
|
| 73 |
+ }, |
|
| 74 |
+ }, |
|
| 75 |
+ }, |
|
| 76 |
+ }, |
|
| 77 |
+ {
|
|
| 78 |
+ name: "failure", |
|
| 79 |
+ testFile: "2.txt", |
|
| 80 |
+ expectedSuites: &api.TestSuites{
|
|
| 81 |
+ Suites: []*api.TestSuite{
|
|
| 82 |
+ {
|
|
| 83 |
+ Name: "package", |
|
| 84 |
+ NumTests: 2, |
|
| 85 |
+ NumFailed: 1, |
|
| 86 |
+ Duration: 0.15, |
|
| 87 |
+ Children: []*api.TestSuite{
|
|
| 88 |
+ {
|
|
| 89 |
+ Name: "package/name", |
|
| 90 |
+ NumTests: 2, |
|
| 91 |
+ NumFailed: 1, |
|
| 92 |
+ Duration: 0.15, |
|
| 93 |
+ TestCases: []*api.TestCase{
|
|
| 94 |
+ {
|
|
| 95 |
+ Name: "TestOne", |
|
| 96 |
+ Duration: 0.02, |
|
| 97 |
+ FailureOutput: &api.FailureOutput{
|
|
| 98 |
+ Output: `=== RUN TestOne |
|
| 99 |
+--- FAIL: TestOne (0.02 seconds) |
|
| 100 |
+ file_test.go:11: Error message |
|
| 101 |
+ file_test.go:11: Longer |
|
| 102 |
+ error |
|
| 103 |
+ message.`, |
|
| 104 |
+ }, |
|
| 105 |
+ }, |
|
| 106 |
+ {
|
|
| 107 |
+ Name: "TestTwo", |
|
| 108 |
+ Duration: 0.13, |
|
| 109 |
+ }, |
|
| 110 |
+ }, |
|
| 111 |
+ }, |
|
| 112 |
+ }, |
|
| 113 |
+ }, |
|
| 114 |
+ }, |
|
| 115 |
+ }, |
|
| 116 |
+ }, |
|
| 117 |
+ {
|
|
| 118 |
+ name: "skip", |
|
| 119 |
+ testFile: "3.txt", |
|
| 120 |
+ expectedSuites: &api.TestSuites{
|
|
| 121 |
+ Suites: []*api.TestSuite{
|
|
| 122 |
+ {
|
|
| 123 |
+ Name: "package", |
|
| 124 |
+ NumTests: 2, |
|
| 125 |
+ NumSkipped: 1, |
|
| 126 |
+ Duration: 0.15, |
|
| 127 |
+ Children: []*api.TestSuite{
|
|
| 128 |
+ {
|
|
| 129 |
+ Name: "package/name", |
|
| 130 |
+ NumTests: 2, |
|
| 131 |
+ NumSkipped: 1, |
|
| 132 |
+ Duration: 0.15, |
|
| 133 |
+ TestCases: []*api.TestCase{
|
|
| 134 |
+ {
|
|
| 135 |
+ Name: "TestOne", |
|
| 136 |
+ Duration: 0.02, |
|
| 137 |
+ SkipMessage: &api.SkipMessage{
|
|
| 138 |
+ Message: `=== RUN TestOne |
|
| 139 |
+--- SKIP: TestOne (0.02 seconds) |
|
| 140 |
+ file_test.go:11: Skip message`, |
|
| 141 |
+ }, |
|
| 142 |
+ }, |
|
| 143 |
+ {
|
|
| 144 |
+ Name: "TestTwo", |
|
| 145 |
+ Duration: 0.13, |
|
| 146 |
+ }, |
|
| 147 |
+ }, |
|
| 148 |
+ }, |
|
| 149 |
+ }, |
|
| 150 |
+ }, |
|
| 151 |
+ }, |
|
| 152 |
+ }, |
|
| 153 |
+ }, |
|
| 154 |
+ {
|
|
| 155 |
+ name: "go 1.4", |
|
| 156 |
+ testFile: "4.txt", |
|
| 157 |
+ expectedSuites: &api.TestSuites{
|
|
| 158 |
+ Suites: []*api.TestSuite{
|
|
| 159 |
+ {
|
|
| 160 |
+ Name: "package", |
|
| 161 |
+ NumTests: 2, |
|
| 162 |
+ Duration: 0.16, |
|
| 163 |
+ Children: []*api.TestSuite{
|
|
| 164 |
+ {
|
|
| 165 |
+ Name: "package/name", |
|
| 166 |
+ NumTests: 2, |
|
| 167 |
+ Duration: 0.16, |
|
| 168 |
+ TestCases: []*api.TestCase{
|
|
| 169 |
+ {
|
|
| 170 |
+ Name: "TestOne", |
|
| 171 |
+ Duration: 0.06, |
|
| 172 |
+ }, |
|
| 173 |
+ {
|
|
| 174 |
+ Name: "TestTwo", |
|
| 175 |
+ Duration: 0.1, |
|
| 176 |
+ }, |
|
| 177 |
+ }, |
|
| 178 |
+ }, |
|
| 179 |
+ }, |
|
| 180 |
+ }, |
|
| 181 |
+ }, |
|
| 182 |
+ }, |
|
| 183 |
+ }, |
|
| 184 |
+ {
|
|
| 185 |
+ name: "multiple suites", |
|
| 186 |
+ testFile: "5.txt", |
|
| 187 |
+ expectedSuites: &api.TestSuites{
|
|
| 188 |
+ Suites: []*api.TestSuite{
|
|
| 189 |
+ {
|
|
| 190 |
+ Name: "package", |
|
| 191 |
+ NumTests: 4, |
|
| 192 |
+ NumFailed: 1, |
|
| 193 |
+ Duration: 0.31, |
|
| 194 |
+ Children: []*api.TestSuite{
|
|
| 195 |
+ {
|
|
| 196 |
+ Name: "package/name1", |
|
| 197 |
+ NumTests: 2, |
|
| 198 |
+ Duration: 0.16, |
|
| 199 |
+ TestCases: []*api.TestCase{
|
|
| 200 |
+ {
|
|
| 201 |
+ Name: "TestOne", |
|
| 202 |
+ Duration: 0.06, |
|
| 203 |
+ }, |
|
| 204 |
+ {
|
|
| 205 |
+ Name: "TestTwo", |
|
| 206 |
+ Duration: 0.1, |
|
| 207 |
+ }, |
|
| 208 |
+ }, |
|
| 209 |
+ }, |
|
| 210 |
+ {
|
|
| 211 |
+ Name: "package/name2", |
|
| 212 |
+ NumTests: 2, |
|
| 213 |
+ Duration: 0.15, |
|
| 214 |
+ NumFailed: 1, |
|
| 215 |
+ TestCases: []*api.TestCase{
|
|
| 216 |
+ {
|
|
| 217 |
+ Name: "TestOne", |
|
| 218 |
+ Duration: 0.02, |
|
| 219 |
+ FailureOutput: &api.FailureOutput{
|
|
| 220 |
+ Output: `=== RUN TestOne |
|
| 221 |
+--- FAIL: TestOne (0.02 seconds) |
|
| 222 |
+ file_test.go:11: Error message |
|
| 223 |
+ file_test.go:11: Longer |
|
| 224 |
+ error |
|
| 225 |
+ message.`, |
|
| 226 |
+ }, |
|
| 227 |
+ }, |
|
| 228 |
+ {
|
|
| 229 |
+ Name: "TestTwo", |
|
| 230 |
+ Duration: 0.13, |
|
| 231 |
+ }, |
|
| 232 |
+ }, |
|
| 233 |
+ }, |
|
| 234 |
+ }, |
|
| 235 |
+ }, |
|
| 236 |
+ }, |
|
| 237 |
+ }, |
|
| 238 |
+ }, |
|
| 239 |
+ {
|
|
| 240 |
+ name: "coverage statement", |
|
| 241 |
+ testFile: "6.txt", |
|
| 242 |
+ expectedSuites: &api.TestSuites{
|
|
| 243 |
+ Suites: []*api.TestSuite{
|
|
| 244 |
+ {
|
|
| 245 |
+ Name: "package", |
|
| 246 |
+ NumTests: 2, |
|
| 247 |
+ Duration: 0.16, |
|
| 248 |
+ Children: []*api.TestSuite{
|
|
| 249 |
+ {
|
|
| 250 |
+ Name: "package/name", |
|
| 251 |
+ NumTests: 2, |
|
| 252 |
+ Duration: 0.16, |
|
| 253 |
+ Properties: []*api.TestSuiteProperty{
|
|
| 254 |
+ {
|
|
| 255 |
+ Name: "coverage.statements.pct", |
|
| 256 |
+ Value: "13.37", |
|
| 257 |
+ }, |
|
| 258 |
+ }, |
|
| 259 |
+ TestCases: []*api.TestCase{
|
|
| 260 |
+ {
|
|
| 261 |
+ Name: "TestOne", |
|
| 262 |
+ Duration: 0.06, |
|
| 263 |
+ }, |
|
| 264 |
+ {
|
|
| 265 |
+ Name: "TestTwo", |
|
| 266 |
+ Duration: 0.1, |
|
| 267 |
+ }, |
|
| 268 |
+ }, |
|
| 269 |
+ }, |
|
| 270 |
+ }, |
|
| 271 |
+ }, |
|
| 272 |
+ }, |
|
| 273 |
+ }, |
|
| 274 |
+ }, |
|
| 275 |
+ {
|
|
| 276 |
+ name: "coverage statement in package result", |
|
| 277 |
+ testFile: "7.txt", |
|
| 278 |
+ expectedSuites: &api.TestSuites{
|
|
| 279 |
+ Suites: []*api.TestSuite{
|
|
| 280 |
+ {
|
|
| 281 |
+ Name: "package", |
|
| 282 |
+ NumTests: 2, |
|
| 283 |
+ Duration: 0.16, |
|
| 284 |
+ Children: []*api.TestSuite{
|
|
| 285 |
+ {
|
|
| 286 |
+ Name: "package/name", |
|
| 287 |
+ NumTests: 2, |
|
| 288 |
+ Duration: 0.16, |
|
| 289 |
+ Properties: []*api.TestSuiteProperty{
|
|
| 290 |
+ {
|
|
| 291 |
+ Name: "coverage.statements.pct", |
|
| 292 |
+ Value: "10.0", |
|
| 293 |
+ }, |
|
| 294 |
+ }, |
|
| 295 |
+ TestCases: []*api.TestCase{
|
|
| 296 |
+ {
|
|
| 297 |
+ Name: "TestOne", |
|
| 298 |
+ Duration: 0.06, |
|
| 299 |
+ }, |
|
| 300 |
+ {
|
|
| 301 |
+ Name: "TestTwo", |
|
| 302 |
+ Duration: 0.1, |
|
| 303 |
+ }, |
|
| 304 |
+ }, |
|
| 305 |
+ }, |
|
| 306 |
+ }, |
|
| 307 |
+ }, |
|
| 308 |
+ }, |
|
| 309 |
+ }, |
|
| 310 |
+ }, |
|
| 311 |
+ {
|
|
| 312 |
+ name: "go 1.5", |
|
| 313 |
+ testFile: "8.txt", |
|
| 314 |
+ expectedSuites: &api.TestSuites{
|
|
| 315 |
+ Suites: []*api.TestSuite{
|
|
| 316 |
+ {
|
|
| 317 |
+ Name: "package", |
|
| 318 |
+ NumTests: 2, |
|
| 319 |
+ Duration: 0.05, |
|
| 320 |
+ Children: []*api.TestSuite{
|
|
| 321 |
+ {
|
|
| 322 |
+ Name: "package/name", |
|
| 323 |
+ NumTests: 2, |
|
| 324 |
+ Duration: 0.05, |
|
| 325 |
+ TestCases: []*api.TestCase{
|
|
| 326 |
+ {
|
|
| 327 |
+ Name: "TestOne", |
|
| 328 |
+ Duration: 0.02, |
|
| 329 |
+ }, |
|
| 330 |
+ {
|
|
| 331 |
+ Name: "TestTwo", |
|
| 332 |
+ Duration: 0.03, |
|
| 333 |
+ }, |
|
| 334 |
+ }, |
|
| 335 |
+ }, |
|
| 336 |
+ }, |
|
| 337 |
+ }, |
|
| 338 |
+ }, |
|
| 339 |
+ }, |
|
| 340 |
+ }, |
|
| 341 |
+ {
|
|
| 342 |
+ name: "nested ", |
|
| 343 |
+ testFile: "9.txt", |
|
| 344 |
+ expectedSuites: &api.TestSuites{
|
|
| 345 |
+ Suites: []*api.TestSuite{
|
|
| 346 |
+ {
|
|
| 347 |
+ Name: "package", |
|
| 348 |
+ NumTests: 6, |
|
| 349 |
+ NumFailed: 1, |
|
| 350 |
+ NumSkipped: 1, |
|
| 351 |
+ Duration: 0.4, |
|
| 352 |
+ Children: []*api.TestSuite{
|
|
| 353 |
+ {
|
|
| 354 |
+ Name: "package/name", |
|
| 355 |
+ NumTests: 4, |
|
| 356 |
+ NumFailed: 1, |
|
| 357 |
+ NumSkipped: 1, |
|
| 358 |
+ Duration: 0.1, |
|
| 359 |
+ TestCases: []*api.TestCase{
|
|
| 360 |
+ {
|
|
| 361 |
+ Name: "TestOne", |
|
| 362 |
+ Duration: 0.02, |
|
| 363 |
+ }, |
|
| 364 |
+ {
|
|
| 365 |
+ Name: "TestTwo", |
|
| 366 |
+ Duration: 0.03, |
|
| 367 |
+ }, |
|
| 368 |
+ }, |
|
| 369 |
+ Children: []*api.TestSuite{
|
|
| 370 |
+ |
|
| 371 |
+ {
|
|
| 372 |
+ Name: "package/name/nested", |
|
| 373 |
+ NumTests: 2, |
|
| 374 |
+ NumFailed: 1, |
|
| 375 |
+ NumSkipped: 1, |
|
| 376 |
+ Duration: 0.05, |
|
| 377 |
+ TestCases: []*api.TestCase{
|
|
| 378 |
+ {
|
|
| 379 |
+ Name: "TestOne", |
|
| 380 |
+ Duration: 0.02, |
|
| 381 |
+ FailureOutput: &api.FailureOutput{
|
|
| 382 |
+ Output: `=== RUN TestOne |
|
| 383 |
+--- FAIL: TestOne (0.02 seconds) |
|
| 384 |
+ file_test.go:11: Error message |
|
| 385 |
+ file_test.go:11: Longer |
|
| 386 |
+ error |
|
| 387 |
+ message.`, |
|
| 388 |
+ }, |
|
| 389 |
+ }, |
|
| 390 |
+ {
|
|
| 391 |
+ Name: "TestTwo", |
|
| 392 |
+ Duration: 0.03, |
|
| 393 |
+ SkipMessage: &api.SkipMessage{
|
|
| 394 |
+ Message: `=== RUN TestTwo |
|
| 395 |
+--- SKIP: TestTwo (0.03 seconds) |
|
| 396 |
+ file_test.go:11: Skip message |
|
| 397 |
+PASS`, // we include this line greedily even though it does not belong to the test |
|
| 398 |
+ }, |
|
| 399 |
+ }, |
|
| 400 |
+ }, |
|
| 401 |
+ }, |
|
| 402 |
+ }, |
|
| 403 |
+ }, |
|
| 404 |
+ {
|
|
| 405 |
+ Name: "package/other", |
|
| 406 |
+ NumTests: 2, |
|
| 407 |
+ Duration: 0.3, |
|
| 408 |
+ Children: []*api.TestSuite{
|
|
| 409 |
+ |
|
| 410 |
+ {
|
|
| 411 |
+ Name: "package/other/nested", |
|
| 412 |
+ NumTests: 2, |
|
| 413 |
+ Duration: 0.3, |
|
| 414 |
+ TestCases: []*api.TestCase{
|
|
| 415 |
+ {
|
|
| 416 |
+ Name: "TestOne", |
|
| 417 |
+ Duration: 0.1, |
|
| 418 |
+ }, |
|
| 419 |
+ {
|
|
| 420 |
+ Name: "TestTwo", |
|
| 421 |
+ Duration: 0.2, |
|
| 422 |
+ }, |
|
| 423 |
+ }, |
|
| 424 |
+ }, |
|
| 425 |
+ }, |
|
| 426 |
+ }, |
|
| 427 |
+ }, |
|
| 428 |
+ }, |
|
| 429 |
+ }, |
|
| 430 |
+ }, |
|
| 431 |
+ }, |
|
| 432 |
+ {
|
|
| 433 |
+ name: "test case timing doesn't add to test suite timing", |
|
| 434 |
+ testFile: "10.txt", |
|
| 435 |
+ expectedSuites: &api.TestSuites{
|
|
| 436 |
+ Suites: []*api.TestSuite{
|
|
| 437 |
+ {
|
|
| 438 |
+ Name: "package", |
|
| 439 |
+ NumTests: 2, |
|
| 440 |
+ Duration: 2.16, |
|
| 441 |
+ Children: []*api.TestSuite{
|
|
| 442 |
+ {
|
|
| 443 |
+ Name: "package/name", |
|
| 444 |
+ NumTests: 2, |
|
| 445 |
+ Duration: 2.16, |
|
| 446 |
+ TestCases: []*api.TestCase{
|
|
| 447 |
+ {
|
|
| 448 |
+ Name: "TestOne", |
|
| 449 |
+ Duration: 0.06, |
|
| 450 |
+ }, |
|
| 451 |
+ {
|
|
| 452 |
+ Name: "TestTwo", |
|
| 453 |
+ Duration: 0.1, |
|
| 454 |
+ }, |
|
| 455 |
+ }, |
|
| 456 |
+ }, |
|
| 457 |
+ }, |
|
| 458 |
+ }, |
|
| 459 |
+ }, |
|
| 460 |
+ }, |
|
| 461 |
+ }, |
|
| 462 |
+ } |
|
| 463 |
+ |
|
| 464 |
+ for _, testCase := range testCases {
|
|
| 465 |
+ parser := NewParser(nested.NewTestSuitesBuilder(testCase.rootSuiteNames)) |
|
| 466 |
+ |
|
| 467 |
+ testFile := "./../../../test/gotest/testdata/" + testCase.testFile |
|
| 468 |
+ |
|
| 469 |
+ reader, err := os.Open(testFile) |
|
| 470 |
+ if err != nil {
|
|
| 471 |
+ t.Errorf("%s: unexpected error opening file %q: %v", testCase.name, testFile, err)
|
|
| 472 |
+ continue |
|
| 473 |
+ } |
|
| 474 |
+ testSuites, err := parser.Parse(bufio.NewScanner(reader)) |
|
| 475 |
+ if err != nil {
|
|
| 476 |
+ t.Errorf("%s: unexpected error parsing file: %v", testCase.name, err)
|
|
| 477 |
+ continue |
|
| 478 |
+ } |
|
| 479 |
+ |
|
| 480 |
+ if !reflect.DeepEqual(testSuites, testCase.expectedSuites) {
|
|
| 481 |
+ fmt.Println(util.ObjectGoPrintDiff(testSuites, testCase.expectedSuites)) |
|
| 482 |
+ t.Errorf("%s: did not produce the correct test suites from file:\n\texpected:\n\t%v,\n\tgot\n\t%v", testCase.name, testCase.expectedSuites, testSuites)
|
|
| 483 |
+ } |
|
| 484 |
+ } |
|
| 485 |
+} |
| 0 | 486 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,12 @@ |
| 0 |
+package parser |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bufio" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/openshift/origin/tools/junitreport/pkg/api" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+// TestOutputParser knows how to parse test output to create a collection of test suites |
|
| 9 |
+type TestOutputParser interface {
|
|
| 10 |
+ Parse(input *bufio.Scanner) (*api.TestSuites, error) |
|
| 11 |
+} |
| 0 | 12 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,39 @@ |
| 0 |
+package stack |
|
| 1 |
+ |
|
| 2 |
+import "github.com/openshift/origin/tools/junitreport/pkg/api" |
|
| 3 |
+ |
|
| 4 |
+// TestDataParser knows how to take raw test data and extract the useful information from it |
|
| 5 |
+type TestDataParser interface {
|
|
| 6 |
+ // MarksBeginning determines if the line marks the begining of a test case |
|
| 7 |
+ MarksBeginning(line string) bool |
|
| 8 |
+ |
|
| 9 |
+ // ExtractName extracts the name of the test case from test output lines |
|
| 10 |
+ ExtractName(line string) (name string, succeeded bool) |
|
| 11 |
+ |
|
| 12 |
+ // ExtractResult extracts the test result from a test output line |
|
| 13 |
+ ExtractResult(line string) (result api.TestResult, succeeded bool) |
|
| 14 |
+ |
|
| 15 |
+ // ExtractDuration extracts the test duration from a test output line |
|
| 16 |
+ ExtractDuration(line string) (duration string, succeeded bool) |
|
| 17 |
+ |
|
| 18 |
+ // ExtractMessage extracts a message (e.g. for signalling why a failure or skip occured) from a test output line |
|
| 19 |
+ ExtractMessage(line string) (message string, succeeded bool) |
|
| 20 |
+ |
|
| 21 |
+ // MarksCompletion determines if the line marks the completion of a test case |
|
| 22 |
+ MarksCompletion(line string) bool |
|
| 23 |
+} |
|
| 24 |
+ |
|
| 25 |
+// TestSuiteDataParser knows how to take raw test suite data and extract the useful information from it |
|
| 26 |
+type TestSuiteDataParser interface {
|
|
| 27 |
+ // MarksBeginning determines if the line marks the begining of a test suite |
|
| 28 |
+ MarksBeginning(line string) bool |
|
| 29 |
+ |
|
| 30 |
+ // ExtractName extracts the name of the test suite from a test output line |
|
| 31 |
+ ExtractName(line string) (name string, succeeded bool) |
|
| 32 |
+ |
|
| 33 |
+ // ExtractProperties extracts any metadata properties of the test suite from a test output line |
|
| 34 |
+ ExtractProperties(line string) (properties map[string]string, succeeded bool) |
|
| 35 |
+ |
|
| 36 |
+ // MarksCompletion determines if the line marks the completion of a test suite |
|
| 37 |
+ MarksCompletion(line string) bool |
|
| 38 |
+} |
| 0 | 39 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,125 @@ |
| 0 |
+package stack |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bufio" |
|
| 4 |
+ "strings" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/openshift/origin/tools/junitreport/pkg/api" |
|
| 7 |
+ "github.com/openshift/origin/tools/junitreport/pkg/builder" |
|
| 8 |
+ "github.com/openshift/origin/tools/junitreport/pkg/parser" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+// NewParser returns a new parser that's capable of parsing Go unit test output |
|
| 12 |
+func NewParser(builder builder.TestSuitesBuilder, testParser TestDataParser, suiteParser TestSuiteDataParser) parser.TestOutputParser {
|
|
| 13 |
+ return &testOutputParser{
|
|
| 14 |
+ builder: builder, |
|
| 15 |
+ testParser: testParser, |
|
| 16 |
+ suiteParser: suiteParser, |
|
| 17 |
+ } |
|
| 18 |
+} |
|
| 19 |
+ |
|
| 20 |
+// testOutputParser uses a stack to parse test output. Critical assumptions that this parser makes are: |
|
| 21 |
+// 1 - packages may be nested but tests may not |
|
| 22 |
+// 2 - no package declarations will occur within the boundaries of a test |
|
| 23 |
+// 3 - all tests and packages are fully bounded by a start and result line |
|
| 24 |
+// 4 - if a package or test declaration occurs after the start of a package but before it's result, |
|
| 25 |
+// the sub-package's or member test's result line will occur before that of the parent package |
|
| 26 |
+// i.e. any test or package overlap will necessarily mean that one package's lines are a superset |
|
| 27 |
+// of any lines of tests or other packages overlapping with it |
|
| 28 |
+// 5 - any text in the input file that doesn't match the parser regex is necessarily the output of the |
|
| 29 |
+// current test being built |
|
| 30 |
+type testOutputParser struct {
|
|
| 31 |
+ builder builder.TestSuitesBuilder |
|
| 32 |
+ |
|
| 33 |
+ testParser TestDataParser |
|
| 34 |
+ suiteParser TestSuiteDataParser |
|
| 35 |
+} |
|
| 36 |
+ |
|
| 37 |
+// Parse parses output syntax of a specific class, the assumptions of which are outlined in the struct definition. |
|
| 38 |
+// The specific boundary markers and metadata encodings are free to vary as long as regex can be build to extract them |
|
| 39 |
+// from test output. |
|
| 40 |
+func (p *testOutputParser) Parse(input *bufio.Scanner) (*api.TestSuites, error) {
|
|
| 41 |
+ inProgress := NewTestSuiteStack() |
|
| 42 |
+ |
|
| 43 |
+ var currentTest *api.TestCase |
|
| 44 |
+ var currentResult api.TestResult |
|
| 45 |
+ var currentOutput []string |
|
| 46 |
+ var currentMessage string |
|
| 47 |
+ |
|
| 48 |
+ for input.Scan() {
|
|
| 49 |
+ line := input.Text() |
|
| 50 |
+ isTestOutput := true |
|
| 51 |
+ |
|
| 52 |
+ if p.testParser.MarksBeginning(line) {
|
|
| 53 |
+ currentTest = &api.TestCase{}
|
|
| 54 |
+ currentResult = api.TestResultFail |
|
| 55 |
+ currentOutput = []string{}
|
|
| 56 |
+ currentMessage = "" |
|
| 57 |
+ } |
|
| 58 |
+ |
|
| 59 |
+ if name, contained := p.testParser.ExtractName(line); contained {
|
|
| 60 |
+ currentTest.Name = name |
|
| 61 |
+ } |
|
| 62 |
+ |
|
| 63 |
+ if result, contained := p.testParser.ExtractResult(line); contained {
|
|
| 64 |
+ currentResult = result |
|
| 65 |
+ } |
|
| 66 |
+ |
|
| 67 |
+ if duration, contained := p.testParser.ExtractDuration(line); contained {
|
|
| 68 |
+ if err := currentTest.SetDuration(duration); err != nil {
|
|
| 69 |
+ return nil, err |
|
| 70 |
+ } |
|
| 71 |
+ } |
|
| 72 |
+ |
|
| 73 |
+ if message, contained := p.testParser.ExtractMessage(line); contained {
|
|
| 74 |
+ currentMessage = message |
|
| 75 |
+ } |
|
| 76 |
+ |
|
| 77 |
+ if p.testParser.MarksCompletion(line) {
|
|
| 78 |
+ // if we have finished the current test case, we finalize our current test, add it to the package |
|
| 79 |
+ // at the head of our in progress package stack, and clear our current test record. |
|
| 80 |
+ output := strings.Join(currentOutput, "\n") |
|
| 81 |
+ |
|
| 82 |
+ switch currentResult {
|
|
| 83 |
+ case api.TestResultSkip: |
|
| 84 |
+ currentTest.MarkSkipped(currentMessage) |
|
| 85 |
+ case api.TestResultFail: |
|
| 86 |
+ currentTest.MarkFailed(currentMessage, output) |
|
| 87 |
+ } |
|
| 88 |
+ |
|
| 89 |
+ inProgress.Peek().AddTestCase(currentTest) |
|
| 90 |
+ currentTest = &api.TestCase{}
|
|
| 91 |
+ } |
|
| 92 |
+ |
|
| 93 |
+ if p.suiteParser.MarksBeginning(line) {
|
|
| 94 |
+ // if we encounter the beginning of a suite, we create a new suite to be considered and |
|
| 95 |
+ // add it to the head of our in progress package stack |
|
| 96 |
+ inProgress.Push(&api.TestSuite{})
|
|
| 97 |
+ isTestOutput = false |
|
| 98 |
+ } |
|
| 99 |
+ |
|
| 100 |
+ if name, contained := p.suiteParser.ExtractName(line); contained {
|
|
| 101 |
+ inProgress.Peek().Name = name |
|
| 102 |
+ isTestOutput = false |
|
| 103 |
+ } |
|
| 104 |
+ |
|
| 105 |
+ if properties, contained := p.suiteParser.ExtractProperties(line); contained {
|
|
| 106 |
+ for propertyName := range properties {
|
|
| 107 |
+ inProgress.Peek().AddProperty(propertyName, properties[propertyName]) |
|
| 108 |
+ } |
|
| 109 |
+ isTestOutput = false |
|
| 110 |
+ } |
|
| 111 |
+ |
|
| 112 |
+ if p.suiteParser.MarksCompletion(line) {
|
|
| 113 |
+ // if we encounter the end of a suite, we remove the suite at the head of the in progress stack |
|
| 114 |
+ p.builder.AddSuite(inProgress.Pop()) |
|
| 115 |
+ isTestOutput = false |
|
| 116 |
+ } |
|
| 117 |
+ |
|
| 118 |
+ // we want to associate every line other than those directly involved with test suites as output of a test case |
|
| 119 |
+ if isTestOutput {
|
|
| 120 |
+ currentOutput = append(currentOutput, line) |
|
| 121 |
+ } |
|
| 122 |
+ } |
|
| 123 |
+ return p.builder.Build(), nil |
|
| 124 |
+} |
| 0 | 125 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,73 @@ |
| 0 |
+package stack |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/openshift/origin/tools/junitreport/pkg/api" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+// TestSuiteStack is a data structure that holds api.TestSuite objects in a LIFO |
|
| 9 |
+type TestSuiteStack interface {
|
|
| 10 |
+ // Push adds the testSuite to the top of the LIFO |
|
| 11 |
+ Push(pkg *api.TestSuite) |
|
| 12 |
+ // Pop removes the head of the LIFO and returns it |
|
| 13 |
+ Pop() *api.TestSuite |
|
| 14 |
+ // Peek returns a reference to the head of the LIFO without removing it |
|
| 15 |
+ Peek() *api.TestSuite |
|
| 16 |
+ // IsEmpty determines if the stack has any members |
|
| 17 |
+ IsEmpty() bool |
|
| 18 |
+} |
|
| 19 |
+ |
|
| 20 |
+// NewTestSuiteStack returns a new TestSuiteStack |
|
| 21 |
+func NewTestSuiteStack() TestSuiteStack {
|
|
| 22 |
+ return &testSuiteStack{
|
|
| 23 |
+ head: nil, |
|
| 24 |
+ } |
|
| 25 |
+} |
|
| 26 |
+ |
|
| 27 |
+// testSuiteStack is an implementation of a TestSuiteStack using a linked list |
|
| 28 |
+type testSuiteStack struct {
|
|
| 29 |
+ head *testSuiteNode |
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+// Push adds the testSuite to the top of the LIFO |
|
| 33 |
+func (s *testSuiteStack) Push(data *api.TestSuite) {
|
|
| 34 |
+ newNode := &testSuiteNode{
|
|
| 35 |
+ Member: data, |
|
| 36 |
+ Next: s.head, |
|
| 37 |
+ } |
|
| 38 |
+ s.head = newNode |
|
| 39 |
+} |
|
| 40 |
+ |
|
| 41 |
+// Pop removes the head of the LIFO and returns it |
|
| 42 |
+func (s *testSuiteStack) Pop() *api.TestSuite {
|
|
| 43 |
+ if s.IsEmpty() {
|
|
| 44 |
+ return nil |
|
| 45 |
+ } |
|
| 46 |
+ oldNode := s.head |
|
| 47 |
+ s.head = s.head.Next |
|
| 48 |
+ return oldNode.Member |
|
| 49 |
+} |
|
| 50 |
+ |
|
| 51 |
+// Peek returns a reference to the head of the LIFO without removing it |
|
| 52 |
+func (s *testSuiteStack) Peek() *api.TestSuite {
|
|
| 53 |
+ if s.IsEmpty() {
|
|
| 54 |
+ return nil |
|
| 55 |
+ } |
|
| 56 |
+ return s.head.Member |
|
| 57 |
+} |
|
| 58 |
+ |
|
| 59 |
+// IsEmpty determines if the stack has any members |
|
| 60 |
+func (s *testSuiteStack) IsEmpty() bool {
|
|
| 61 |
+ return s.head == nil |
|
| 62 |
+} |
|
| 63 |
+ |
|
| 64 |
+// testSuiteNode is a node in a singly-linked list |
|
| 65 |
+type testSuiteNode struct {
|
|
| 66 |
+ Member *api.TestSuite |
|
| 67 |
+ Next *testSuiteNode |
|
| 68 |
+} |
|
| 69 |
+ |
|
| 70 |
+func (n *testSuiteNode) String() string {
|
|
| 71 |
+ return fmt.Sprintf("{Member: %s, Next: %s}", n.Member, n.Next.String())
|
|
| 72 |
+} |
| 0 | 73 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,179 @@ |
| 0 |
+package stack |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "reflect" |
|
| 4 |
+ "testing" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/openshift/origin/tools/junitreport/pkg/api" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func TestPush(t *testing.T) {
|
|
| 10 |
+ var testCases = []struct {
|
|
| 11 |
+ name string |
|
| 12 |
+ stackSeed *testSuiteNode |
|
| 13 |
+ testSuiteToPush *api.TestSuite |
|
| 14 |
+ expectedStack *testSuiteNode |
|
| 15 |
+ }{
|
|
| 16 |
+ {
|
|
| 17 |
+ name: "push on empty stack", |
|
| 18 |
+ stackSeed: nil, |
|
| 19 |
+ testSuiteToPush: newTestSuite("test"),
|
|
| 20 |
+ expectedStack: newTestSuiteNode(newTestSuite("test"), nil),
|
|
| 21 |
+ }, |
|
| 22 |
+ {
|
|
| 23 |
+ name: "push on existing stack", |
|
| 24 |
+ stackSeed: newTestSuiteNode(newTestSuite("test"), nil),
|
|
| 25 |
+ testSuiteToPush: newTestSuite("test2"),
|
|
| 26 |
+ expectedStack: newTestSuiteNode(newTestSuite("test2"), newTestSuiteNode(newTestSuite("test"), nil)),
|
|
| 27 |
+ }, |
|
| 28 |
+ {
|
|
| 29 |
+ name: "push on deep stack", |
|
| 30 |
+ stackSeed: newTestSuiteNode(newTestSuite("test2"), newTestSuiteNode(newTestSuite("test3"), nil)),
|
|
| 31 |
+ testSuiteToPush: newTestSuite("test1"),
|
|
| 32 |
+ expectedStack: newTestSuiteNode(newTestSuite("test1"), newTestSuiteNode(newTestSuite("test2"), newTestSuiteNode(newTestSuite("test3"), nil))),
|
|
| 33 |
+ }, |
|
| 34 |
+ } |
|
| 35 |
+ |
|
| 36 |
+ for _, testCase := range testCases {
|
|
| 37 |
+ testStack := &testSuiteStack{
|
|
| 38 |
+ head: testCase.stackSeed, |
|
| 39 |
+ } |
|
| 40 |
+ testStack.Push(testCase.testSuiteToPush) |
|
| 41 |
+ |
|
| 42 |
+ if !reflect.DeepEqual(testStack.head, testCase.expectedStack) {
|
|
| 43 |
+ t.Errorf("%s: did not get correct stack state after push:\n\texpected:\n\t%s\n\tgot:\n\t%s\n", testCase.name, testCase.expectedStack, testStack.head)
|
|
| 44 |
+ } |
|
| 45 |
+ } |
|
| 46 |
+} |
|
| 47 |
+ |
|
| 48 |
+func TestPop(t *testing.T) {
|
|
| 49 |
+ var testCases = []struct {
|
|
| 50 |
+ name string |
|
| 51 |
+ stackSeed *testSuiteNode |
|
| 52 |
+ expectedTestSuite *api.TestSuite |
|
| 53 |
+ expectedStack *testSuiteNode |
|
| 54 |
+ }{
|
|
| 55 |
+ {
|
|
| 56 |
+ name: "pop on empty stack", |
|
| 57 |
+ stackSeed: nil, |
|
| 58 |
+ expectedTestSuite: nil, |
|
| 59 |
+ expectedStack: nil, |
|
| 60 |
+ }, |
|
| 61 |
+ {
|
|
| 62 |
+ name: "pop on existing stack", |
|
| 63 |
+ stackSeed: newTestSuiteNode(newTestSuite("test"), nil),
|
|
| 64 |
+ expectedTestSuite: newTestSuite("test"),
|
|
| 65 |
+ expectedStack: nil, |
|
| 66 |
+ }, |
|
| 67 |
+ {
|
|
| 68 |
+ name: "pop on deep stack", |
|
| 69 |
+ stackSeed: newTestSuiteNode(newTestSuite("test"), newTestSuiteNode(newTestSuite("test2"), nil)),
|
|
| 70 |
+ expectedTestSuite: newTestSuite("test"),
|
|
| 71 |
+ expectedStack: newTestSuiteNode(newTestSuite("test2"), nil),
|
|
| 72 |
+ }, |
|
| 73 |
+ } |
|
| 74 |
+ |
|
| 75 |
+ for _, testCase := range testCases {
|
|
| 76 |
+ testStack := &testSuiteStack{
|
|
| 77 |
+ head: testCase.stackSeed, |
|
| 78 |
+ } |
|
| 79 |
+ testSuite := testStack.Pop() |
|
| 80 |
+ if !reflect.DeepEqual(testSuite, testCase.expectedTestSuite) {
|
|
| 81 |
+ t.Errorf("%s: did not get correct package from pop:\n\texpected:\n\t%s\n\tgot:\n\t%s\n", testCase.name, testCase.expectedTestSuite, testSuite)
|
|
| 82 |
+ } |
|
| 83 |
+ if !reflect.DeepEqual(testStack.head, testCase.expectedStack) {
|
|
| 84 |
+ t.Errorf("%s: did not get correct stack state after pop:\n\texpected:\n\t%s\n\tgot:\n\t%s\n", testCase.name, testCase.expectedStack, testStack.head)
|
|
| 85 |
+ } |
|
| 86 |
+ } |
|
| 87 |
+} |
|
| 88 |
+ |
|
| 89 |
+func TestPeek(t *testing.T) {
|
|
| 90 |
+ var testCases = []struct {
|
|
| 91 |
+ name string |
|
| 92 |
+ stackSeed *testSuiteNode |
|
| 93 |
+ expectedTestSuite *api.TestSuite |
|
| 94 |
+ expectedStack *testSuiteNode |
|
| 95 |
+ }{
|
|
| 96 |
+ {
|
|
| 97 |
+ name: "peek on empty stack", |
|
| 98 |
+ stackSeed: nil, |
|
| 99 |
+ expectedTestSuite: nil, |
|
| 100 |
+ expectedStack: nil, |
|
| 101 |
+ }, |
|
| 102 |
+ {
|
|
| 103 |
+ name: "peek on existing stack", |
|
| 104 |
+ stackSeed: newTestSuiteNode(newTestSuite("test"), nil),
|
|
| 105 |
+ expectedTestSuite: newTestSuite("test"),
|
|
| 106 |
+ expectedStack: newTestSuiteNode(newTestSuite("test"), nil),
|
|
| 107 |
+ }, |
|
| 108 |
+ {
|
|
| 109 |
+ name: "peek on deep stack", |
|
| 110 |
+ stackSeed: newTestSuiteNode(newTestSuite("test"), newTestSuiteNode(newTestSuite("test2"), nil)),
|
|
| 111 |
+ expectedTestSuite: newTestSuite("test"),
|
|
| 112 |
+ expectedStack: newTestSuiteNode(newTestSuite("test"), newTestSuiteNode(newTestSuite("test2"), nil)),
|
|
| 113 |
+ }, |
|
| 114 |
+ } |
|
| 115 |
+ |
|
| 116 |
+ for _, testCase := range testCases {
|
|
| 117 |
+ testStack := &testSuiteStack{
|
|
| 118 |
+ head: testCase.stackSeed, |
|
| 119 |
+ } |
|
| 120 |
+ testSuite := testStack.Peek() |
|
| 121 |
+ if !reflect.DeepEqual(testSuite, testCase.expectedTestSuite) {
|
|
| 122 |
+ t.Errorf("%s: did not get correct package from pop:\n\texpected:\n\t%s\n\tgot:\n\t%s\n", testCase.name, testCase.expectedTestSuite, testSuite)
|
|
| 123 |
+ } |
|
| 124 |
+ if !reflect.DeepEqual(testStack.head, testCase.expectedStack) {
|
|
| 125 |
+ t.Errorf("%s: did not get correct stack state after pop:\n\texpected:\n\t%s\n\tgot:\n\t%s\n", testCase.name, testCase.expectedStack, testStack.head)
|
|
| 126 |
+ } |
|
| 127 |
+ } |
|
| 128 |
+} |
|
| 129 |
+ |
|
| 130 |
+func TestIsEmpty(t *testing.T) {
|
|
| 131 |
+ var testCases = []struct {
|
|
| 132 |
+ name string |
|
| 133 |
+ stackSeed *testSuiteNode |
|
| 134 |
+ expectedState bool |
|
| 135 |
+ expectedStack *testSuiteNode |
|
| 136 |
+ }{
|
|
| 137 |
+ {
|
|
| 138 |
+ name: "isempty on empty stack", |
|
| 139 |
+ stackSeed: nil, |
|
| 140 |
+ expectedState: true, |
|
| 141 |
+ expectedStack: nil, |
|
| 142 |
+ }, |
|
| 143 |
+ {
|
|
| 144 |
+ name: "isempty on existing stack", |
|
| 145 |
+ stackSeed: newTestSuiteNode(newTestSuite("test"), nil),
|
|
| 146 |
+ expectedState: false, |
|
| 147 |
+ expectedStack: newTestSuiteNode(newTestSuite("test"), nil),
|
|
| 148 |
+ }, |
|
| 149 |
+ } |
|
| 150 |
+ |
|
| 151 |
+ for _, testCase := range testCases {
|
|
| 152 |
+ testStack := &testSuiteStack{
|
|
| 153 |
+ head: testCase.stackSeed, |
|
| 154 |
+ } |
|
| 155 |
+ state := testStack.IsEmpty() |
|
| 156 |
+ |
|
| 157 |
+ if state != testCase.expectedState {
|
|
| 158 |
+ t.Errorf("%s: did not get correct stack emptiness after push: expected: %t got: %t\n", testCase.name, testCase.expectedState, state)
|
|
| 159 |
+ } |
|
| 160 |
+ |
|
| 161 |
+ if !reflect.DeepEqual(testStack.head, testCase.expectedStack) {
|
|
| 162 |
+ t.Errorf("%s: did not get correct stack state after push:\n\texpected:\n\t%s\n\tgot:\n\t%s\n", testCase.name, testCase.expectedStack, testStack.head)
|
|
| 163 |
+ } |
|
| 164 |
+ } |
|
| 165 |
+} |
|
| 166 |
+ |
|
| 167 |
+func newTestSuite(name string) *api.TestSuite {
|
|
| 168 |
+ return &api.TestSuite{
|
|
| 169 |
+ Name: name, |
|
| 170 |
+ } |
|
| 171 |
+} |
|
| 172 |
+ |
|
| 173 |
+func newTestSuiteNode(testSuite *api.TestSuite, next *testSuiteNode) *testSuiteNode {
|
|
| 174 |
+ return &testSuiteNode{
|
|
| 175 |
+ Member: testSuite, |
|
| 176 |
+ Next: next, |
|
| 177 |
+ } |
|
| 178 |
+} |
| 0 | 179 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,7 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package/name" tests="2" skipped="0" failures="0" time="2.16"> |
|
| 3 |
+ <testcase name="TestOne" time="0.06"></testcase> |
|
| 4 |
+ <testcase name="TestTwo" time="0.1"></testcase> |
|
| 5 |
+ </testsuite> |
|
| 6 |
+</testsuites> |
| 0 | 7 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,9 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package" tests="2" skipped="0" failures="0" time="2.16"> |
|
| 3 |
+ <testsuite name="package/name" tests="2" skipped="0" failures="0" time="2.16"> |
|
| 4 |
+ <testcase name="TestOne" time="0.06"></testcase> |
|
| 5 |
+ <testcase name="TestTwo" time="0.1"></testcase> |
|
| 6 |
+ </testsuite> |
|
| 7 |
+ </testsuite> |
|
| 8 |
+</testsuites> |
| 0 | 9 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,7 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package/name" tests="2" skipped="0" failures="0" time="0.16"> |
|
| 3 |
+ <testcase name="TestOne" time="0.06"></testcase> |
|
| 4 |
+ <testcase name="TestTwo" time="0.1"></testcase> |
|
| 5 |
+ </testsuite> |
|
| 6 |
+</testsuites> |
| 0 | 7 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,9 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package" tests="2" skipped="0" failures="0" time="0.16"> |
|
| 3 |
+ <testsuite name="package/name" tests="2" skipped="0" failures="0" time="0.16"> |
|
| 4 |
+ <testcase name="TestOne" time="0.06"></testcase> |
|
| 5 |
+ <testcase name="TestTwo" time="0.1"></testcase> |
|
| 6 |
+ </testsuite> |
|
| 7 |
+ </testsuite> |
|
| 8 |
+</testsuites> |
| 0 | 9 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,7 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package/name" tests="2" skipped="0" failures="0" time="0.16"> |
|
| 3 |
+ <testcase name="TestOne" time="0.06"></testcase> |
|
| 4 |
+ <testcase name="TestTwo" time="0.1"></testcase> |
|
| 5 |
+ </testsuite> |
|
| 6 |
+</testsuites> |
| 0 | 7 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,9 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package/name" tests="2" skipped="0" failures="1" time="0.15"> |
|
| 3 |
+ <testcase name="TestOne" time="0.02"> |
|
| 4 |
+ <failure message="">=== RUN TestOne
--- FAIL: TestOne (0.02 seconds)
	file_test.go:11: Error message
	file_test.go:11: Longer
		error
		message.</failure> |
|
| 5 |
+ </testcase> |
|
| 6 |
+ <testcase name="TestTwo" time="0.13"></testcase> |
|
| 7 |
+ </testsuite> |
|
| 8 |
+</testsuites> |
| 0 | 9 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,11 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package" tests="2" skipped="0" failures="1" time="0.15"> |
|
| 3 |
+ <testsuite name="package/name" tests="2" skipped="0" failures="1" time="0.15"> |
|
| 4 |
+ <testcase name="TestOne" time="0.02"> |
|
| 5 |
+ <failure message="">=== RUN TestOne
--- FAIL: TestOne (0.02 seconds)
	file_test.go:11: Error message
	file_test.go:11: Longer
		error
		message.</failure> |
|
| 6 |
+ </testcase> |
|
| 7 |
+ <testcase name="TestTwo" time="0.13"></testcase> |
|
| 8 |
+ </testsuite> |
|
| 9 |
+ </testsuite> |
|
| 10 |
+</testsuites> |
| 0 | 11 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,9 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package/name" tests="2" skipped="1" failures="0" time="0.15"> |
|
| 3 |
+ <testcase name="TestOne" time="0.02"> |
|
| 4 |
+ <skipped message="=== RUN TestOne
--- SKIP: TestOne (0.02 seconds)
	file_test.go:11: Skip message"></skipped> |
|
| 5 |
+ </testcase> |
|
| 6 |
+ <testcase name="TestTwo" time="0.13"></testcase> |
|
| 7 |
+ </testsuite> |
|
| 8 |
+</testsuites> |
| 0 | 9 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,11 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package" tests="2" skipped="1" failures="0" time="0.15"> |
|
| 3 |
+ <testsuite name="package/name" tests="2" skipped="1" failures="0" time="0.15"> |
|
| 4 |
+ <testcase name="TestOne" time="0.02"> |
|
| 5 |
+ <skipped message="=== RUN TestOne
--- SKIP: TestOne (0.02 seconds)
	file_test.go:11: Skip message"></skipped> |
|
| 6 |
+ </testcase> |
|
| 7 |
+ <testcase name="TestTwo" time="0.13"></testcase> |
|
| 8 |
+ </testsuite> |
|
| 9 |
+ </testsuite> |
|
| 10 |
+</testsuites> |
| 0 | 11 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,7 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package/name" tests="2" skipped="0" failures="0" time="0.16"> |
|
| 3 |
+ <testcase name="TestOne" time="0.06"></testcase> |
|
| 4 |
+ <testcase name="TestTwo" time="0.1"></testcase> |
|
| 5 |
+ </testsuite> |
|
| 6 |
+</testsuites> |
| 0 | 7 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,9 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package" tests="2" skipped="0" failures="0" time="0.16"> |
|
| 3 |
+ <testsuite name="package/name" tests="2" skipped="0" failures="0" time="0.16"> |
|
| 4 |
+ <testcase name="TestOne" time="0.06"></testcase> |
|
| 5 |
+ <testcase name="TestTwo" time="0.1"></testcase> |
|
| 6 |
+ </testsuite> |
|
| 7 |
+ </testsuite> |
|
| 8 |
+</testsuites> |
| 0 | 9 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,13 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package/name1" tests="2" skipped="0" failures="0" time="0.16"> |
|
| 3 |
+ <testcase name="TestOne" time="0.06"></testcase> |
|
| 4 |
+ <testcase name="TestTwo" time="0.1"></testcase> |
|
| 5 |
+ </testsuite> |
|
| 6 |
+ <testsuite name="package/name2" tests="2" skipped="0" failures="1" time="0.15"> |
|
| 7 |
+ <testcase name="TestOne" time="0.02"> |
|
| 8 |
+ <failure message="">=== RUN TestOne
--- FAIL: TestOne (0.02 seconds)
	file_test.go:11: Error message
	file_test.go:11: Longer
		error
		message.</failure> |
|
| 9 |
+ </testcase> |
|
| 10 |
+ <testcase name="TestTwo" time="0.13"></testcase> |
|
| 11 |
+ </testsuite> |
|
| 12 |
+</testsuites> |
| 0 | 13 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,15 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package" tests="4" skipped="0" failures="1" time="0.31"> |
|
| 3 |
+ <testsuite name="package/name1" tests="2" skipped="0" failures="0" time="0.16"> |
|
| 4 |
+ <testcase name="TestOne" time="0.06"></testcase> |
|
| 5 |
+ <testcase name="TestTwo" time="0.1"></testcase> |
|
| 6 |
+ </testsuite> |
|
| 7 |
+ <testsuite name="package/name2" tests="2" skipped="0" failures="1" time="0.15"> |
|
| 8 |
+ <testcase name="TestOne" time="0.02"> |
|
| 9 |
+ <failure message="">=== RUN TestOne
--- FAIL: TestOne (0.02 seconds)
	file_test.go:11: Error message
	file_test.go:11: Longer
		error
		message.</failure> |
|
| 10 |
+ </testcase> |
|
| 11 |
+ <testcase name="TestTwo" time="0.13"></testcase> |
|
| 12 |
+ </testsuite> |
|
| 13 |
+ </testsuite> |
|
| 14 |
+</testsuites> |
| 0 | 15 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,8 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package/name" tests="2" skipped="0" failures="0" time="0.16"> |
|
| 3 |
+ <propery name="coverage.statements.pct" value="13.37"></propery> |
|
| 4 |
+ <testcase name="TestOne" time="0.06"></testcase> |
|
| 5 |
+ <testcase name="TestTwo" time="0.1"></testcase> |
|
| 6 |
+ </testsuite> |
|
| 7 |
+</testsuites> |
| 0 | 8 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,10 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package" tests="2" skipped="0" failures="0" time="0.16"> |
|
| 3 |
+ <testsuite name="package/name" tests="2" skipped="0" failures="0" time="0.16"> |
|
| 4 |
+ <propery name="coverage.statements.pct" value="13.37"></propery> |
|
| 5 |
+ <testcase name="TestOne" time="0.06"></testcase> |
|
| 6 |
+ <testcase name="TestTwo" time="0.1"></testcase> |
|
| 7 |
+ </testsuite> |
|
| 8 |
+ </testsuite> |
|
| 9 |
+</testsuites> |
| 0 | 10 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,8 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package/name" tests="2" skipped="0" failures="0" time="0.16"> |
|
| 3 |
+ <propery name="coverage.statements.pct" value="10.0"></propery> |
|
| 4 |
+ <testcase name="TestOne" time="0.06"></testcase> |
|
| 5 |
+ <testcase name="TestTwo" time="0.1"></testcase> |
|
| 6 |
+ </testsuite> |
|
| 7 |
+</testsuites> |
| 0 | 8 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,10 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package" tests="2" skipped="0" failures="0" time="0.16"> |
|
| 3 |
+ <testsuite name="package/name" tests="2" skipped="0" failures="0" time="0.16"> |
|
| 4 |
+ <propery name="coverage.statements.pct" value="10.0"></propery> |
|
| 5 |
+ <testcase name="TestOne" time="0.06"></testcase> |
|
| 6 |
+ <testcase name="TestTwo" time="0.1"></testcase> |
|
| 7 |
+ </testsuite> |
|
| 8 |
+ </testsuite> |
|
| 9 |
+</testsuites> |
| 0 | 10 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,7 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package/name" tests="2" skipped="0" failures="0" time="0.05"> |
|
| 3 |
+ <testcase name="TestOne" time="0.02"></testcase> |
|
| 4 |
+ <testcase name="TestTwo" time="0.03"></testcase> |
|
| 5 |
+ </testsuite> |
|
| 6 |
+</testsuites> |
| 0 | 7 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,9 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package" tests="2" skipped="0" failures="0" time="0.05"> |
|
| 3 |
+ <testsuite name="package/name" tests="2" skipped="0" failures="0" time="0.05"> |
|
| 4 |
+ <testcase name="TestOne" time="0.02"></testcase> |
|
| 5 |
+ <testcase name="TestTwo" time="0.03"></testcase> |
|
| 6 |
+ </testsuite> |
|
| 7 |
+ </testsuite> |
|
| 8 |
+</testsuites> |
| 0 | 9 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,19 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package/name" tests="2" skipped="0" failures="0" time="0.05"> |
|
| 3 |
+ <testcase name="TestOne" time="0.02"></testcase> |
|
| 4 |
+ <testcase name="TestTwo" time="0.03"></testcase> |
|
| 5 |
+ </testsuite> |
|
| 6 |
+ <testsuite name="package/name/nested" tests="2" skipped="1" failures="1" time="0.05"> |
|
| 7 |
+ <testcase name="TestOne" time="0.02"> |
|
| 8 |
+ <failure message="">=== RUN TestOne
--- FAIL: TestOne (0.02 seconds)
	file_test.go:11: Error message
	file_test.go:11: Longer
		error
		message.</failure> |
|
| 9 |
+ </testcase> |
|
| 10 |
+ <testcase name="TestTwo" time="0.03"> |
|
| 11 |
+ <skipped message="=== RUN TestTwo
--- SKIP: TestTwo (0.03 seconds)
	file_test.go:11: Skip message
PASS"></skipped> |
|
| 12 |
+ </testcase> |
|
| 13 |
+ </testsuite> |
|
| 14 |
+ <testsuite name="package/other/nested" tests="2" skipped="0" failures="0" time="0.3"> |
|
| 15 |
+ <testcase name="TestOne" time="0.1"></testcase> |
|
| 16 |
+ <testcase name="TestTwo" time="0.2"></testcase> |
|
| 17 |
+ </testsuite> |
|
| 18 |
+</testsuites> |
| 0 | 19 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,23 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package" tests="6" skipped="1" failures="1" time="0.4"> |
|
| 3 |
+ <testsuite name="package/name" tests="4" skipped="1" failures="1" time="0.1"> |
|
| 4 |
+ <testcase name="TestOne" time="0.02"></testcase> |
|
| 5 |
+ <testcase name="TestTwo" time="0.03"></testcase> |
|
| 6 |
+ <testsuite name="package/name/nested" tests="2" skipped="1" failures="1" time="0.05"> |
|
| 7 |
+ <testcase name="TestOne" time="0.02"> |
|
| 8 |
+ <failure message="">=== RUN TestOne
--- FAIL: TestOne (0.02 seconds)
	file_test.go:11: Error message
	file_test.go:11: Longer
		error
		message.</failure> |
|
| 9 |
+ </testcase> |
|
| 10 |
+ <testcase name="TestTwo" time="0.03"> |
|
| 11 |
+ <skipped message="=== RUN TestTwo
--- SKIP: TestTwo (0.03 seconds)
	file_test.go:11: Skip message
PASS"></skipped> |
|
| 12 |
+ </testcase> |
|
| 13 |
+ </testsuite> |
|
| 14 |
+ </testsuite> |
|
| 15 |
+ <testsuite name="package/other" tests="2" skipped="0" failures="0" time="0.3"> |
|
| 16 |
+ <testsuite name="package/other/nested" tests="2" skipped="0" failures="0" time="0.3"> |
|
| 17 |
+ <testcase name="TestOne" time="0.1"></testcase> |
|
| 18 |
+ <testcase name="TestTwo" time="0.2"></testcase> |
|
| 19 |
+ </testsuite> |
|
| 20 |
+ </testsuite> |
|
| 21 |
+ </testsuite> |
|
| 22 |
+</testsuites> |
| 0 | 23 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,21 @@ |
| 0 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<testsuites> |
|
| 2 |
+ <testsuite name="package/name" tests="4" skipped="1" failures="1" time="0.1"> |
|
| 3 |
+ <testcase name="TestOne" time="0.02"></testcase> |
|
| 4 |
+ <testcase name="TestTwo" time="0.03"></testcase> |
|
| 5 |
+ <testsuite name="package/name/nested" tests="2" skipped="1" failures="1" time="0.05"> |
|
| 6 |
+ <testcase name="TestOne" time="0.02"> |
|
| 7 |
+ <failure message="">=== RUN TestOne
--- FAIL: TestOne (0.02 seconds)
	file_test.go:11: Error message
	file_test.go:11: Longer
		error
		message.</failure> |
|
| 8 |
+ </testcase> |
|
| 9 |
+ <testcase name="TestTwo" time="0.03"> |
|
| 10 |
+ <skipped message="=== RUN TestTwo
--- SKIP: TestTwo (0.03 seconds)
	file_test.go:11: Skip message
PASS"></skipped> |
|
| 11 |
+ </testcase> |
|
| 12 |
+ </testsuite> |
|
| 13 |
+ </testsuite> |
|
| 14 |
+ <testsuite name="package/other" tests="2" skipped="0" failures="0" time="0.3"> |
|
| 15 |
+ <testsuite name="package/other/nested" tests="2" skipped="0" failures="0" time="0.3"> |
|
| 16 |
+ <testcase name="TestOne" time="0.1"></testcase> |
|
| 17 |
+ <testcase name="TestTwo" time="0.2"></testcase> |
|
| 18 |
+ </testsuite> |
|
| 19 |
+ </testsuite> |
|
| 20 |
+</testsuites> |
| 0 | 2 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,10 @@ |
| 0 |
+Of 2 tests executed in 0.150s, 1 succeeded, 1 failed, and 0 were skipped. |
|
| 1 |
+ |
|
| 2 |
+In suite "package/name", test case "TestOne" failed: |
|
| 3 |
+=== RUN TestOne |
|
| 4 |
+--- FAIL: TestOne (0.02 seconds) |
|
| 5 |
+ file_test.go:11: Error message |
|
| 6 |
+ file_test.go:11: Longer |
|
| 7 |
+ error |
|
| 8 |
+ message. |
|
| 9 |
+ |
| 0 | 2 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,10 @@ |
| 0 |
+Of 4 tests executed in 0.310s, 3 succeeded, 1 failed, and 0 were skipped. |
|
| 1 |
+ |
|
| 2 |
+In suite "package/name2", test case "TestOne" failed: |
|
| 3 |
+=== RUN TestOne |
|
| 4 |
+--- FAIL: TestOne (0.02 seconds) |
|
| 5 |
+ file_test.go:11: Error message |
|
| 6 |
+ file_test.go:11: Longer |
|
| 7 |
+ error |
|
| 8 |
+ message. |
|
| 9 |
+ |
| 0 | 2 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,16 @@ |
| 0 |
+Of 6 tests executed in 0.400s, 4 succeeded, 1 failed, and 1 was skipped. |
|
| 1 |
+ |
|
| 2 |
+In suite "package/name/nested", test case "TestOne" failed: |
|
| 3 |
+=== RUN TestOne |
|
| 4 |
+--- FAIL: TestOne (0.02 seconds) |
|
| 5 |
+ file_test.go:11: Error message |
|
| 6 |
+ file_test.go:11: Longer |
|
| 7 |
+ error |
|
| 8 |
+ message. |
|
| 9 |
+ |
|
| 10 |
+In suite "package/name/nested", test case "TestTwo" was skipped: |
|
| 11 |
+=== RUN TestTwo |
|
| 12 |
+--- SKIP: TestTwo (0.03 seconds) |
|
| 13 |
+ file_test.go:11: Skip message |
|
| 14 |
+PASS |
|
| 15 |
+ |
| 0 | 6 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,17 @@ |
| 0 |
+=== RUN TestOne |
|
| 1 |
+--- PASS: TestOne (0.06 seconds) |
|
| 2 |
+=== RUN TestTwo |
|
| 3 |
+--- PASS: TestTwo (0.10 seconds) |
|
| 4 |
+PASS |
|
| 5 |
+ok package/name1 0.160s |
|
| 6 |
+=== RUN TestOne |
|
| 7 |
+--- FAIL: TestOne (0.02 seconds) |
|
| 8 |
+ file_test.go:11: Error message |
|
| 9 |
+ file_test.go:11: Longer |
|
| 10 |
+ error |
|
| 11 |
+ message. |
|
| 12 |
+=== RUN TestTwo |
|
| 13 |
+--- PASS: TestTwo (0.13 seconds) |
|
| 14 |
+FAIL |
|
| 15 |
+exit status 1 |
|
| 16 |
+FAIL package/name2 0.150s |
| 0 | 6 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,23 @@ |
| 0 |
+=== RUN TestOne |
|
| 1 |
+--- PASS: TestOne (0.02s) |
|
| 2 |
+=== RUN TestTwo |
|
| 3 |
+--- PASS: TestTwo (0.03s) |
|
| 4 |
+PASS |
|
| 5 |
+ok package/name 0.050s |
|
| 6 |
+=== RUN TestOne |
|
| 7 |
+--- FAIL: TestOne (0.02 seconds) |
|
| 8 |
+ file_test.go:11: Error message |
|
| 9 |
+ file_test.go:11: Longer |
|
| 10 |
+ error |
|
| 11 |
+ message. |
|
| 12 |
+=== RUN TestTwo |
|
| 13 |
+--- SKIP: TestTwo (0.03 seconds) |
|
| 14 |
+ file_test.go:11: Skip message |
|
| 15 |
+PASS |
|
| 16 |
+ok package/name/nested 0.050s |
|
| 17 |
+=== RUN TestOne |
|
| 18 |
+--- PASS: TestOne (0.1s) |
|
| 19 |
+=== RUN TestTwo |
|
| 20 |
+--- PASS: TestTwo (0.2s) |
|
| 21 |
+PASS |
|
| 22 |
+ok package/other/nested 0.300s |
|
| 0 | 23 |
\ No newline at end of file |
| 1 | 24 |
new file mode 100755 |
| ... | ... |
@@ -0,0 +1,71 @@ |
| 0 |
+#!/bin/bash |
|
| 1 |
+ |
|
| 2 |
+# This file runs the integration tests for the `junitreport` binary to ensure that correct jUnit XML is produced. |
|
| 3 |
+ |
|
| 4 |
+set -o errexit |
|
| 5 |
+set -o nounset |
|
| 6 |
+set -o pipefail |
|
| 7 |
+ |
|
| 8 |
+JUNITREPORT_ROOT=$(dirname "${BASH_SOURCE}")/..
|
|
| 9 |
+pushd "${JUNITREPORT_ROOT}" > /dev/null
|
|
| 10 |
+ |
|
| 11 |
+TMPDIR="/tmp/junitreport/test/integration" |
|
| 12 |
+mkdir -p "${TMPDIR}"
|
|
| 13 |
+ |
|
| 14 |
+echo "[INFO] Building junitreport binary for testing..." |
|
| 15 |
+go build . |
|
| 16 |
+ |
|
| 17 |
+for suite in test/*/; do |
|
| 18 |
+ suite_name=$( basename ${suite} )
|
|
| 19 |
+ echo "[INFO] Testing suite ${suite_name}..."
|
|
| 20 |
+ |
|
| 21 |
+ WORKINGDIR="${TMPDIR}/${suite_name}"
|
|
| 22 |
+ mkdir -p "${WORKINGDIR}"
|
|
| 23 |
+ |
|
| 24 |
+ # test every case with flat and nested suites |
|
| 25 |
+ for test in ${suite}/testdata/*.txt; do
|
|
| 26 |
+ test_name=$( basename ${test} | grep -Po ".+(?=\.txt)" )
|
|
| 27 |
+ |
|
| 28 |
+ cat "${test}" | ./junitreport -type "${suite_name}" -suites flat > "${WORKINGDIR}/${test_name}_flat.xml"
|
|
| 29 |
+ if ! diff "${suite}/reports/${test_name}_flat.xml" "${WORKINGDIR}/${test_name}_flat.xml"; then
|
|
| 30 |
+ echo "[FAIL] Test '${test_name}' in suite '${suite_name}' failed for flat suite builder."
|
|
| 31 |
+ exit 1 |
|
| 32 |
+ fi |
|
| 33 |
+ |
|
| 34 |
+ cat "${test}" | ./junitreport -type "${suite_name}" -suites nested > "${WORKINGDIR}/${test_name}_nested.xml"
|
|
| 35 |
+ if ! diff "${suite}/reports/${test_name}_nested.xml" "${WORKINGDIR}/${test_name}_nested.xml"; then
|
|
| 36 |
+ echo "[FAIL] Test '${test_name}' in suite '${suite_name}' failed for nested suite builder."
|
|
| 37 |
+ exit 1 |
|
| 38 |
+ fi |
|
| 39 |
+ |
|
| 40 |
+ cat "${WORKINGDIR}/${test_name}_flat.xml" | ./junitreport summarize > "${WORKINGDIR}/${test_name}_summary.txt"
|
|
| 41 |
+ if ! diff "${suite}/summaries/${test_name}_summary.txt" "${WORKINGDIR}/${test_name}_summary.txt"; then
|
|
| 42 |
+ echo "[FAIL] Test '${test_name}' in suite '${suite_name}' failed to summarize flat XML."
|
|
| 43 |
+ fi |
|
| 44 |
+ |
|
| 45 |
+ cat "${WORKINGDIR}/${test_name}_nested.xml" | ./junitreport summarize > "${WORKINGDIR}/${test_name}_summary.txt"
|
|
| 46 |
+ if ! diff "${suite}/summaries/${test_name}_summary.txt" "${WORKINGDIR}/${test_name}_summary.txt"; then
|
|
| 47 |
+ echo "[FAIL] Test '${test_name}' in suite '${suite_name}' failed to summarize nested XML."
|
|
| 48 |
+ fi |
|
| 49 |
+ done |
|
| 50 |
+ |
|
| 51 |
+ echo "[PASS] Test output type passed: ${suite_name}"
|
|
| 52 |
+done |
|
| 53 |
+ |
|
| 54 |
+echo "[INFO] Testing restricted roots with nested suites..." |
|
| 55 |
+# test some cases with nested suites and given roots |
|
| 56 |
+cat "test/gotest/testdata/1.txt" | ./junitreport -type gotest -suites nested -roots package/name > "${TMPDIR}/gotest/1_nested_restricted.xml"
|
|
| 57 |
+if ! diff "test/gotest/reports/1_nested_restricted.xml" "${TMPDIR}/gotest/1_nested_restricted.xml"; then
|
|
| 58 |
+ echo "[FAIL] Test '1' in suite 'gotest' failed for nested suite builder with restricted roots: 'package/name'." |
|
| 59 |
+ exit 1 |
|
| 60 |
+fi |
|
| 61 |
+ |
|
| 62 |
+cat "test/gotest/testdata/9.txt" | ./junitreport -type gotest -suites nested -roots package/name,package/other > "${TMPDIR}/gotest/9_nested_restricted.xml"
|
|
| 63 |
+if ! diff "test/gotest/reports/9_nested_restricted.xml" "${TMPDIR}/gotest/9_nested_restricted.xml"; then
|
|
| 64 |
+ echo "[FAIL] Test '9' in suite 'gotest' failed for nested suite builder with restricted roots: 'package/name,package/other'." |
|
| 65 |
+ exit 1 |
|
| 66 |
+fi |
|
| 67 |
+echo "[PASS] Suite passed: restricted roots" |
|
| 68 |
+ |
|
| 69 |
+echo "[PASS] junitreport testing successful" |
|
| 70 |
+popd > /dev/null |