diff --git a/junit/junit.go b/junit/junit.go index be0313a..38c8cfd 100644 --- a/junit/junit.go +++ b/junit/junit.go @@ -236,5 +236,27 @@ func formatDuration(d time.Duration) string { // formatOutput combines the lines from the given output into a single string. func formatOutput(output []string) string { - return strings.Join(output, "\n") + return escapeIllegalChars(strings.Join(output, "\n")) +} + +func escapeIllegalChars(str string) string { + return strings.Map(func(r rune) rune { + if isInCharacterRange(r) { + return r + } + return '\uFFFD' + }, str) +} + +// Decide whether the given rune is in the XML Character Range, per +// the Char production of https://www.xml.com/axml/testaxml.htm, +// Section 2.2 Characters. +// From: encoding/xml/xml.go +func isInCharacterRange(r rune) (inrange bool) { + return r == 0x09 || + r == 0x0A || + r == 0x0D || + r >= 0x20 && r <= 0xD7FF || + r >= 0xE000 && r <= 0xFFFD || + r >= 0x10000 && r <= 0x10FFFF } diff --git a/junit/junit_test.go b/junit/junit_test.go index a19478c..627428c 100644 --- a/junit/junit_test.go +++ b/junit/junit_test.go @@ -26,6 +26,11 @@ func TestCreateFromReport(t *testing.T) { Result: gtr.Pass, Output: []string{"ok"}, }, + { + Name: "TestEscapeOutput", + Result: gtr.Pass, + Output: []string{"\x00\v\f \t\\"}, + }, { Name: "TestFail", Result: gtr.Fail, @@ -47,14 +52,14 @@ func TestCreateFromReport(t *testing.T) { } want := Testsuites{ - Tests: 6, + Tests: 7, Errors: 3, Failures: 1, Skipped: 1, Suites: []Testsuite{ { Name: "package/name", - Tests: 6, + Tests: 7, Errors: 3, ID: 0, Failures: 1, @@ -72,6 +77,12 @@ func TestCreateFromReport(t *testing.T) { Time: "0.000", SystemOut: &Output{Data: "ok"}, }, + { + Name: "TestEscapeOutput", + Classname: "package/name", + Time: "0.000", + SystemOut: &Output{Data: `��� \`}, + }, { Name: "TestFail", Classname: "package/name", @@ -123,17 +134,19 @@ func TestMarshalUnmarshal(t *testing.T) { Disabled: 1, Suites: []Testsuite{ { - Name: "suite1", - Tests: 1, - Errors: 1, - Failures: 1, - Hostname: "localhost", - ID: 0, - Package: "package", - Skipped: 1, - Time: "12.345", - Timestamp: "2012-03-09T14:38:06+01:00", - Properties: properties("key", "value"), + Name: "suite1", + Tests: 1, + Errors: 1, + Failures: 1, + Hostname: "localhost", + ID: 0, + Package: "package", + Skipped: 1, + Time: "12.345", + Timestamp: "2012-03-09T14:38:06+01:00", + Properties: &[]Property{ + {Name: "key", Value: "value"}, + }, Testcases: []Testcase{ { Name: "test1", @@ -168,14 +181,3 @@ func TestMarshalUnmarshal(t *testing.T) { t.Errorf("Unmarshal result incorrect, diff (-want +got):\n%s\n", diff) } } - -func properties(keyvals ...string) *[]Property { - if len(keyvals)%2 != 0 { - panic("invalid keyvals specified") - } - var props []Property - for i := 0; i < len(keyvals); i += 2 { - props = append(props, Property{keyvals[i], keyvals[i+1]}) - } - return &props -} diff --git a/parser/gotest/report_builder.go b/parser/gotest/report_builder.go index d53b323..5369d8b 100644 --- a/parser/gotest/report_builder.go +++ b/parser/gotest/report_builder.go @@ -120,6 +120,11 @@ func (b *reportBuilder) Build() gtr.Report { } b.packages = append(b.packages, b.CreatePackage(name, b.packageName, "", 0, "")) } + + // Create packages for any leftover build errors. + for _, buildErr := range b.buildErrors { + b.packages = append(b.packages, b.CreatePackage("", buildErr.Name, "", 0, "")) + } return gtr.Report{Packages: b.packages} } @@ -145,7 +150,7 @@ func (b *reportBuilder) CreatePackage(packageName, newPackageName, result string // First check if this package contained a build error. If that's the case, // we won't find any tests in this package. for id, buildErr := range b.buildErrors { - if buildErr.Name == newPackageName { + if buildErr.Name == newPackageName || strings.TrimSuffix(buildErr.Name, "_test") == newPackageName { pkg.BuildError = buildErr pkg.BuildError.ID = id pkg.BuildError.Duration = duration @@ -164,7 +169,16 @@ func (b *reportBuilder) CreatePackage(packageName, newPackageName, result string delete(b.packageBuilders, packageName) pb.output.SetActiveID(0) + // If the packageBuilder is empty, we never received any events for this + // package so there's no need to continue. if pb.IsEmpty() { + // However, we should at least report an error if the result says we + // failed. + if parseResult(result) == gtr.Fail { + pkg.RunError = gtr.Error{ + Name: newPackageName, + } + } return pkg } diff --git a/parser/gotest/report_builder_test.go b/parser/gotest/report_builder_test.go index c89855b..bd24bf5 100644 --- a/parser/gotest/report_builder_test.go +++ b/parser/gotest/report_builder_test.go @@ -121,7 +121,7 @@ func TestReport(t *testing.T) { } got := rb.Build() if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("FromEvents report incorrect, diff (-want, +got):\n%v", diff) + t.Errorf("Incorrect report created, diff (-want, +got):\n%v", diff) } } @@ -181,7 +181,7 @@ func TestBuildReportMultiplePackages(t *testing.T) { } got := rb.Build() if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("FromEvents report incorrect, diff (-want, +got):\n%v", diff) + t.Errorf("Incorrect report created, diff (-want, +got):\n%v", diff) } } @@ -349,3 +349,82 @@ func TestGroupBenchmarksByName(t *testing.T) { }) } } + +func TestReportSpecialCases(t *testing.T) { + tests := []struct { + name string + events []Event + want gtr.Report + }{ + { + "failed-summary-only", + []Event{{Type: "summary", Result: "FAIL", Name: "package/name", Duration: 1 * time.Millisecond}}, + gtr.Report{ + Packages: []gtr.Package{ + { + Name: "package/name", + Duration: 1 * time.Millisecond, + Timestamp: testTimestamp, + RunError: gtr.Error{ + Name: "package/name", + }, + }, + }, + }, + }, + { + "leftover-builderror", + []Event{ + {Type: "build_output", Name: "package/name"}, + {Type: "output", Data: "error message"}, + }, + gtr.Report{ + Packages: []gtr.Package{ + { + Name: "package/name", + Timestamp: testTimestamp, + BuildError: gtr.Error{ + ID: 1, + Name: "package/name", + Output: []string{"error message"}, + }, + }, + }, + }, + }, + { + "build error in package with _test suffix", + []Event{ + {Type: "build_output", Name: "package/name_test"}, + {Type: "summary", Name: "package/name", Result: "FAIL", Data: "[build failed]"}, + }, + gtr.Report{ + Packages: []gtr.Package{ + { + Name: "package/name", + Timestamp: testTimestamp, + BuildError: gtr.Error{ + ID: 1, + Name: "package/name_test", + Cause: "[build failed]", + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + rb := newReportBuilder() + rb.timestampFunc = testTimestampFunc + for _, ev := range test.events { + rb.ProcessEvent(ev) + } + got := rb.Build() + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("Incorrect report created, diff (-want, +got):\n%v\n", diff) + } + }) + } +} diff --git a/testdata/038-report.xml b/testdata/038-report.xml new file mode 100644 index 0000000..edb773a --- /dev/null +++ b/testdata/038-report.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/testdata/038-test-pkg-name.txt b/testdata/038-test-pkg-name.txt new file mode 100644 index 0000000..2763c5a --- /dev/null +++ b/testdata/038-test-pkg-name.txt @@ -0,0 +1,4 @@ +# package/testpkg/pkg_test [package/testpkg/pkg.test] +pkg/pkg_test.go:5:2: imported and not used: "fmt" +FAIL package/testpkg/pkg [build failed] +FAIL