ICU-12445 Updated ant target coverageJaCoCo to check method coverage. When a new method is added with no test coverage, the check will fail. All existing methods with no test coverage are captured in coverage-exclusion.txt.

X-SVN-Rev: 38667
This commit is contained in:
Yoshito Umaoka 2016-04-29 19:33:24 +00:00
parent 0cbac47c4e
commit 1f2813e7fa
5 changed files with 1452 additions and 6 deletions

View File

@ -873,12 +873,13 @@
<classpath path="${env.JACOCO_DIR}/lib/jacocoant.jar"/>
</taskdef>
<target name="coverageJaCoCo" depends="jar, tests" description="Run the ICU4J unit tests and generate code coverage report">
<target name="coverageJaCoCo" depends="build-tools, jar, tests" description="Run the ICU4J unit tests and generate code coverage report">
<property name="jacoco.out.dir" value="${out.dir}/jacoco"/>
<property name="jacoco.exec.data.file" value="${jacoco.out.dir}/jacoco.exec"/>
<property name="jacoco.report.html.zip" value="${jacoco.out.dir}/report_html.zip"/>
<property name="jacoco.report.xml" value="${jacoco.out.dir}/report.xml"/>
<property name="jacoco.report.csv" value="${jacoco.out.dir}/report.csv"/>
<property name="jacoco.exclusion.txt" value="coverage-exclusion.txt"/>
<delete dir="${jacoco.out.dir}"/>
<mkdir dir="${jacoco.out.dir}"/>
@ -927,6 +928,14 @@
<xml destfile="${jacoco.report.xml}"/>
<csv destfile="${jacoco.report.csv}"/>
</jacoco:report>
<java classname="com.ibm.icu.dev.tool.coverage.JacocoReportCheck" failonerror="true">
<arg line="${jacoco.report.xml} ${jacoco.exclusion.txt}"/>
<classpath>
<pathelement location="${icu4j.build-tools.jar}"/>
</classpath>
</java>
</target>
<!-- Clover code coverage target -->

1047
icu4j/coverage-exclusion.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/JDK7_TOOLS"/>
<classpathentry kind="output" path="out/bin"/>
</classpath>

View File

@ -6,9 +6,10 @@ org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annota
org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.5
org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
@ -97,7 +98,7 @@ org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=ignore
org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.source=1.5
org.eclipse.jdt.core.compiler.source=1.7
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
@ -169,9 +170,12 @@ org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
org.eclipse.jdt.core.formatter.indentation.size=4
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert

View File

