github.com/saucelabs/saucectl@v0.175.1/internal/report/junit/junit.go (about) 1 package junit 2 3 import ( 4 "encoding/xml" 5 "fmt" 6 "os" 7 "strconv" 8 "sync" 9 10 "github.com/rs/zerolog/log" 11 "github.com/saucelabs/saucectl/internal/junit" 12 "github.com/saucelabs/saucectl/internal/report" 13 ) 14 15 // Reporter is a junit implementation for report.Reporter. 16 type Reporter struct { 17 TestResults []report.TestResult 18 Filename string 19 lock sync.Mutex 20 } 21 22 // Add adds the test result to the summary. 23 func (r *Reporter) Add(t report.TestResult) { 24 r.lock.Lock() 25 defer r.lock.Unlock() 26 r.TestResults = append(r.TestResults, t) 27 } 28 29 // Render renders out a test summary junit report to the destination of Reporter.Filename. 30 func (r *Reporter) Render() { 31 r.lock.Lock() 32 defer r.lock.Unlock() 33 34 tt := junit.TestSuites{} 35 for _, v := range r.TestResults { 36 t := junit.TestSuite{ 37 Name: v.Name, 38 Time: strconv.Itoa(int(v.Duration.Seconds())), 39 } 40 t.Properties = append(t.Properties, extractProperties(v)...) 41 42 var allTestSuites []junit.TestSuites 43 for _, attempt := range v.Attempts { 44 allTestSuites = append(allTestSuites, attempt.TestSuites) 45 } 46 47 combinedReports := junit.MergeReports(allTestSuites...) 48 for _, ts := range combinedReports.TestSuites { 49 t.TestCases = append(t.TestCases, ts.TestCases...) 50 } 51 52 tt.TestSuites = append(tt.TestSuites, t) 53 } 54 55 tt.Compute() 56 57 b, err := xml.MarshalIndent(tt, "", " ") 58 if err != nil { 59 log.Err(err).Msg("Failed to create junit report.") 60 return 61 } 62 63 f, err := os.Create(r.Filename) 64 if err != nil { 65 log.Err(err).Msg("Failed to render junit report.") 66 return 67 } 68 defer f.Close() 69 70 if _, err = f.Write(b); err != nil { 71 log.Err(err).Msg("Failed to render junit report.") 72 return 73 } 74 _, _ = fmt.Fprint(f, "\n") 75 } 76 77 func extractProperties(r report.TestResult) []junit.Property { 78 props := []junit.Property{ 79 { 80 Name: "url", 81 Value: r.URL, 82 }, 83 { 84 Name: "browser", 85 Value: r.Browser, 86 }, 87 { 88 Name: "device", 89 Value: r.DeviceName, 90 }, 91 { 92 Name: "platform", 93 Value: r.Platform, 94 }, 95 } 96 97 var filtered []junit.Property 98 for _, p := range props { 99 // we don't want to display properties with empty values 100 if p.Value == "" { 101 continue 102 } 103 104 filtered = append(filtered, p) 105 } 106 107 return filtered 108 } 109 110 // Reset resets the reporter to its initial state. This action will delete all test results. 111 func (r *Reporter) Reset() { 112 r.lock.Lock() 113 defer r.lock.Unlock() 114 r.TestResults = make([]report.TestResult, 0) 115 } 116 117 // ArtifactRequirements returns a list of artifact types are this reporter requires to create a proper report. 118 func (r *Reporter) ArtifactRequirements() []report.ArtifactType { 119 return []report.ArtifactType{report.JUnitArtifact} 120 }