Atlas › Test

TestRunnerPrintSummary

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

Package
go.temporal.io/server/tools/testrunner
Suite / test hierarchy
TestRunnerPrintSummary
Test
TestRunnerPrintSummary
Introduced at
testrunner.go ×5 Frontier kind: Joint frontier
Covered ranges
48
Covered lines
120
Covered files
3

Covered source

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

go.temporal.io/server/tools/testrunner/summary.go 58 covered LOC · 18 ranges

Open complete file

17 }
18
19 > func newSummaryFromReports(reports []*junitReport) summary { summary.go
20 > return summary{
21 > Rows: newSummaryRowsFromReports(reports),
22 > }
23 > }
24
25 // Markdown renders the GitHub step summary HTML and enforces both the total
26 // summary budget and per-row detail truncation.
27 > func (s summary) Markdown() string { summary.go
28 > if len(s.Rows) == 0 {
29 return ""
30 }
31
32 > var sb strings.Builder summary.go
33 > sb.WriteString("<table>\n<tr><th>Kind</th><th>Test</th></tr>\n")
34 >
35 > // Reserve bytes for the closing tag so we can always finish the table.
36 > const tableClose = "</table>\n"
37 > budget := summaryMarkdownMaxBytes - sb.Len() - len(tableClose)
38 >
39 > written := 0
40 > for _, row := range s.Rows {
41 > rendered := row.Markdown()
42 > if len(rendered) > budget {
43 omitted := len(s.Rows) - written
44 fmt.Fprintf(&sb, "<tr><td colspan=\"2\">… %d failure(s) not shown — see full output in job logs</td></tr>\n", omitted)
45 break
46 }
47 > sb.WriteString(rendered) summary.go
48 > budget -= len(rendered)
49 > written++
50 }
51
52 > sb.WriteString(tableClose) summary.go
53 > return sb.String()
54 }
55
56 > func (s summary) JSON() ([]byte, error) { summary.go
57 > return json.MarshalIndent(s, "", " ")
58 > }
59
60 // summaryRow is a single failure entry rendered in both Markdown and JSON summaries.
66 }
67
68 > func newSummaryRowsFromReports(reports []*junitReport) []summaryRow { summary.go
69 > var rows []summaryRow
70 > for _, report := range reports {
71 > for _, suite := range report.Suites {
72 > for _, tc := range suite.Testcases { summary.go
73 > if tc.Failure == nil {
74 continue
75 }
76 > rows = append(rows, newSummaryRow(failureType(tc.Failure.Type), tc.Name, tc.Failure.Data)) summary.go
77 }
78 }
79 }
80 > slices.SortFunc(rows, func(a, b summaryRow) int { summary.go
81 > if byName := strings.Compare(a.Name, b.Name); byName != 0 { summary.go
82 > return byName
83 > }
84 if byKind := strings.Compare(string(a.Kind), string(b.Kind)); byKind != 0 {
85 return byKind
87 return strings.Compare(a.Details, b.Details)
88 })
89 > return rows summary.go
90 }
91
92 > func newSummaryRow(kind failureType, name string, details string) summaryRow { summary.go
93 > if len(details) > summaryMaxDetailBytes {
94 headBytes := (summaryMaxDetailBytes - len(summaryTruncatedMarker)) / 2
95 tailBytes := summaryMaxDetailBytes - len(summaryTruncatedMarker) - headBytes
96 details = details[:headBytes] + summaryTruncatedMarker + details[len(details)-tailBytes:]
97 }
98 > return summaryRow{ summary.go
99 > Kind: kind,
100 > Name: name,
101 > Details: details,
102 > Final: strings.Contains(name, "(final)"),
103 > }
104 }
105
106 // Markdown renders one summary table row.
107 > func (row summaryRow) Markdown() string { summary.go
108 > kind := string(row.Kind)
109 > if row.Final {
110 kind = "❌ " + kind
111 }
112
113 > var sb strings.Builder summary.go
114 > fmt.Fprintf(&sb, "<tr><td>%s</td><td>", html.EscapeString(kind))
115 > if row.Details != "" {
116 > escaped := html.EscapeString(row.Details)
117 > if strings.Contains(row.Details, summaryTruncatedMarker) {
118 escaped += "\n… (truncated — see full output in job logs)"
119 }
120 > fmt.Fprintf(&sb, "<details><summary>%s</summary><pre>%s</pre></details>", summary.go
121 > html.EscapeString(row.Name), escaped)
122 } else {
123 sb.WriteString(html.EscapeString(row.Name))
124 }
125 > sb.WriteString("</td></tr>\n") summary.go
126 > return sb.String()
127 }
go.temporal.io/server/tools/testrunner/testrunner.go 44 covered LOC · 24 ranges

Open complete file

81 }
82
83 > func newRunner() *runner { testrunner.go
84 > return &runner{
85 > attempts: make([]*attempt, 0),
86 > maxAttempts: 1,
87 > }
88 > }
89
90 // nolint:revive,cognitive-complexity
91 > func (r *runner) sanitizeAndParseArgs(command string, args []string) ([]string, error) { testrunner.go
92 > var sanitizedArgs []string
93 > for _, arg := range args {
94 > if strings.HasPrefix(arg, maxAttemptsFlag) { testrunner.go
95 var err error
96 r.maxAttempts, err = strconv.Atoi(strings.Split(arg, "=")[1])
104 }
105
106 > if strings.HasPrefix(arg, totalTimeoutFlag) { testrunner.go
107 var err error
108 r.totalTimeout, err = time.ParseDuration(strings.TrimPrefix(arg, totalTimeoutFlag))
116 }
117
118 > if strings.HasPrefix(arg, gotestsumPathFlag) { testrunner.go
119 r.gotestsumPath = strings.Split(arg, "=")[1]
120 continue
121 }
122
123 > if strings.HasPrefix(arg, crashReportNameFlag) { testrunner.go
124 r.crashName = strings.Split(arg, "=")[1]
125 if r.crashName == "" {
132 }
133
134 > if strings.HasPrefix(arg, junitGlobFlag) { testrunner.go
135 > r.junitGlob = strings.Split(arg, "=")[1] testrunner.go
136 > continue
137 }
138 > if strings.HasPrefix(arg, summaryOutputDirFlag) { testrunner.go
139 > r.summaryOutputDir = strings.Split(arg, "=")[1] testrunner.go
140 > if command != summaryCommand {
141 return nil, fmt.Errorf("argument %q is only valid for command %q", summaryOutputDirFlag, summaryCommand)
142 }
143 > continue testrunner.go
144 }
145 if strings.HasPrefix(arg, coverProfileFlag) {
153 }
154
155 > switch command { testrunner.go
156 case testCommand:
157 if r.coverProfilePath == "" {
171 return nil, fmt.Errorf("missing required argument %q", crashReportNameFlag)
172 }
173 > case summaryCommand: testrunner.go
174 > if r.junitGlob == "" {
175 return nil, fmt.Errorf("missing required argument %q", junitGlobFlag)
176 }
177 > if r.summaryOutputDir == "" { testrunner.go
178 return nil, fmt.Errorf("missing required argument %q", summaryOutputDirFlag)
179 }
182 }
183
184 > return sanitizedArgs, nil testrunner.go
185 }
186
256 }
257
258 > func (r *runner) generateSummary() error { testrunner.go
259 > paths, err := filepath.Glob(r.junitGlob)
260 > if err != nil {
261 return fmt.Errorf("failed to expand junit glob %q: %w", r.junitGlob, err)
262 }
263 > slices.Sort(paths) testrunner.go
264 >
265 > reports := make([]*junitReport, 0, len(paths))
266 > for _, path := range paths {
267 > report := &junitReport{path: path}
268 > if err := report.read(); err != nil {
269 return fmt.Errorf("failed to read junit report %q: %w", path, err)
270 }
271 > reports = append(reports, report) testrunner.go
272 }
273
274 > summary := newSummaryFromReports(reports) testrunner.go
275 > if len(summary.Rows) == 0 {
276 fmt.Println("no failed tests found in junit reports; skipping test summary")
277 return nil
278 }
279
280 > markdown := summary.Markdown() testrunner.go
281 > if err := os.MkdirAll(r.summaryOutputDir, 0o755); err != nil {
282 return fmt.Errorf("failed to create summary output directory: %w", err)
283 }
284 > if err := os.WriteFile(filepath.Join(r.summaryOutputDir, "test-summary.md"), []byte(markdown), 0o644); err != nil { testrunner.go
285 return fmt.Errorf("failed to write summary markdown: %w", err)
286 }
287
288 > content, err := summary.JSON() testrunner.go
289 > if err != nil {
290 return fmt.Errorf("failed to render summary json: %w", err)
291 }
292 > if err := os.WriteFile(filepath.Join(r.summaryOutputDir, "test-summary.json"), append(content, '\n'), 0o644); err != nil { testrunner.go
293 return fmt.Errorf("failed to write summary json: %w", err)
294 }
295 > return nil testrunner.go
296 }
297
go.temporal.io/server/tools/testrunner/junit.go 18 covered LOC · 6 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 }
45 > defer f.Close() junit.go
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 }
51 > return nil junit.go
52 }
53
84 }
85
86 > func (j *junitReport) write() error { junit.go
87 > f, err := os.Create(j.path)
88 > if err != nil {
89 return fmt.Errorf("failed to open junit report file: %w", err)
90 }
91 > defer f.Close() junit.go
92 >
93 > encoder := xml.NewEncoder(f)
94 > encoder.Indent("", " ")
95 > if err = encoder.Encode(j.Testsuites); err != nil {
96 return fmt.Errorf("failed to write junit report file: %w", err)
97 }
98 > log.Printf("wrote junit report to %s", j.path) junit.go
99 > return nil
100 }
101