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  }