1
16
17 package output
18
19 import (
20 "encoding/xml"
21 "os"
22 "path"
23 "regexp"
24 "testing"
25
26 "github.com/google/go-cmp/cmp"
27 "github.com/google/go-cmp/cmp/cmpopts"
28 "github.com/onsi/ginkgo/v2"
29 "github.com/onsi/ginkgo/v2/reporters"
30 "github.com/onsi/gomega"
31 "github.com/stretchr/testify/require"
32
33 "k8s.io/kubernetes/test/e2e/framework"
34 "k8s.io/kubernetes/test/e2e/framework/internal/junit"
35 )
36
37
38
39
40
41
42
43
44 func TestGinkgoOutput(t *testing.T, expected TestResult, runSpecsArgs ...interface{}) {
45 tmpdir := t.TempDir()
46 junitFile := path.Join(tmpdir, "junit.xml")
47 gomega.RegisterFailHandler(framework.Fail)
48 ginkgo.ReportAfterSuite("write JUnit file", func(report ginkgo.Report) {
49 junit.WriteJUnitReport(report, junitFile)
50 })
51 fakeT := &testing.T{}
52 ginkgo.RunSpecs(fakeT, "Logging Suite", runSpecsArgs...)
53
54 var actual reporters.JUnitTestSuites
55 data, err := os.ReadFile(junitFile)
56 require.NoError(t, err)
57 err = xml.Unmarshal(data, &actual)
58 require.NoError(t, err)
59
60 if len(actual.TestSuites) != 1 {
61 t.Fatalf("expected one test suite, got %d, JUnit content:\n%s", len(actual.TestSuites), string(data))
62 }
63 diff := cmp.Diff(expected.Suite, actual.TestSuites[0],
64
65
66
67
68 cmpopts.IgnoreFields(reporters.JUnitTestSuite{}, "Time", "Timestamp", "Name", "Package", "Properties"),
69 cmpopts.IgnoreFields(reporters.JUnitTestCase{}, "Time", "Classname"),
70 cmpopts.SortSlices(func(tc1, tc2 reporters.JUnitTestCase) bool {
71 return tc1.Name < tc2.Name
72 }),
73 cmpopts.AcyclicTransformer("simplify", func(in string) any {
74 out := simplify(in, expected)
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91 return out
92 }),
93 )
94 if diff != "" {
95 t.Fatalf("Simplified JUnit report not as expected (-want, +got):\n%s\n\nFull XML:\n%s", diff, string(data))
96 }
97 }
98
99
100
101 type TestResult struct {
102
103 NormalizeOutput func(string) string
104
105
106 Suite reporters.JUnitTestSuite
107 }
108
109 func simplify(in string, expected TestResult) string {
110 out := normalizeLocation(in)
111 out = stripTimes(out)
112 out = stripAddresses(out)
113 out = normalizeInitFunctions(out)
114 if expected.NormalizeOutput != nil {
115 out = expected.NormalizeOutput(out)
116 }
117 return out
118 }
119
120
121 var timePrefix = regexp.MustCompile(`(?m)^[[:alpha:]]{3} +[[:digit:]]{1,2} +[[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}.[[:digit:]]{3}: `)
122
123
124 var elapsedSuffix = regexp.MustCompile(`Elapsed: [[:digit:]]+(\.[[:digit:]]+)?(µs|ns|ms|s|m)`)
125
126
127 var afterSuffix = regexp.MustCompile(`after [[:digit:]]+(\.[[:digit:]]+)?(µs|ns|ms|s|m).`)
128
129
130 var timeSuffix = regexp.MustCompile(`(?m)@[[:space:]][[:digit:]]{2}/[[:digit:]]{2}/[[:digit:]]{2} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}(\.[[:digit:]]{1,3})?( \([[:digit:]]+(\.[[:digit:]]+)?(µs|ns|ms|s|m)\))?$`)
131
132 func stripTimes(in string) string {
133 out := timePrefix.ReplaceAllString(in, "")
134 out = elapsedSuffix.ReplaceAllString(out, "Elapsed: <elapsed>")
135 out = timeSuffix.ReplaceAllString(out, "<time>")
136 out = afterSuffix.ReplaceAllString(out, "after <after>.")
137 return out
138 }
139
140
141 var instanceAddr = regexp.MustCompile(` \| 0x[0-9a-fA-F]+>`)
142
143 func stripAddresses(in string) string {
144 return instanceAddr.ReplaceAllString(in, ">")
145 }
146
147
148
149 var stackLocation = regexp.MustCompile(`(?:/|vendor/|test/|GOROOT/).*/([[:^space:]]+.go:[[:digit:]]+)( \+0x[0-9a-fA-F]+)?`)
150
151
152 var functionArgs = regexp.MustCompile(`([[:alpha:][:digit:].]+)\(.*\)`)
153
154
155 var klogPrefix = regexp.MustCompile(`(?m)^[IEF][[:digit:]]{4} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}\.[[:digit:]]{6}[[:space:]]+[[:digit:]]+ `)
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170 var testFailureOutput = regexp.MustCompile(`(?m)^k8s.io/kubernetes/test/e2e/framework/internal/output\.TestGinkgoOutput\(.*\n\t.*(\n.*\n\t.*)*`)
171
172
173
174 func normalizeLocation(in string) string {
175 out := in
176 out = stackLocation.ReplaceAllString(out, "$1")
177 out = functionArgs.ReplaceAllString(out, "$1()")
178 out = testFailureOutput.ReplaceAllString(out, "")
179 out = klogPrefix.ReplaceAllString(out, "<klog> ")
180 return out
181 }
182
183 var initFunc = regexp.MustCompile(`(init\.+func|glob\.+func)`)
184
185
186
187 func normalizeInitFunctions(in string) string {
188 out := initFunc.ReplaceAllString(in, "<init.func>")
189 return out
190 }
191
View as plain text