1
8
9 package reporters
10
11 import (
12 "fmt"
13 "os"
14 "path"
15 "strings"
16
17 "github.com/onsi/ginkgo/v2/types"
18 )
19
20 func tcEscape(s string) string {
21 s = strings.ReplaceAll(s, "|", "||")
22 s = strings.ReplaceAll(s, "'", "|'")
23 s = strings.ReplaceAll(s, "\n", "|n")
24 s = strings.ReplaceAll(s, "\r", "|r")
25 s = strings.ReplaceAll(s, "[", "|[")
26 s = strings.ReplaceAll(s, "]", "|]")
27 return s
28 }
29
30 func GenerateTeamcityReport(report types.Report, dst string) error {
31 if err := os.MkdirAll(path.Dir(dst), 0770); err != nil {
32 return err
33 }
34 f, err := os.Create(dst)
35 if err != nil {
36 return err
37 }
38
39 name := report.SuiteDescription
40 labels := report.SuiteLabels
41 if len(labels) > 0 {
42 name = name + " [" + strings.Join(labels, ", ") + "]"
43 }
44 fmt.Fprintf(f, "##teamcity[testSuiteStarted name='%s']\n", tcEscape(name))
45 for _, spec := range report.SpecReports {
46 name := fmt.Sprintf("[%s]", spec.LeafNodeType)
47 if spec.FullText() != "" {
48 name = name + " " + spec.FullText()
49 }
50 labels := spec.Labels()
51 if len(labels) > 0 {
52 name = name + " [" + strings.Join(labels, ", ") + "]"
53 }
54
55 name = tcEscape(name)
56 fmt.Fprintf(f, "##teamcity[testStarted name='%s']\n", name)
57 switch spec.State {
58 case types.SpecStatePending:
59 fmt.Fprintf(f, "##teamcity[testIgnored name='%s' message='pending']\n", name)
60 case types.SpecStateSkipped:
61 message := "skipped"
62 if spec.Failure.Message != "" {
63 message += " - " + spec.Failure.Message
64 }
65 fmt.Fprintf(f, "##teamcity[testIgnored name='%s' message='%s']\n", name, tcEscape(message))
66 case types.SpecStateFailed:
67 details := failureDescriptionForUnstructuredReporters(spec)
68 fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='failed - %s' details='%s']\n", name, tcEscape(spec.Failure.Message), tcEscape(details))
69 case types.SpecStatePanicked:
70 details := failureDescriptionForUnstructuredReporters(spec)
71 fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='panicked - %s' details='%s']\n", name, tcEscape(spec.Failure.ForwardedPanic), tcEscape(details))
72 case types.SpecStateTimedout:
73 details := failureDescriptionForUnstructuredReporters(spec)
74 fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='timedout - %s' details='%s']\n", name, tcEscape(spec.Failure.Message), tcEscape(details))
75 case types.SpecStateInterrupted:
76 details := failureDescriptionForUnstructuredReporters(spec)
77 fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='interrupted - %s' details='%s']\n", name, tcEscape(spec.Failure.Message), tcEscape(details))
78 case types.SpecStateAborted:
79 details := failureDescriptionForUnstructuredReporters(spec)
80 fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='aborted - %s' details='%s']\n", name, tcEscape(spec.Failure.Message), tcEscape(details))
81 }
82
83 fmt.Fprintf(f, "##teamcity[testStdOut name='%s' out='%s']\n", name, tcEscape(systemOutForUnstructuredReporters(spec)))
84 fmt.Fprintf(f, "##teamcity[testStdErr name='%s' out='%s']\n", name, tcEscape(systemErrForUnstructuredReporters(spec)))
85 fmt.Fprintf(f, "##teamcity[testFinished name='%s' duration='%d']\n", name, int(spec.RunTime.Seconds()*1000.0))
86 }
87 fmt.Fprintf(f, "##teamcity[testSuiteFinished name='%s']\n", tcEscape(report.SuiteDescription))
88
89 return f.Close()
90 }
91
92 func MergeAndCleanupTeamcityReports(sources []string, dst string) ([]string, error) {
93 messages := []string{}
94 merged := []byte{}
95 for _, source := range sources {
96 data, err := os.ReadFile(source)
97 if err != nil {
98 messages = append(messages, fmt.Sprintf("Could not open %s:\n%s", source, err.Error()))
99 continue
100 }
101 os.Remove(source)
102 merged = append(merged, data...)
103 }
104 return messages, os.WriteFile(dst, merged, 0666)
105 }
106
View as plain text