Atlas › Test

TestParseAlerts_DataRaceAndPanic

Exact test identity: go.temporal.io/server/tools/testrunner/TestParseAlerts_DataRaceAndPanic

Package
go.temporal.io/server/tools/testrunner
Suite / test hierarchy
TestParseAlerts_DataRaceAndPanic
Test
TestParseAlerts_DataRaceAndPanic
Introduced at
log.go ×27 Frontier kind: Joint frontier
Covered ranges
38
Covered lines
136
Covered files
2

Covered source

Expand a file to inspect source; the > gutter marks covered lines.

go.temporal.io/server/tools/testrunner/log.go 126 covered LOC · 34 ranges

Open complete file

86 // 1) Fully-qualified test name containing ".Test"
87 // 2) First detected test name
88 > func primaryTestName(tests []string) string { log.go
89 > if len(tests) == 0 {
90 return ""
91 }
92 > for _, t := range tests { log.go
93 > if strings.Contains(t, ".Test") {
94 > return t log.go
95 > }
96 }
97 return tests[0]
120 // alerts such as data races and panics. It returns a slice of alerts in the
121 // order they were encountered.
122 > func parseAlerts(stdout string) []alert { log.go
123 > lines := strings.Split(strings.ReplaceAll(stdout, "\r\n", "\n"), "\n")
124 > var alerts []alert
125 >
126 > for i := 0; i < len(lines); i++ {
127 > line := lines[i]
128 >
129 > if a, next, ok := tryParseDataRace(lines, i, line); ok {
130 > alerts = append(alerts, a)
131 > i = next
132 > continue
133 }
134 > if a, next, ok := tryParsePanic(lines, i, line); ok { log.go
135 > alerts = append(alerts, a)
136 > i = next
137 > continue
138 }
139 > if a, next, ok := tryParseFatal(lines, i, line); ok { log.go
140 alerts = append(alerts, a)
141 i = next
144 }
145
146 > return alerts log.go
147 }
148
149 // extractTestNames tries to identify Go test function names from a log block.
150 // It looks for fully-qualified names like pkg.TestXxx(...) and Go test failure lines.
151 > func extractTestNames(block string) []string { log.go
152 > var tests []string
153 > seen := make(map[string]struct{})
154 > for line := range strings.SplitSeq(block, "\n") {
155 > l := strings.TrimSpace(line)
156 > if l == "" {
157 > continue
158 }
159 > if name, ok := parseTripleDashTestName(l); ok { log.go
160 addUniqueTest(&tests, seen, name)
161 continue
162 }
163 > if name, ok := parseFullyQualifiedTestName(l); ok { log.go
164 > addUniqueTest(&tests, seen, name)
165 > continue
166 }
167 > if name, ok := parsePlainTestName(l); ok { log.go
168 addUniqueTest(&tests, seen, name)
169 }
170 }
171 > return tests log.go
172 }
173
174 // addUniqueTest appends name to tests if not already seen.
175 > func addUniqueTest(tests *[]string, seen map[string]struct{}, name string) { log.go
176 > if _, ok := seen[name]; ok {
177 > return log.go
178 > }
179 > seen[name] = struct{}{} log.go
180 > *tests = append(*tests, name)
181 }
182
183 // parseTripleDashTestName parses Go test failure lines and returns the test name if present.
184 > func parseTripleDashTestName(line string) (string, bool) { log.go
185 > if !strings.HasPrefix(line, goTestFailLinePrefix) {
186 > return "", false log.go
187 > }
188 name := strings.TrimSpace(strings.TrimPrefix(line, goTestFailLinePrefix))
189 name, _, _ = strings.Cut(name, " ")
195
196 // parseFullyQualifiedTestName extracts names like "pkg/path.TestName" from a line.
197 > func parseFullyQualifiedTestName(line string) (string, bool) { log.go
198 > idx := strings.Index(line, ".Test")
199 > if idx < 0 {
200 > return "", false
201 > }
202 // Include the package/path qualifier preceding ".Test"
203 > start := 0 log.go
204 > if sp := strings.LastIndex(line[:idx], " "); sp >= 0 {
205 start = sp + 1
206 }
207 > if p := strings.Index(line[idx:], "("); p > 0 { log.go
208 > return line[start : idx+p], true
209 > }
210 return "", false
211 }
212
213 // parsePlainTestName extracts a leading "TestName(" form.
214 > func parsePlainTestName(line string) (string, bool) { log.go
215 > if !strings.HasPrefix(line, "Test") || !strings.Contains(line, "(") {
216 > return "", false
217 > }
218 name := line
219 if p := strings.Index(name, "("); p > 0 {
224
225 // tryParseDataRace parses a data race alert at position i if present.
226 > func tryParseDataRace(lines []string, i int, line string) (alert, int, bool) { log.go
227 > if !strings.HasPrefix(line, "WARNING: DATA RACE") {
228 > return alert{}, i, false
229 > }
230 > start := findRaceBlockStart(lines, i)
231 > // Merge contiguous race-report sections into a single alert. The Go race
232 > // detector may emit multiple "WARNING: DATA RACE" blocks back-to-back,
233 > // each wrapped by a line of ==================. Treat adjacent sections as
234 > // a single logical alert until we either hit a test boundary or a race
235 > // boundary that is not followed by another race section.
236 > block, end := collectBlock(lines, start, func(curLine string, idx, start int) bool {
237 > // Stop at PASS/FAIL boundaries always.
238 > if isTestResultBoundary(curLine) {
239 return true
240 }
241 // If we hit a race boundary after we've started, only stop if the next
242 // non-current line does not continue the race report.
243 > if idx > start && isRaceBoundary(curLine) { log.go
244 > if idx+1 < len(lines) {
245 > next := strings.TrimSpace(lines[idx+1])
246 > if isRaceBoundary(next) || strings.HasPrefix(next, "WARNING: DATA RACE") {
247 > return false
248 > }
249 }
250 > return true log.go
251 }
252 > return false log.go
253 })
254 > return alert{ log.go
255 > Type: failureTypeDataRace,
256 > Summary: "Data race detected",
257 > Details: block,
258 > Tests: extractTestNames(block),
259 > }, end, true
260 }
261
262 // tryParsePanic parses a non-timeout panic alert at position i if present.
263 > func tryParsePanic(lines []string, i int, line string) (alert, int, bool) { log.go
264 > if !strings.HasPrefix(line, "panic: ") || strings.HasPrefix(line, "panic: test timed out after") {
265 > return alert{}, i, false
266 > }
267 > block, end := collectBlock(lines, i, shouldStopOnTestBoundary)
268 > return alert{
269 > Type: failureTypePanic,
270 > Summary: strings.TrimSpace(strings.TrimPrefix(line, "panic: ")),
271 > Details: block,
272 > Tests: extractTestNames(block),
273 > }, end, true
274 }
275
276 // tryParseFatal parses a runtime fatal error alert at position i if present.
277 > func tryParseFatal(lines []string, i int, line string) (alert, int, bool) { log.go
278 > if !strings.HasPrefix(line, "fatal error: ") {
279 > return alert{}, i, false
280 > }
281 block, end := collectBlock(lines, i, shouldStopOnTestBoundary)
282 return alert{
289
290 // findRaceBlockStart searches upward for the race report delimiter.
291 > func findRaceBlockStart(lines []string, i int) int { log.go
292 > start := i
293 > for j := i - 1; j >= 0; j-- {
294 > if isRaceBoundary(lines[j]) {
295 > start = j
296 > break
297 }
298 }
299 > return start log.go
300 }
301
302 // collectBlock builds a block from start until the stop condition is met.
303 > func collectBlock(lines []string, start int, stop func(line string, idx, start int) bool) (string, int) { log.go
304 > var b strings.Builder
305 > for j := start; j < len(lines); j++ {
306 > b.WriteString(lines[j])
307 > b.WriteByte('\n')
308 > if stop(lines[j], j, start) {
309 > return b.String(), j
310 > }
311 }
312 return b.String(), len(lines) - 1
313 }
314
315 > func isRaceBoundary(line string) bool { log.go
316 > return strings.HasPrefix(strings.TrimSpace(line), "==================")
317 > }
318
319 > func isTestResultBoundary(line string) bool { log.go
320 > return strings.HasPrefix(line, "FAIL") || strings.HasPrefix(line, "PASS")
321 > }
322
323 > func shouldStopOnTestBoundary(line string, _ int, _ int) bool { log.go
324 > return isTestResultBoundary(line)
325 > }
326
327 // parseFailedTestsFromOutput extracts failing test names from gotestsum stdout.
go.temporal.io/server/tools/testrunner/junit.go 10 covered LOC · 4 ranges

Open complete file

201 // dedupeAlerts removes duplicate alerts (e.g., repeated across retries) based
202 // on type and details while preserving the first-seen order.
203 > func dedupeAlerts(alerts []alert) []alert { junit.go
204 > seen := make(map[string]struct{}, len(alerts))
205 > var out []alert
206 > for _, a := range alerts {
207 > key := string(a.Type) + "\n" + a.Details
208 > if _, ok := seen[key]; ok {
209 > continue junit.go
210 }
211 > seen[key] = struct{}{} junit.go
212 > out = append(out, a)
213 }
214 > return out junit.go
215 }
216