diff --git a/pkg/models/ut.go b/pkg/models/ut.go index f1fee8140..22eed68df 100644 --- a/pkg/models/ut.go +++ b/pkg/models/ut.go @@ -73,3 +73,57 @@ type Line struct { Number int `xml:"number,attr"` Hits int `xml:"hits,attr"` } + +type Jacoco struct { + Name string `xml:"name,attr"` + XMLName xml.Name `xml:"report"` + Packages []JacocoPackage `xml:"package"` + SessionInfo []SessionInfo `xml:"sessioninfo"` +} + +type SessionInfo struct { + ID string `xml:"id,attr"` + Start string `xml:"start,attr"` + Dump string `xml:"dump,attr"` +} + +type JacocoPackage struct { + Name string `xml:"name,attr"` + Classes []JacocoClass `xml:"class"` + Counters []Counter `xml:"counter"` + SourceFiles []JacocoSourceFile `xml:"sourcefile"` // Adding this field to capture source files +} + +type JacocoSourceFile struct { + Name string `xml:"name,attr"` + Lines []JacocoLine `xml:"line"` + Counters []Counter `xml:"counter"` +} + +type JacocoClass struct { + Name string `xml:"name,attr"` + SourceFile string `xml:"sourcefilename,attr"` + Methods []JacocoMethod `xml:"method"` + Lines []JacocoLine `xml:"line"` // This is where JacocoLine is used +} + +type JacocoMethod struct { + Name string `xml:"name,attr"` + Descriptor string `xml:"desc,attr"` + Line string `xml:"line,attr"` + Counters []Counter `xml:"counter"` +} + +type JacocoLine struct { + Number string `xml:"nr,attr"` + MissedInstructions string `xml:"mi,attr"` + CoveredInstructions string `xml:"ci,attr"` + MissedBranches string `xml:"mb,attr"` + CoveredBranches string `xml:"cb,attr"` +} + +type Counter struct { + Type string `xml:"type,attr"` + Missed string `xml:"missed,attr"` + Covered string `xml:"covered,attr"` +} diff --git a/pkg/service/utgen/coverage.go b/pkg/service/utgen/coverage.go index b3e432372..888609d29 100644 --- a/pkg/service/utgen/coverage.go +++ b/pkg/service/utgen/coverage.go @@ -5,6 +5,7 @@ import ( "encoding/xml" "fmt" "os" + "strconv" "strings" "time" @@ -67,6 +68,8 @@ func (cp *CoverageProcessor) ParseCoverageReport() (*models.CoverageResult, erro switch cp.Format { case "cobertura": return cp.ParseCoverageReportCobertura() + case "jacoco": + return cp.ParseCoverageReportJacoco() case "lcov": return nil, fmt.Errorf("parsing for %s coverage reports is not implemented yet", cp.Format) default: @@ -152,3 +155,95 @@ func (cp *CoverageProcessor) ParseCoverageReportCobertura() (*models.CoverageRes return coverageResult, nil } + +func (cp *CoverageProcessor) ParseCoverageReportJacoco() (*models.CoverageResult, error) { + + filesToCover := make([]string, 0) + // Open the XML file + xmlFile, err := os.Open(cp.ReportPath) + if err != nil { + return nil, err + } + + defer func() { + if err := xmlFile.Close(); err != nil { + return + } + }() + + // Decode the XML file into a Coverage struct + var jacoco models.Jacoco + if err := xml.NewDecoder(xmlFile).Decode(&jacoco); err != nil { + return nil, err + } + + // Find coverage for the specified file + var linesCovered, linesMissed []int + var totalLines, coveredLines int + var filteredSourceFiles []models.JacocoSourceFile + + for _, pkg := range jacoco.Packages { + for _, src := range pkg.SourceFiles { + if cp.SrcPath == "." { + filesToCover = append(filesToCover, src.Name) + } + if strings.HasSuffix(src.Name, cp.SrcPath) { + for _, line := range src.Lines { + totalLines++ + missedInstructions, errMissed := strconv.Atoi(line.MissedInstructions) + coveredInstructions, errCovered := strconv.Atoi(line.CoveredInstructions) + if errMissed != nil || errCovered != nil { + // Handle conversion error + continue + } + if coveredInstructions > 0 { + coveredLines++ + lineNumber, err := strconv.Atoi(line.Number) + if err == nil { + linesCovered = append(linesCovered, lineNumber) + } + } else if missedInstructions > 0 { + // Use missedInstructions to check if a line has any missed instructions + lineNumber, err := strconv.Atoi(line.Number) + if err == nil { + linesMissed = append(linesMissed, lineNumber) + } + } + } + filteredSourceFiles = append(filteredSourceFiles, src) + break + } + } + } + var coveragePercentage float64 + if totalLines > 0 { + coveragePercentage = float64(len(linesCovered)) / float64(totalLines) + } + + // Reconstruct the coverage report with only the filtered class + filteredCov := models.Jacoco{ + Packages: []models.JacocoPackage{ + { + SourceFiles: filteredSourceFiles, + }, + }, + } + + // Encode the filtered coverage report to XML + var filteredBuf bytes.Buffer + xmlEncoder := xml.NewEncoder(&filteredBuf) + xmlEncoder.Indent("", " ") + if err := xmlEncoder.Encode(filteredCov); err != nil { + return nil, err + } + + coverageResult := &models.CoverageResult{ + LinesCovered: linesCovered, + LinesMissed: linesMissed, + Coverage: coveragePercentage, + Files: filesToCover, + ReportContent: filteredBuf.String(), + } + + return coverageResult, nil +}