Atlas › Test
TestMergeReports_IterationSuffixPreserved
Exact test identity: go.temporal.io/server/tools/testrunner/TestMergeReports_IterationSuffixPreserved
- Package
go.temporal.io/server/tools/testrunner
- Suite / test hierarchy
TestMergeReports_IterationSuffixPreserved
- Test
TestMergeReports_IterationSuffixPreserved
- Introduced at
- TestMergeReports_IterationSuffixPreserved Frontier kind: Test frontier
- Covered ranges
- 32
- Covered lines
- 97
- Covered files
- 2
Covered source
Expand a file to inspect source; the > gutter marks covered lines.
go.temporal.io/server/tools/testrunner/junit.go 57 covered LOC · 16 ranges
Open complete file
38
}
39
40
>
func (j *junitReport) read() error {
junit.go
41
>
f, err := os.Open(j.path)
42
>
if err != nil {
43
return fmt.Errorf("failed to open junit report file: %w", err)
44
}
46
>
47
>
decoder := xml.NewDecoder(f)
48
>
if err = decoder.Decode(&j.Testsuites); err != nil {
49
return fmt.Errorf("failed to read junit report file: %w", err)
50
}
52
}
53
255
}
256
257
>
func mergeReports(reports []*junitReport) (*junitReport, error) {
junit.go
258
>
if len(reports) == 0 {
259
return nil, errors.New("no reports to merge")
260
}
261
262
>
var reportingErrs []error
junit.go
263
>
var combined junit.Testsuites
264
>
combined.XMLName = reports[0].XMLName
265
>
combined.Name = reports[0].Name
266
>
267
>
for i, report := range reports {
268
>
combined.Tests += report.Tests
269
>
combined.Errors += report.Errors
270
>
combined.Failures += report.Failures
271
>
combined.Skipped += report.Skipped
272
>
combined.Disabled += report.Disabled
273
>
combined.Time += report.Time
274
>
275
>
// If the report is for a retry ...
276
>
var suffix string
277
>
if i > 0 {
278
suffix = fmt.Sprintf(" (retry %d)", i)
279
if i == len(reports)-1 {
295
}
296
297
>
for _, suite := range report.Suites {
junit.go
298
>
if len(suite.Testcases) == 0 {
299
continue
300
}
301
302
>
newSuite := suite // shallow copy
junit.go
303
>
newSuite.Name += suffix
304
>
newSuite.Testcases = make([]junit.Testcase, 0, len(suite.Testcases))
305
>
306
>
// Sort test cases by name.
307
>
slices.SortFunc(suite.Testcases, func(a, b junit.Testcase) int {
308
>
return strings.Compare(a.Name, b.Name)
junit.go
309
>
})
310
311
// Collect test cases.
312
>
for j := range len(suite.Testcases) {
junit.go
313
>
testCase := suite.Testcases[j]
314
>
// Check if this is a parent test case (ie prefix of next subtest).
315
>
// Use testCase.Name+"/" to avoid matching iteration suffixes like #01.
316
>
if j != len(suite.Testcases)-1 && strings.HasPrefix(suite.Testcases[j+1].Name, testCase.Name+"/") {
317
// Discard test case parents since they provide no value.
318
continue
320
321
// Parse failure details from Failure.Data, if present.
322
>
if testCase.Failure != nil && testCase.Failure.Data != "" {
junit.go
323
>
if details := parseFailureDetails(testCase.Failure.Data); details != noFailureDetails {
324
>
testCase.Failure.Data = details
junit.go
325
>
}
326
}
327
328
// Failure.Type carries the canonical kind in merged JUnit.
329
>
if testCase.Failure != nil {
junit.go
330
>
if suite.Name == alertsSuiteName {
331
if testCase.Failure.Type == "" {
332
testCase.Failure.Type = testCase.Failure.Message
333
}
335
>
testCase.Failure.Type = string(failureTypeFailed)
336
>
}
337
}
339
>
newSuite.Testcases = append(newSuite.Testcases, testCase)
340
}
341
>
combined.Suites = append(combined.Suites, newSuite)
junit.go
342
}
343
}
344
346
>
Testsuites: combined,
347
>
reportingErrs: reportingErrs,
348
>
}, nil
349
}
350
go.temporal.io/server/tools/testrunner/log.go 40 covered LOC · 16 ranges
Open complete file
344
345
// parseFailureDetails extracts the actionable part of a JUnit failure Data block.
346
>
func parseFailureDetails(data string) string {
log.go
347
>
lines := normalizedFailureLines(data)
348
>
349
>
// Prefer assertion blocks because they contain the useful testify failure
350
>
// detail and can be selected from the end while ignoring trailing logs.
351
>
if block, ok := findLastAssertionFailureBlock(lines); ok {
352
return block
353
}
354
// Some failures, such as gomock errors, do not include an Error Trace.
355
// Fall back to the last Go test output block in those cases.
356
>
if start, end, ok := findLastTestOutputFailureBlock(lines); ok {
log.go
357
>
return strings.Join(lines[start:end], "\n")
log.go
358
>
}
359
return noFailureDetails
360
}
361
362
>
func normalizedFailureLines(data string) []string {
log.go
363
>
lines := strings.Split(strings.ReplaceAll(data, "\r\n", "\n"), "\n")
364
>
for len(lines) > 0 {
365
>
t := strings.TrimSpace(lines[len(lines)-1])
366
>
if t != "" && t != "FAIL" {
368
}
369
>
lines = lines[:len(lines)-1]
log.go
370
}
372
}
373
374
>
func findLastAssertionFailureBlock(lines []string) (string, bool) {
log.go
375
>
var failLine string
376
>
for i := len(lines) - 1; i >= 0; i-- {
377
>
line := strings.TrimSpace(lines[i])
log.go
378
>
if failLine == "" && strings.HasPrefix(line, goTestFailLinePrefix) {
379
>
// Keep the final Go test failure line because it carries the test duration.
log.go
380
>
failLine = line
381
>
continue
382
}
383
>
if !strings.Contains(lines[i], "Error Trace:") {
log.go
384
>
continue
385
}
386
400
return strings.Join(out, "\n"), true
401
}
403
}
404
422
}
423
424
>
func findLastTestOutputFailureBlock(lines []string) (start, end int, ok bool) {
log.go
425
>
for start := len(lines) - 1; start >= 0; start-- {
426
>
if !isTestOutputLine(lines[start]) {
log.go
427
>
continue
428
}
430
>
for end < len(lines) && !isTestOutputLine(lines[end]) && lines[end] != "" {
431
>
end++
432
>
}
433
>
return start, end, true
434
}
435
return 0, 0, false
440
// Testify assertion content is indented further (8+ spaces or tabs), so this
441
// distinguishes log entries from assertion block content.
442
>
func isTestOutputLine(line string) bool {
log.go
443
>
return len(line) > 4 && line[:4] == " " && line[4] != ' ' && line[4] != '\t'
444
>
}