You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2017/10/14 12:24:43 UTC
[3/6] camel git commit: Camel route coverage maven plugin to support
anonymous routes.
Camel route coverage maven plugin to support anonymous routes.
Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/4393783e
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/4393783e
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/4393783e
Branch: refs/heads/master
Commit: 4393783e59a00d3a64b95cd7a42db0487ef6595f
Parents: 9bd1e29
Author: Claus Ibsen <da...@apache.org>
Authored: Sat Oct 14 13:47:20 2017 +0200
Committer: Claus Ibsen <da...@apache.org>
Committed: Sat Oct 14 14:09:42 2017 +0200
----------------------------------------------------------------------
.../velocity/VelocityContentCacheTest.java | 3 +-
.../VelocitySomeValuesNotInExchangeTest.java | 4 +-
.../parser/helper/RouteCoverageHelper.java | 53 +++++++-
.../apache/camel/maven/RouteCoverageMojo.java | 129 +++++++++++++++++--
.../camel/maven/model/RouteCoverageNode.java | 42 ++++++
5 files changed, 219 insertions(+), 12 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/camel/blob/4393783e/components/camel-velocity/src/test/java/org/apache/camel/component/velocity/VelocityContentCacheTest.java
----------------------------------------------------------------------
diff --git a/components/camel-velocity/src/test/java/org/apache/camel/component/velocity/VelocityContentCacheTest.java b/components/camel-velocity/src/test/java/org/apache/camel/component/velocity/VelocityContentCacheTest.java
index 060571a..5fb55ce 100644
--- a/components/camel-velocity/src/test/java/org/apache/camel/component/velocity/VelocityContentCacheTest.java
+++ b/components/camel-velocity/src/test/java/org/apache/camel/component/velocity/VelocityContentCacheTest.java
@@ -59,9 +59,10 @@ public class VelocityContentCacheTest extends CamelTestSupport {
template.sendBodyAndHeader("file://target/test-classes/org/apache/camel/component/velocity?fileExist=Override", "Bye $headers.name", Exchange.FILE_NAME, "hello.vm");
mock.reset();
- mock.expectedBodiesReceived("Bye Paris");
+ mock.expectedBodiesReceived("Bye Paris", "Bye World");
template.sendBodyAndHeader("direct:a", "Body", "name", "Paris");
+ template.sendBodyAndHeader("direct:a", "Body", "name", "World");
mock.assertIsSatisfied();
}
http://git-wip-us.apache.org/repos/asf/camel/blob/4393783e/components/camel-velocity/src/test/java/org/apache/camel/component/velocity/VelocitySomeValuesNotInExchangeTest.java
----------------------------------------------------------------------
diff --git a/components/camel-velocity/src/test/java/org/apache/camel/component/velocity/VelocitySomeValuesNotInExchangeTest.java b/components/camel-velocity/src/test/java/org/apache/camel/component/velocity/VelocitySomeValuesNotInExchangeTest.java
index 7ff09bd..3ba0819 100644
--- a/components/camel-velocity/src/test/java/org/apache/camel/component/velocity/VelocitySomeValuesNotInExchangeTest.java
+++ b/components/camel-velocity/src/test/java/org/apache/camel/component/velocity/VelocitySomeValuesNotInExchangeTest.java
@@ -61,7 +61,9 @@ public class VelocitySomeValuesNotInExchangeTest extends CamelTestSupport {
protected RouteBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
public void configure() throws Exception {
- from("direct:a").to("velocity:org/apache/camel/component/velocity/someValuesNotInExchange.vm").to("mock:result");
+ from("direct:a")
+ .to("velocity:org/apache/camel/component/velocity/someValuesNotInExchange.vm")
+ .to("mock:result");
}
};
}
http://git-wip-us.apache.org/repos/asf/camel/blob/4393783e/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/helper/RouteCoverageHelper.java
----------------------------------------------------------------------
diff --git a/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/helper/RouteCoverageHelper.java b/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/helper/RouteCoverageHelper.java
index 2bc4bf8..45c61ed 100644
--- a/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/helper/RouteCoverageHelper.java
+++ b/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/helper/RouteCoverageHelper.java
@@ -19,7 +19,9 @@ package org.apache.camel.parser.helper;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.w3c.dom.Document;
@@ -39,9 +41,19 @@ public final class RouteCoverageHelper {
private RouteCoverageHelper() {
}
+ /**
+ * Parses the dumped route coverage data and creates a line by line coverage data
+ *
+ * @param directory the directory with the dumped route coverage data
+ * @param routeId the route id to gather, must not be null.
+ * @return line by line coverage data
+ */
public static List<CoverageData> parseDumpRouteCoverageByRouteId(String directory, String routeId) throws Exception {
List<CoverageData> answer = new ArrayList<>();
+ if (routeId == null) {
+ return answer;
+ }
File[] files = new File(directory).listFiles(f -> f.getName().endsWith(".xml"));
if (files == null) {
return answer;
@@ -58,8 +70,7 @@ public final class RouteCoverageHelper {
String id = route.getAttributes().getNamedItem("id").getNodeValue();
// must be the target route
if (routeId.equals(id)) {
- // parse each route and build a Map<String, Integer> with the no of messages processed
- // where String is the EIP name
+ // parse each route and build a List<CoverageData> for line by line coverage data
AtomicInteger counter = new AtomicInteger();
parseRouteData(catalog, route, answer, counter);
}
@@ -70,6 +81,44 @@ public final class RouteCoverageHelper {
return answer;
}
+ public static Map<String, List<CoverageData>> parseDumpRouteCoverageByClassAndTestMethod(String directory) throws Exception {
+ Map<String, List<CoverageData>> answer = new LinkedHashMap();
+
+ File[] files = new File(directory).listFiles(f -> f.getName().endsWith(".xml"));
+ if (files == null) {
+ return answer;
+ }
+
+ CamelCatalog catalog = new DefaultCamelCatalog(true);
+
+ for (File file : files) {
+ try (FileInputStream fis = new FileInputStream(file)) {
+ Document dom = XmlLineNumberParser.parseXml(fis);
+ NodeList routes = dom.getElementsByTagName("route");
+ for (int i = 0; i < routes.getLength(); i++) {
+ Node route = routes.item(i);
+ // parse each route and build a List<CoverageData> for line by line coverage data
+ AtomicInteger counter = new AtomicInteger();
+ List<CoverageData> data = new ArrayList<>();
+ parseRouteData(catalog, route, data, counter);
+ // create a key which is based on the file name without extension
+ String key = file.getName();
+ // strip .xml extension
+ key = key.substring(0, key.length() - 4);
+ // is there existing data
+ List<CoverageData> existing = answer.get(key);
+ if (existing != null) {
+ existing.addAll(data);
+ } else {
+ answer.put(key, data);
+ }
+ }
+ }
+ }
+
+ return answer;
+ }
+
private static void parseRouteData(CamelCatalog catalog, Node node, List<CoverageData> data, AtomicInteger counter) {
// must be a known EIP model
String key = node.getNodeName();
http://git-wip-us.apache.org/repos/asf/camel/blob/4393783e/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/RouteCoverageMojo.java
----------------------------------------------------------------------
diff --git a/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/RouteCoverageMojo.java b/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/RouteCoverageMojo.java
index 6f89638..8168ea7 100644
--- a/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/RouteCoverageMojo.java
+++ b/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/RouteCoverageMojo.java
@@ -23,12 +23,16 @@ import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
+import edu.emory.mathcs.backport.java.util.Collections;
import org.apache.camel.maven.helper.EndpointHelper;
import org.apache.camel.maven.model.RouteCoverageNode;
import org.apache.camel.parser.RouteBuilderParser;
@@ -36,6 +40,7 @@ import org.apache.camel.parser.XmlRouteParser;
import org.apache.camel.parser.helper.RouteCoverageHelper;
import org.apache.camel.parser.model.CamelNodeDetails;
import org.apache.camel.parser.model.CoverageData;
+import org.apache.camel.util.FileUtil;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
@@ -94,6 +99,17 @@ public class RouteCoverageMojo extends AbstractExecMojo {
*/
private String excludes;
+ /**
+ * Whether to allow anonymous routes (routes without any route id assigned).
+ * By using route id's then its safer to match the route cover data with the route source code.
+ * Anonymous routes are less safe to use for route coverage as its harder to know
+ * exactly which route that was tested corresponds to which of the routes from the source code.
+ *
+ * @parameter property="camel.anonymousRoutes"
+ * default-value="false"
+ */
+ private boolean anonymousRoutes = false;
+
// CHECKSTYLE:OFF
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
@@ -168,14 +184,17 @@ public class RouteCoverageMojo extends AbstractExecMojo {
// skip any routes which has no route id assigned
long anonymous = routeTrees.stream().filter(t -> t.getRouteId() == null).count();
- if (anonymous > 0) {
+ if (!anonymousRoutes && anonymous > 0) {
getLog().warn("Discovered " + anonymous + " anonymous routes. Add route ids to these routes for route coverage support");
}
final AtomicInteger notCovered = new AtomicInteger();
- routeTrees = routeTrees.stream().filter(t -> t.getRouteId() != null).collect(Collectors.toList());
- for (CamelNodeDetails t : routeTrees) {
+ List<CamelNodeDetails> routeIdTrees = routeTrees.stream().filter(t -> t.getRouteId() != null).collect(Collectors.toList());
+ List<CamelNodeDetails> anonymousRouteTrees = routeTrees.stream().filter(t -> t.getRouteId() == null).collect(Collectors.toList());
+
+ // favor strict matching on route ids
+ for (CamelNodeDetails t : routeIdTrees) {
String routeId = t.getRouteId();
String fileName = asRelativeFile(t.getFileName());
@@ -186,7 +205,7 @@ public class RouteCoverageMojo extends AbstractExecMojo {
getLog().warn("No route coverage data found for route: " + routeId
+ ". Make sure to enable route coverage in your unit tests and assign unique route ids to your routes. Also remember to run unit tests first.");
} else {
- List<RouteCoverageNode> coverage = gatherRouteCoverageSummary(t, coverageData);
+ List<RouteCoverageNode> coverage = gatherRouteCoverageSummary(Collections.singletonList(t), coverageData);
String out = templateCoverageData(fileName, routeId, coverage, notCovered);
getLog().info("Route coverage summary:\n\n" + out);
getLog().info("");
@@ -197,10 +216,100 @@ public class RouteCoverageMojo extends AbstractExecMojo {
}
}
+ if (anonymousRoutes && !anonymousRouteTrees.isEmpty()) {
+ // grab dump data for the route
+ try {
+ Map<String, List<CoverageData>> datas = RouteCoverageHelper.parseDumpRouteCoverageByClassAndTestMethod("target/camel-route-coverage");
+ if (datas.isEmpty()) {
+ getLog().warn("No route coverage data found"
+ + ". Make sure to enable route coverage in your unit tests. Also remember to run unit tests first.");
+ } else {
+ Map<String, List<CamelNodeDetails>> routes = groupAnonymousRoutesByClassName(anonymousRouteTrees);
+ // attempt to match anonymous routes via the unit test class
+ for (Map.Entry<String, List<CamelNodeDetails>> t : routes.entrySet()) {
+ List<RouteCoverageNode> coverage = new ArrayList<>();
+ String className = t.getKey();
+
+ // we may have multiple tests in the same test class that tests different parts of the same
+ // routes so merge their coverage reports into a single coverage
+ for (Map.Entry<String, List<CoverageData>> entry : datas.entrySet()) {
+ String key = entry.getKey();
+ String dataClassName = key.substring(0, key.indexOf('-'));
+ if (dataClassName.equals(className)) {
+ List<RouteCoverageNode> result = gatherRouteCoverageSummary(t.getValue(), entry.getValue());
+ // merge them together
+ mergeCoverageData(coverage, result);
+ }
+ }
+
+ if (!coverage.isEmpty()) {
+ String out = templateCoverageData(className, null, coverage, notCovered);
+ getLog().info("Route coverage summary:\n\n" + out);
+ getLog().info("");
+ }
+ }
+ }
+ } catch (Exception e) {
+ throw new MojoExecutionException("Error during gathering route coverage data", e);
+ }
+ }
+
if (failOnError && notCovered.get() > 0) {
throw new MojoExecutionException("There are " + notCovered.get() + " route(s) not fully covered!");
}
}
+
+ private Map<String, List<CamelNodeDetails>> groupAnonymousRoutesByClassName(List<CamelNodeDetails> anonymousRouteTrees) {
+ Map<String, List<CamelNodeDetails>> answer = new LinkedHashMap<>();
+
+ for (CamelNodeDetails t : anonymousRouteTrees) {
+ String fileName = asRelativeFile(t.getFileName());
+ String className = FileUtil.stripExt(FileUtil.stripPath(fileName));
+ List<CamelNodeDetails> list = answer.computeIfAbsent(className, k -> new ArrayList<>());
+ list.add(t);
+ }
+
+ return answer;
+ }
+
+ private void mergeCoverageData(List<RouteCoverageNode> coverage, List<RouteCoverageNode> result) {
+ List<RouteCoverageNode> toBeAdded = new ArrayList<>();
+
+ ListIterator<RouteCoverageNode> it = null;
+ for (RouteCoverageNode node : result) {
+ // do we have an existing
+ it = positionToLineNumber(it, coverage, node.getLineNumber());
+ RouteCoverageNode existing = it.hasNext() ? it.next() : null;
+ if (existing != null) {
+ int count = existing.getCount() + node.getCount();
+ existing.setCount(count);
+ } else {
+ // its a new node
+ toBeAdded.add(node);
+ }
+ }
+
+ if (!toBeAdded.isEmpty()) {
+ coverage.addAll(toBeAdded);
+ }
+ }
+
+ private ListIterator<RouteCoverageNode> positionToLineNumber(ListIterator<RouteCoverageNode> it, List<RouteCoverageNode> coverage, int lineNumber) {
+ // restart
+ if (it == null || !it.hasNext()) {
+ it = coverage.listIterator();
+ }
+ while (it.hasNext()) {
+ RouteCoverageNode node = it.next();
+ if (node.getLineNumber() == lineNumber) {
+ // go back
+ it.previous();
+ return it;
+ }
+ }
+ return it;
+ }
+
// CHECKSTYLE:ON
@SuppressWarnings("unchecked")
@@ -213,7 +322,9 @@ public class RouteCoverageMojo extends AbstractExecMojo {
} else {
sw.println("File:\t" + fileName);
}
- sw.println("RouteId:\t" + routeId);
+ if (routeId != null) {
+ sw.println("RouteId:\t" + routeId);
+ }
sw.println();
sw.println(String.format("%8s %8s %s", "Line #", "Count", "Route"));
sw.println(String.format("%8s %8s %s", "------", "-----", "-----"));
@@ -241,12 +352,14 @@ public class RouteCoverageMojo extends AbstractExecMojo {
return bos.toString();
}
- private static List<RouteCoverageNode> gatherRouteCoverageSummary(CamelNodeDetails route, List<CoverageData> coverageData) {
+ private static List<RouteCoverageNode> gatherRouteCoverageSummary(List<CamelNodeDetails> route, List<CoverageData> coverageData) {
List<RouteCoverageNode> answer = new ArrayList<>();
Iterator<CoverageData> it = coverageData.iterator();
- AtomicInteger level = new AtomicInteger();
- gatherRouteCoverageSummary(route, it, level, answer);
+ for (CamelNodeDetails r : route) {
+ AtomicInteger level = new AtomicInteger();
+ gatherRouteCoverageSummary(r, it, level, answer);
+ }
return answer;
}
http://git-wip-us.apache.org/repos/asf/camel/blob/4393783e/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/model/RouteCoverageNode.java
----------------------------------------------------------------------
diff --git a/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/model/RouteCoverageNode.java b/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/model/RouteCoverageNode.java
index cb6aba6..9656c56 100644
--- a/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/model/RouteCoverageNode.java
+++ b/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/model/RouteCoverageNode.java
@@ -74,4 +74,46 @@ public final class RouteCoverageNode {
this.level = level;
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ RouteCoverageNode that = (RouteCoverageNode) o;
+
+ if (lineNumber != that.lineNumber) {
+ return false;
+ }
+ if (level != that.level) {
+ return false;
+ }
+ if (!className.equals(that.className)) {
+ return false;
+ }
+ return name.equals(that.name);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = className.hashCode();
+ result = 31 * result + name.hashCode();
+ result = 31 * result + lineNumber;
+ result = 31 * result + level;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "RouteCoverageNode["
+ + "lineNumber=" + lineNumber
+ + ", count=" + count
+ + ", name='" + name + '\''
+ + ", level=" + level
+ + ", className='" + className + '\''
+ + ']';
+ }
}