@ -0,0 +1,386 @@
/*
*******************************************************************************
* Copyright (C) 2016, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.dev.tool.coverage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* A tool used for scanning JaCoCo report.xml and detect methods not covered by the
* ICU4J unit tests. This tool is called from ICU4J ant target: coverageJaCoCo, and
* signals failure if there are any methods with no test coverage (and not included
* in 'coverage-exclusion.txt').
*/
public class JacocoReportCheck {
public static void main(String... args) {
if (args.length < 1) {
System.err.println("Missing jacoco report.xml");
System.exit(1);
}
System.out.println("Checking method coverage in " + args[0]);
if (args.length > 1) {
System.out.println("Coverage check exclusion file: " + args[1]);
}
File reportXml = new File(args[0]);
Map<String, ReportEntry> entries = parseReport(reportXml);
if (entries == null) {
System.err.println("Failed to parse jacoco report.xml");
System.exit(2);
}
Set<String> excludedSet = new HashSet<String>();
if (args.length > 1) {
File exclusionTxt = new File(args[1]);
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(new FileInputStream(exclusionTxt)));
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
line = line.trim();
if (line.startsWith("//") || line.length() == 0) {
// comment or blank line
continue;
}
boolean added = excludedSet.add(line);
if (!added) {
System.err.println("Warning: Duplicated exclusion entry - " + line);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
// ignore
}
}
}
}
Set<String> noCoverageSet = new TreeSet<String>();
Set<String> coveredButExcludedSet = new TreeSet<String>();
for (ReportEntry reportEntry : entries.values()) {
String key = reportEntry.key();
Counter methodCnt = reportEntry.method().methodCounter();
int methodMissed = methodCnt == null ? 1 : methodCnt.missed();
if (methodMissed > 0) {
// no test coverage
if (!excludedSet.contains(key)) {
noCoverageSet.add(key);
}
} else {
// covered
if (excludedSet.contains(key)) {
coveredButExcludedSet.add(key);
}
}
}
if (noCoverageSet.size() > 0) {
System.out.println("//");
System.out.println("// Methods with no test coverage, not included in the exclusion set");
System.out.println("//");
for (String key : noCoverageSet) {
System.out.println(key);
}
}
if (coveredButExcludedSet.size() > 0) {
System.out.println("//");
System.out.println("// Methods coverved by tests, but included in the exclusion set");
System.out.println("//");
for (String key : coveredButExcludedSet) {
System.out.println(key);
}
}
System.out.println("Method coverage check finished");
if (noCoverageSet.size() > 0) {
System.err.println("Error: Found method(s) with no test coverage");
System.exit(-1);
}
}
private static Map<String, ReportEntry> parseReport(File reportXmlFile) {
try {
Map<String, ReportEntry> entries = new TreeMap<String, ReportEntry>();
DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
docBuilder.setEntityResolver(new EntityResolver() {
// Ignores JaCoCo report DTD
public InputSource resolveEntity(String publicId, String systemId) {
return new InputSource(new StringReader(""));
}
});
Document doc = docBuilder.parse(reportXmlFile);
NodeList nodes = doc.getElementsByTagName("report");
for (int idx = 0; idx < nodes.getLength(); idx++) {
Node node = nodes.item(idx);
if (node.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
Element reportElement = (Element)node;
NodeList packages = reportElement.getElementsByTagName("package");
for (int pidx = 0 ; pidx < packages.getLength(); pidx++) {
Node pkgNode = packages.item(pidx);
if (pkgNode.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
Element pkgElement = (Element)pkgNode;
NodeList classes = pkgElement.getChildNodes();
if (classes == null) {
continue;
}
// Iterate through classes
for (int cidx = 0; cidx < classes.getLength(); cidx++) {
Node clsNode = classes.item(cidx);
if (clsNode.getNodeType() != Node.ELEMENT_NODE || !"class".equals(clsNode.getNodeName())) {
continue;
}
Element clsElement = (Element)clsNode;
String cls = clsElement.getAttribute("name");
NodeList methods = clsNode.getChildNodes();
if (methods == null) {
continue;
}
// Iterate through method elements
for (int midx = 0; midx < methods.getLength(); midx++) {
Node mtdNode = methods.item(midx);
if (mtdNode.getNodeType() != Node.ELEMENT_NODE || !"method".equals(mtdNode.getNodeName())) {
continue;
}
Element mtdElement = (Element)mtdNode;
String mtdName = mtdElement.getAttribute("name");
String mtdDesc = mtdElement.getAttribute("desc");
String mtdLineStr = mtdElement.getAttribute("line");
assert mtdName != null;
assert mtdDesc != null;
assert mtdLineStr != null;
int mtdLine = -1;
try {
mtdLine = Integer.parseInt(mtdLineStr);
} catch (NumberFormatException e) {
// Ignore line # parse failure
e.printStackTrace();
}
// Iterate through counter elements and add report entries
Counter instructionCnt = null;
Counter branchCnt = null;
Counter lineCnt = null;
Counter complexityCnt = null;
Counter methodCnt = null;
NodeList counters = mtdNode.getChildNodes();
if (counters == null) {
continue;
}
for (int i = 0; i < counters.getLength(); i++) {
Node cntNode = counters.item(i);
if (cntNode.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
Element cntElement = (Element)cntNode;
String type = cntElement.getAttribute("type");
String missedStr = cntElement.getAttribute("missed");
String coveredStr = cntElement.getAttribute("covered");
assert type != null;
assert missedStr != null;
assert coveredStr != null;
int missed = -1;
int covered = -1;
try {
missed = Integer.parseInt(missedStr);
} catch (NumberFormatException e) {
// Ignore missed # parse failure
e.printStackTrace();
}
try {
covered = Integer.parseInt(coveredStr);
} catch (NumberFormatException e) {
// Ignore covered # parse failure
e.printStackTrace();
}
if (type.equals("INSTRUCTION")) {
instructionCnt = new Counter(missed, covered);
} else if (type.equals("BRANCH")) {
branchCnt = new Counter(missed, covered);
} else if (type.equals("LINE")) {
lineCnt = new Counter(missed, covered);
} else if (type.equals("COMPLEXITY")) {
complexityCnt = new Counter(missed, covered);
} else if (type.equals("METHOD")) {
methodCnt = new Counter(missed, covered);
} else {
System.err.println("Unknown counter type: " + type);
// Ignore
}
}
// Add the entry
Method method = new Method(mtdName, mtdDesc, mtdLine,
instructionCnt, branchCnt, lineCnt, complexityCnt, methodCnt);
ReportEntry entry = new ReportEntry(cls, method);
ReportEntry prev = entries.put(entry.key(), entry);
if (prev != null) {
System.out.println("oh");
}
}
}
}
}
return entries;
} catch (IOException e) {
e.printStackTrace();
return null;
} catch (ParserConfigurationException e) {
e.printStackTrace();
return null;
} catch (SAXException e) {
e.printStackTrace();
return null;
}
}
private static class Counter {
final int missed;
final int covered;
Counter(int missed, int covered) {
this.missed = missed;
this.covered = covered;
}
int missed() {
return missed;
}
int covered() {
return covered;
}
}
private static class Method {
final String name;
final String desc;
final int line;
final Counter instructionCnt;
final Counter branchCnt;
final Counter lineCnt;
final Counter complexityCnt;
final Counter methodCnt;
Method(String name, String desc, int line,
Counter instructionCnt, Counter branchCnt, Counter lineCnt,
Counter complexityCnt, Counter methodCnt) {
this.name = name;
this.desc = desc;
this.line = line;
this.instructionCnt = instructionCnt;
this.branchCnt = branchCnt;
this.lineCnt = lineCnt;
this.complexityCnt = complexityCnt;
this.methodCnt = methodCnt;
}
String name() {
return name;
}
String desc() {
return desc;
}
int line() {
return line;
}
Counter instructionCounter() {
return instructionCnt;
}
Counter branchCounter() {
return branchCnt;
}
Counter lineCounter() {
return lineCnt;
}
Counter complexityCounter() {
return complexityCnt;
}
Counter methodCounter() {
return methodCnt;
}
}
private static class ReportEntry {
final String cls;
final Method method;
final String key;
ReportEntry(String cls, Method method) {
this.cls = cls;
this.method = method;
this.key = cls + "#" + method.name() + ":" + method.desc();
}
String key() {
return key;
}
String cls() {
return cls;
}
Method method() {
return method;
}
}
}