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