View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
5    */
6   package mockit.coverage;
7   
8   import edu.umd.cs.findbugs.annotations.NonNull;
9   
10  import java.io.File;
11  import java.io.IOException;
12  import java.io.Writer;
13  import java.nio.charset.StandardCharsets;
14  import java.nio.file.Files;
15  import java.nio.file.Path;
16  import java.util.Map.Entry;
17  
18  import mockit.coverage.data.CoverageData;
19  import mockit.coverage.data.FileCoverageData;
20  import mockit.coverage.lines.LineCoverageData;
21  import mockit.coverage.lines.PerFileLineCoverage;
22  
23  import org.checkerframework.checker.index.qual.NonNegative;
24  import org.slf4j.Logger;
25  import org.slf4j.LoggerFactory;
26  
27  /**
28   * Generates a XML file containing the coverage data gathered by the test run. The XML schema used is the one
29   * <a href="http://docs.sonarqube.org/display/SONAR/Generic+Test+Data">defined</a> by the SonarQube project:
30   *
31   * <pre>{@code
32   * &lt;coverage version="1">
33   *    &lt;file path="com/example/MyClass.java">
34   *       &lt;lineToCover lineNumber="5" covered="false"/>
35   *       &lt;lineToCover lineNumber="8" covered="true" branchesToCover="2" coveredBranches="1"/>
36   *    &lt;/file>
37   * &lt;/coverage>
38   * }</pre>
39   */
40  final class XmlFile {
41  
42      /** The logger. */
43      private static final Logger logger = LoggerFactory.getLogger(XmlFile.class);
44  
45      @NonNull
46      private final String srcDir;
47      @NonNull
48      private final File outputFile;
49      @NonNull
50      private final CoverageData coverageData;
51  
52      XmlFile(@NonNull String outputDir, @NonNull CoverageData coverageData) {
53          // noinspection DynamicRegexReplaceableByCompiledPattern
54          String firstSrcDir = Configuration.getProperty("srcDirs", "").split("\\s*,\\s*")[0];
55          srcDir = firstSrcDir.isEmpty() ? "" : firstSrcDir + '/';
56  
57          String parentDir = Configuration.getOrChooseOutputDirectory(outputDir);
58          outputFile = Path.of(parentDir, "coverage.xml").toFile();
59          this.coverageData = coverageData;
60      }
61  
62      void generate() throws IOException {
63          try (Writer out = Files.newBufferedWriter(outputFile.toPath(), StandardCharsets.UTF_8)) {
64              out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
65              out.write("<coverage version=\"1\">\n");
66  
67              for (Entry<String, FileCoverageData> fileAndData : coverageData.getFileToFileData().entrySet()) {
68                  String sourceFileName = fileAndData.getKey();
69                  writeOpeningXmlElementForSourceFile(out, sourceFileName);
70  
71                  PerFileLineCoverage lineInfo = fileAndData.getValue().lineCoverageInfo;
72                  writeXmlElementsForExecutableLines(out, lineInfo);
73  
74                  out.write("\t</file>\n");
75              }
76  
77              out.write("</coverage>\n");
78          }
79  
80          logger.info("JMockit: Coverage data written to {}", outputFile.getCanonicalPath());
81      }
82  
83      private void writeOpeningXmlElementForSourceFile(@NonNull Writer out, @NonNull String sourceFileName)
84              throws IOException {
85          out.write("\t<file path=\"");
86          out.write(srcDir);
87          out.write(sourceFileName);
88          out.write("\">\n");
89      }
90  
91      private static void writeXmlElementsForExecutableLines(@NonNull Writer out, @NonNull PerFileLineCoverage lineInfo)
92              throws IOException {
93          int lineCount = lineInfo.getLineCount();
94  
95          for (int lineNum = 1; lineNum <= lineCount; lineNum++) {
96              if (lineInfo.hasLineData(lineNum)) {
97                  LineCoverageData lineData = lineInfo.getLineData(lineNum);
98  
99                  out.write("\t\t<lineToCover lineNumber=\"");
100                 writeNumber(out, lineNum);
101                 out.write("\" covered=\"");
102                 out.write(Boolean.toString(lineData.isCovered()));
103 
104                 if (lineData.containsBranches()) {
105                     out.write("\" branchesToCover=\"");
106                     writeNumber(out, lineData.getNumberOfBranchingSourcesAndTargets());
107                     out.write("\" coveredBranches=\"");
108                     writeNumber(out, lineData.getNumberOfCoveredBranchingSourcesAndTargets());
109                 }
110 
111                 out.write("\"/>\n");
112             }
113         }
114     }
115 
116     private static void writeNumber(@NonNull Writer out, @NonNegative int value) throws IOException {
117         out.write(Integer.toString(value));
118     }
119 }