• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /*
4  *******************************************************************************
5  * Copyright (C) 2016, International Business Machines Corporation and         *
6  * others. All Rights Reserved.                                                *
7  *******************************************************************************
8  */
9 package com.ibm.icu.dev.tool.coverage;
10 
11 import java.io.BufferedReader;
12 import java.io.File;
13 import java.io.FileInputStream;
14 import java.io.IOException;
15 import java.io.InputStreamReader;
16 import java.io.StringReader;
17 import java.util.HashSet;
18 import java.util.Map;
19 import java.util.Set;
20 import java.util.TreeMap;
21 import java.util.TreeSet;
22 
23 import javax.xml.parsers.DocumentBuilder;
24 import javax.xml.parsers.DocumentBuilderFactory;
25 import javax.xml.parsers.ParserConfigurationException;
26 
27 import org.w3c.dom.Document;
28 import org.w3c.dom.Element;
29 import org.w3c.dom.Node;
30 import org.w3c.dom.NodeList;
31 import org.xml.sax.EntityResolver;
32 import org.xml.sax.InputSource;
33 import org.xml.sax.SAXException;
34 
35 /**
36  * A tool used for scanning JaCoCo report.xml and detect methods not covered by the
37  * ICU4J unit tests. This tool is called from ICU4J ant target: coverageJaCoCo, and
38  * signals failure if there are any methods with no test coverage (and not included
39  * in 'coverage-exclusion.txt').
40  */
41 public class JacocoReportCheck {
main(String... args)42     public static void main(String... args) {
43         if (args.length < 1) {
44             System.err.println("Missing jacoco report.xml");
45             System.exit(1);
46         }
47 
48         System.out.println("Checking method coverage in " + args[0]);
49         if (args.length > 1) {
50             System.out.println("Coverage check exclusion file: " + args[1]);
51         }
52 
53         File reportXml = new File(args[0]);
54         Map<String, ReportEntry> entries = parseReport(reportXml);
55         if (entries == null) {
56             System.err.println("Failed to parse jacoco report.xml");
57             System.exit(2);
58         }
59 
60         Set<String> excludedSet = new HashSet<String>();
61         if (args.length > 1) {
62             File exclusionTxt = new File(args[1]);
63             BufferedReader reader = null;
64             try {
65                 reader = new BufferedReader(new InputStreamReader(new FileInputStream(exclusionTxt)));
66                 while (true) {
67                     String line = reader.readLine();
68                     if (line == null) {
69                         break;
70                     }
71                     line = line.trim();
72                     if (line.startsWith("//") || line.length() == 0) {
73                         // comment or blank line
74                         continue;
75                     }
76                     boolean added = excludedSet.add(line);
77                     if (!added) {
78                         System.err.println("Warning: Duplicated exclusion entry - " + line);
79                     }
80                 }
81             } catch (IOException e) {
82                 e.printStackTrace();
83             } finally {
84                 if (reader != null) {
85                     try {
86                         reader.close();
87                     } catch (IOException e) {
88                         e.printStackTrace();
89                         // ignore
90                     }
91                 }
92             }
93         }
94 
95 
96         Set<String> noCoverageSet = new TreeSet<String>();
97         Set<String> coveredButExcludedSet = new TreeSet<String>();
98 
99         for (ReportEntry reportEntry : entries.values()) {
100             String key = reportEntry.key();
101             Counter methodCnt = reportEntry.method().methodCounter();
102             int methodMissed = methodCnt == null ? 1 : methodCnt.missed();
103             if (methodMissed > 0) {
104                 // no test coverage
105                 if (!excludedSet.contains(key)) {
106                     noCoverageSet.add(key);
107                 }
108             } else {
109                 // covered
110                 if (excludedSet.contains(key)) {
111                     coveredButExcludedSet.add(key);
112                 }
113             }
114         }
115 
116         if (noCoverageSet.size() > 0) {
117             System.out.println("//");
118             System.out.println("// Methods with no test coverage, not included in the exclusion set");
119             System.out.println("//");
120             for (String key : noCoverageSet) {
121                 System.out.println(key);
122             }
123         }
124 
125         if (coveredButExcludedSet.size() > 0) {
126             System.out.println("//");
127             System.out.println("// Methods covered by tests, but included in the exclusion set");
128             System.out.println("//");
129             for (String key : coveredButExcludedSet) {
130                 System.out.println(key);
131             }
132         }
133 
134         System.out.println("Method coverage check finished");
135 
136         if (noCoverageSet.size() > 0) {
137             System.err.println("Error: Found method(s) with no test coverage");
138             System.exit(-1);
139         }
140     }
141 
parseReport(File reportXmlFile)142     private static Map<String, ReportEntry> parseReport(File reportXmlFile) {
143         try {
144             Map<String, ReportEntry> entries = new TreeMap<String, ReportEntry>();
145             DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
146             docBuilder.setEntityResolver(new EntityResolver() {
147                 // Ignores JaCoCo report DTD
148                 public InputSource resolveEntity(String publicId, String systemId) {
149                     return new InputSource(new StringReader(""));
150                 }
151             });
152             Document doc = docBuilder.parse(reportXmlFile);
153             NodeList nodes = doc.getElementsByTagName("report");
154             for (int idx = 0; idx < nodes.getLength(); idx++) {
155                 Node node = nodes.item(idx);
156                 if (node.getNodeType() != Node.ELEMENT_NODE) {
157                     continue;
158                 }
159                 Element reportElement = (Element)node;
160                 NodeList packages = reportElement.getElementsByTagName("package");
161                 for (int pidx = 0 ; pidx < packages.getLength(); pidx++) {
162                     Node pkgNode = packages.item(pidx);
163                     if (pkgNode.getNodeType() != Node.ELEMENT_NODE) {
164                         continue;
165                     }
166                     Element pkgElement = (Element)pkgNode;
167                     NodeList classes = pkgElement.getChildNodes();
168                     if (classes == null) {
169                         continue;
170                     }
171 
172                     // Iterate through classes
173                     for (int cidx = 0; cidx < classes.getLength(); cidx++) {
174                         Node clsNode = classes.item(cidx);
175                         if (clsNode.getNodeType() != Node.ELEMENT_NODE || !"class".equals(clsNode.getNodeName())) {
176                             continue;
177                         }
178                         Element clsElement = (Element)clsNode;
179                         String cls = clsElement.getAttribute("name");
180 
181                         NodeList methods = clsNode.getChildNodes();
182                         if (methods == null) {
183                             continue;
184                         }
185 
186                         // Iterate through method elements
187                         for (int midx = 0; midx < methods.getLength(); midx++) {
188                             Node mtdNode = methods.item(midx);
189                             if (mtdNode.getNodeType() != Node.ELEMENT_NODE || !"method".equals(mtdNode.getNodeName())) {
190                                 continue;
191                             }
192                             Element mtdElement = (Element)mtdNode;
193                             String mtdName = mtdElement.getAttribute("name");
194                             String mtdDesc = mtdElement.getAttribute("desc");
195                             String mtdLineStr = mtdElement.getAttribute("line");
196                             assert mtdName != null;
197                             assert mtdDesc != null;
198                             assert mtdLineStr != null;
199 
200                             int mtdLine = -1;
201                             try {
202                                  mtdLine = Integer.parseInt(mtdLineStr);
203                             } catch (NumberFormatException e) {
204                                 // Ignore line # parse failure
205                                 e.printStackTrace();
206                             }
207 
208                             // Iterate through counter elements and add report entries
209 
210                             Counter instructionCnt = null;
211                             Counter branchCnt = null;
212                             Counter lineCnt = null;
213                             Counter complexityCnt = null;
214                             Counter methodCnt = null;
215 
216                             NodeList counters = mtdNode.getChildNodes();
217                             if (counters == null) {
218                                 continue;
219                             }
220                             for (int i = 0; i < counters.getLength(); i++) {
221                                 Node cntNode = counters.item(i);
222                                 if (cntNode.getNodeType() != Node.ELEMENT_NODE) {
223                                     continue;
224                                 }
225                                 Element cntElement = (Element)cntNode;
226                                 String type = cntElement.getAttribute("type");
227                                 String missedStr = cntElement.getAttribute("missed");
228                                 String coveredStr = cntElement.getAttribute("covered");
229                                 assert type != null;
230                                 assert missedStr != null;
231                                 assert coveredStr != null;
232 
233                                 int missed = -1;
234                                 int covered = -1;
235                                 try {
236                                     missed = Integer.parseInt(missedStr);
237                                 } catch (NumberFormatException e) {
238                                     // Ignore missed # parse failure
239                                     e.printStackTrace();
240                                 }
241                                 try {
242                                     covered = Integer.parseInt(coveredStr);
243                                 } catch (NumberFormatException e) {
244                                     // Ignore covered # parse failure
245                                     e.printStackTrace();
246                                 }
247 
248                                 if (type.equals("INSTRUCTION")) {
249                                     instructionCnt = new Counter(missed, covered);
250                                 } else if (type.equals("BRANCH")) {
251                                     branchCnt = new Counter(missed, covered);
252                                 } else if (type.equals("LINE")) {
253                                     lineCnt = new Counter(missed, covered);
254                                 } else if (type.equals("COMPLEXITY")) {
255                                     complexityCnt = new Counter(missed, covered);
256                                 } else if (type.equals("METHOD")) {
257                                     methodCnt = new Counter(missed, covered);
258                                 } else {
259                                     System.err.println("Unknown counter type: " + type);
260                                     // Ignore
261                                 }
262                             }
263                             // Add the entry
264                             Method method = new Method(mtdName, mtdDesc, mtdLine,
265                                     instructionCnt, branchCnt, lineCnt, complexityCnt, methodCnt);
266 
267                             ReportEntry entry = new ReportEntry(cls, method);
268                             ReportEntry prev = entries.put(entry.key(), entry);
269                             if (prev != null) {
270                                 System.out.println("oh");
271                             }
272                         }
273                     }
274                 }
275             }
276             return entries;
277         } catch (IOException e) {
278             e.printStackTrace();
279             return null;
280         } catch (ParserConfigurationException e) {
281             e.printStackTrace();
282             return null;
283         } catch (SAXException e) {
284             e.printStackTrace();
285             return null;
286         }
287     }
288 
289     private static class Counter {
290         final int missed;
291         final int covered;
292 
Counter(int missed, int covered)293         Counter(int missed, int covered) {
294             this.missed = missed;
295             this.covered = covered;
296         }
297 
missed()298         int missed() {
299             return missed;
300         }
301 
covered()302         int covered() {
303             return covered;
304         }
305     }
306 
307     private static class Method {
308         final String name;
309         final String desc;
310         final int line;
311 
312         final Counter instructionCnt;
313         final Counter branchCnt;
314         final Counter lineCnt;
315         final Counter complexityCnt;
316         final Counter methodCnt;
317 
Method(String name, String desc, int line, Counter instructionCnt, Counter branchCnt, Counter lineCnt, Counter complexityCnt, Counter methodCnt)318         Method(String name, String desc, int line,
319                 Counter instructionCnt, Counter branchCnt, Counter lineCnt,
320                 Counter complexityCnt, Counter methodCnt) {
321             this.name = name;
322             this.desc = desc;
323             this.line = line;
324             this.instructionCnt = instructionCnt;
325             this.branchCnt = branchCnt;
326             this.lineCnt = lineCnt;
327             this.complexityCnt = complexityCnt;
328             this.methodCnt = methodCnt;
329         }
330 
name()331         String name() {
332             return name;
333         }
334 
desc()335         String desc() {
336             return desc;
337         }
338 
line()339         int line() {
340             return line;
341         }
342 
instructionCounter()343         Counter instructionCounter() {
344             return instructionCnt;
345         }
346 
branchCounter()347         Counter branchCounter() {
348             return branchCnt;
349         }
350 
lineCounter()351         Counter lineCounter() {
352             return lineCnt;
353         }
354 
complexityCounter()355         Counter complexityCounter() {
356             return complexityCnt;
357         }
358 
methodCounter()359         Counter methodCounter() {
360             return methodCnt;
361         }
362     }
363 
364     private static class ReportEntry {
365         final String cls;
366         final Method method;
367         final String key;
368 
ReportEntry(String cls, Method method)369         ReportEntry(String cls, Method method) {
370             this.cls = cls;
371             this.method = method;
372             this.key = cls + "#" + method.name() + ":" + method.desc();
373         }
374 
key()375         String key() {
376             return key;
377         }
378 
cls()379         String cls() {
380             return cls;
381         }
382 
method()383         Method method() {
384             return method;
385         }
386     }
387 
388 }
389