| | | 1 | | package reporter |
| | | 2 | | |
| | | 3 | | import ( |
| | | 4 | | "context" |
| | | 5 | | "encoding/xml" |
| | | 6 | | "fmt" |
| | | 7 | | "io" |
| | | 8 | | "sort" |
| | | 9 | | "time" |
| | | 10 | | |
| | | 11 | | "github.com/jedi-knights/neospec/internal/domain" |
| | | 12 | | ) |
| | | 13 | | |
| | | 14 | | // Cobertura writes coverage data in Cobertura XML format. |
| | | 15 | | // https://cobertura.github.io/cobertura/ |
| | | 16 | | type Cobertura struct{} |
| | | 17 | | |
| | | 18 | | // NewCobertura creates a Cobertura reporter. |
| | | 19 | | func NewCobertura() *Cobertura { return &Cobertura{} } |
| | | 20 | | |
| | | 21 | | // coberturaXML is the root element of the Cobertura XML report. |
| | | 22 | | type coberturaXML struct { |
| | | 23 | | XMLName xml.Name `xml:"coverage"` |
| | | 24 | | Version string `xml:"version,attr"` |
| | | 25 | | Timestamp int64 `xml:"timestamp,attr"` |
| | | 26 | | LinesValid int `xml:"lines-valid,attr"` |
| | | 27 | | LinesCovered int `xml:"lines-covered,attr"` |
| | | 28 | | LineRate float64 `xml:"line-rate,attr"` |
| | | 29 | | Packages coberturaPackages `xml:"packages"` |
| | | 30 | | } |
| | | 31 | | |
| | | 32 | | type coberturaPackages struct { |
| | | 33 | | Packages []coberturaPackage `xml:"package"` |
| | | 34 | | } |
| | | 35 | | |
| | | 36 | | type coberturaPackage struct { |
| | | 37 | | Name string `xml:"name,attr"` |
| | | 38 | | LineRate float64 `xml:"line-rate,attr"` |
| | | 39 | | Classes coberturaClasses `xml:"classes"` |
| | | 40 | | } |
| | | 41 | | |
| | | 42 | | type coberturaClasses struct { |
| | | 43 | | Classes []coberturaClass `xml:"class"` |
| | | 44 | | } |
| | | 45 | | |
| | | 46 | | type coberturaClass struct { |
| | | 47 | | Name string `xml:"name,attr"` |
| | | 48 | | Filename string `xml:"filename,attr"` |
| | | 49 | | LineRate float64 `xml:"line-rate,attr"` |
| | | 50 | | Lines coberturaLines `xml:"lines"` |
| | | 51 | | } |
| | | 52 | | |
| | | 53 | | type coberturaLines struct { |
| | | 54 | | Lines []coberturaLine `xml:"line"` |
| | | 55 | | } |
| | | 56 | | |
| | | 57 | | type coberturaLine struct { |
| | | 58 | | Number int `xml:"number,attr"` |
| | | 59 | | Hits int `xml:"hits,attr"` |
| | | 60 | | } |
| | | 61 | | |
| | 4 | 62 | | func (c *Cobertura) Write(_ context.Context, w io.Writer, _ *domain.SuiteResult, cov *domain.CoverageData) error { |
| | 2 | 63 | | if cov == nil { |
| | 2 | 64 | | cov = &domain.CoverageData{} |
| | 2 | 65 | | } |
| | | 66 | | |
| | 4 | 67 | | lineRate := 0.0 |
| | 2 | 68 | | if cov.TotalLines() > 0 { |
| | 2 | 69 | | lineRate = float64(cov.HitLines()) / float64(cov.TotalLines()) |
| | 2 | 70 | | } |
| | | 71 | | |
| | 4 | 72 | | report := coberturaXML{ |
| | 4 | 73 | | Version: "neospec-1.0", |
| | 4 | 74 | | Timestamp: time.Now().Unix(), |
| | 4 | 75 | | LinesValid: cov.TotalLines(), |
| | 4 | 76 | | LinesCovered: cov.HitLines(), |
| | 4 | 77 | | LineRate: lineRate, |
| | 4 | 78 | | } |
| | 4 | 79 | | |
| | 3 | 80 | | for _, file := range cov.Files { |
| | 3 | 81 | | lineRate := 0.0 |
| | 3 | 82 | | if file.TotalLines() > 0 { |
| | 3 | 83 | | lineRate = float64(file.HitLines()) / float64(file.TotalLines()) |
| | 3 | 84 | | } |
| | | 85 | | |
| | 3 | 86 | | lines := make([]int, 0, len(file.Lines)) |
| | 3 | 87 | | for ln := range file.Lines { |
| | 7 | 88 | | lines = append(lines, ln) |
| | 7 | 89 | | } |
| | 3 | 90 | | sort.Ints(lines) |
| | 3 | 91 | | |
| | 3 | 92 | | cls := coberturaClass{ |
| | 3 | 93 | | Name: file.Path, |
| | 3 | 94 | | Filename: file.Path, |
| | 3 | 95 | | LineRate: lineRate, |
| | 3 | 96 | | } |
| | 3 | 97 | | for _, ln := range lines { |
| | 7 | 98 | | cls.Lines.Lines = append(cls.Lines.Lines, coberturaLine{ |
| | 7 | 99 | | Number: ln, |
| | 7 | 100 | | Hits: file.Lines[ln], |
| | 7 | 101 | | }) |
| | 7 | 102 | | } |
| | | 103 | | |
| | 3 | 104 | | pkg := coberturaPackage{ |
| | 3 | 105 | | Name: ".", |
| | 3 | 106 | | LineRate: lineRate, |
| | 3 | 107 | | } |
| | 3 | 108 | | pkg.Classes.Classes = append(pkg.Classes.Classes, cls) |
| | 3 | 109 | | report.Packages.Packages = append(report.Packages.Packages, pkg) |
| | | 110 | | } |
| | | 111 | | |
| | 4 | 112 | | fmt.Fprintln(w, `<?xml version="1.0" encoding="UTF-8"?>`) |
| | 4 | 113 | | enc := xml.NewEncoder(w) |
| | 4 | 114 | | enc.Indent("", " ") |
| | 1 | 115 | | if err := enc.Encode(report); err != nil { |
| | 1 | 116 | | return fmt.Errorf("encoding cobertura XML: %w", err) |
| | 1 | 117 | | } |
| | 3 | 118 | | return enc.Flush() |
| | | 119 | | } |