github.com/sercand/please@v13.4.0+incompatible/src/test/xml_coverage.go (about) 1 // Code for parsing XML coverage output (eg. Java or Python). 2 3 package test 4 5 import ( 6 "github.com/thought-machine/please/src/cli" 7 "github.com/thought-machine/please/src/core" 8 "encoding/xml" 9 "math" 10 "path" 11 "strings" 12 "time" 13 ) 14 15 func parseXMLCoverageResults(target *core.BuildTarget, coverage *core.TestCoverage, data []byte) error { 16 xcoverage := xmlCoverage{} 17 if err := xml.Unmarshal(data, &xcoverage); err != nil { 18 return err 19 } 20 for _, pkg := range xcoverage.Packages.Package { 21 for _, cls := range pkg.Classes.Class { 22 filename := strings.TrimPrefix(cls.Filename, core.RepoRoot) 23 // There can be multiple classes per file so we must merge here, not overwrite. 24 coverage.Files[filename] = core.MergeCoverageLines(coverage.Files[filename], parseXMLLines(cls.Lines.Line)) 25 } 26 } 27 coverage.Tests[target.Label] = coverage.Files 28 return nil 29 } 30 31 func parseXMLLines(lines []xmlCoverageLine) []core.LineCoverage { 32 ret := []core.LineCoverage{} 33 for _, line := range lines { 34 for i := len(ret) + 1; i < line.Number; i++ { 35 ret = append(ret, core.NotExecutable) 36 } 37 if line.Hits > 0 { 38 ret = append(ret, core.Covered) 39 } else { 40 ret = append(ret, core.Uncovered) 41 } 42 } 43 return ret 44 } 45 46 // Covert the Coverage result to XML bytes, ready to be write to file 47 func coverageResultToXML(sources []core.BuildLabel, coverage core.TestCoverage) []byte { 48 linesValid := 0 49 linesCovered := 0 50 validFiles := coverage.OrderedFiles() 51 52 // get the string representative of sources 53 sourcesAsStr := make([]string, len(sources)) 54 for i, source := range sources { 55 sourcesAsStr[i] = path.Join(core.RepoRoot, source.PackageName) 56 } 57 58 // Get the list of packages for <package> tag in the coverage xml file 59 var packages []pkg 60 for label, coverage := range coverage.Tests { 61 packageName := label.String() 62 63 // Get the list of classes for <class> tag in the coverage xml file 64 var classes []class 65 classLineRateTotal := 0.0 66 for className, lineCover := range coverage { 67 // Do not include files in coverage report if its not valid 68 if !cli.ContainsString(className, validFiles) { 69 continue 70 } 71 72 name := strings.Replace(className, label.PackageName+"/", "", -1) 73 74 lines, covered, total := getLineCoverageInfo(lineCover) 75 classLineRate := float64(covered) / float64(total) 76 77 cls := class{Name: name, Filename: name, 78 Lines: lines, LineRate: formatFloatPrecision(classLineRate, 4)} 79 80 classes = append(classes, cls) 81 classLineRateTotal += classLineRate 82 linesValid += total 83 linesCovered += covered 84 } 85 86 pkgLineRate := float64(classLineRateTotal) / float64(len(classes)) 87 88 if len(classes) != 0 { 89 pkg := pkg{Name: packageName, Classes: classes, LineRate: formatFloatPrecision(pkgLineRate, 4)} 90 packages = append(packages, pkg) 91 } 92 } 93 94 topLevelLineRate := float64(linesCovered) / float64(linesValid) 95 96 // Create the coverage object based on the data collected 97 coverageObj := coverageType{Packages: packages, LineRate: formatFloatPrecision(topLevelLineRate, 4), 98 LinesCovered: int(linesCovered), LinesValid: int(linesValid), 99 Timestamp: int(time.Now().UnixNano()) / int(time.Millisecond), 100 Sources: sourcesAsStr} 101 102 // Serialise struct to xml bytes 103 xmlBytes, err := xml.MarshalIndent(coverageObj, "", " ") 104 if err != nil { 105 log.Fatalf("Failed to parse to xml: %s", err) 106 } 107 covReport := []byte(xml.Header + string(xmlBytes)) 108 return covReport 109 } 110 111 // Get the line coverage info, returns: list of lines covered, num of covered lines, and total valid lines 112 func getLineCoverageInfo(lineCover []core.LineCoverage) ([]line, int, int) { 113 var lines []line 114 covered := 0 115 total := 0 116 117 for index, status := range lineCover { 118 if status == core.Covered { 119 line := line{Hits: 1, Number: index} 120 lines = append(lines, line) 121 covered += 1 122 total += 1 123 } else if status == core.Uncovered { 124 line := line{Hits: 0, Number: index} 125 lines = append(lines, line) 126 total += 1 127 } 128 } 129 130 return lines, covered, total 131 } 132 133 // format the float64 numbers to a specific precision 134 func formatFloatPrecision(val float64, precision int) float64 { 135 unit := math.Pow10(precision) 136 newFloat := math.Round(val*unit) / unit 137 138 return float64(newFloat) 139 } 140 141 // Note that this is based off coverage.py's format, which is originally a Java format 142 // so some of the structures are a little awkward (eg. 'classes' actually refer to Python modules, not classes). 143 type xmlCoverage struct { 144 Packages struct { 145 Package []struct { 146 Classes struct { 147 Class []struct { 148 LineRate float64 `xml:"line-rate,attr"` 149 Filename string `xml:"filename,attr"` 150 Name string `xml:"name,attr"` 151 Lines struct { 152 Line []xmlCoverageLine `xml:"line"` 153 } `xml:"lines"` 154 } `xml:"class"` 155 } `xml:"classes"` 156 } `xml:"package"` 157 } `xml:"packages"` 158 } 159 160 type xmlCoverageLine struct { 161 Hits int `xml:"hits,attr"` 162 Number int `xml:"number,attr"` 163 } 164 165 // Coverage struct for writing to xml file 166 type coverageType struct { 167 XMLName xml.Name `xml:"coverage"` 168 LineRate float64 `xml:"line-rate,attr"` 169 BranchRate float64 `xml:"branch-rate,attr"` 170 LinesCovered int `xml:"lines-covered,attr"` 171 LinesValid int `xml:"lines-valid,attr"` 172 BranchesCovered int `xml:"branches-covered,attr"` 173 BranchesValid int `xml:"branches-valid,attr"` 174 Complexity float64 `xml:"complexity,attr"` 175 Version string `xml:"version,attr"` 176 Timestamp int `xml:"timestamp,attr"` 177 Sources []string `xml:"sources>source"` 178 Packages []pkg `xml:"packages>package"` 179 } 180 181 type pkg struct { 182 Name string `xml:"name,attr"` 183 LineRate float64 `xml:"line-rate,attr"` 184 BranchRate float64 `xml:"branch-rate,attr"` 185 Complexity float64 `xml:"complexity,attr"` 186 Classes []class `xml:"classes>class"` 187 LineCount int `xml:"line-count,attr"` 188 LineHits int `xml:"line-hits,attr"` 189 } 190 191 type class struct { 192 Name string `xml:"name,attr"` 193 Filename string `xml:"filename,attr"` 194 LineRate float64 `xml:"line-rate,attr"` 195 BranchRate float64 `xml:"branch-rate,attr"` 196 Complexity float64 `xml:"complexity,attr"` 197 Methods []method `xml:"methods>method"` 198 Lines []line `xml:"lines>line"` 199 } 200 201 type method struct { 202 Name string `xml:"name,attr"` 203 Signature string `xml:"signature,attr"` 204 LineRate float64 `xml:"line-rate,attr"` 205 BranchRate float64 `xml:"branch-rate,attr"` 206 Complexity float64 `xml:"complexity,attr"` 207 Lines []line `xml:"lines>line"` 208 LineCount int `xml:"line-count,attr"` 209 LineHits int `xml:"line-hits,attr"` 210 } 211 212 type line struct { 213 Number int `xml:"number,attr"` 214 Hits int `xml:"hits,attr"` 215 }