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/08 12:02:51 UTC

[08/13] camel git commit: Add RouteCoverage annotation to camel-test-spring to make it easy to turn on

Add RouteCoverage annotation to camel-test-spring to make it easy to turn on


Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/702f4a98
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/702f4a98
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/702f4a98

Branch: refs/heads/parser2
Commit: 702f4a98a608f63ac15f4d49c0542a7daa190556
Parents: b05a46d
Author: Claus Ibsen <da...@apache.org>
Authored: Sat Oct 7 20:48:24 2017 +0200
Committer: Claus Ibsen <da...@apache.org>
Committed: Sat Oct 7 20:48:24 2017 +0200

----------------------------------------------------------------------
 .../test/spring/CamelAnnotationsHandler.java    |  15 +
 .../CamelSpringBootExecutionListener.java       |   1 +
 .../CamelSpringDelegatingTestContextLoader.java |   1 +
 .../spring/CamelSpringTestContextLoader.java    |  42 ++-
 ...gTestContextLoaderTestExecutionListener.java |   3 +-
 .../test/spring/CamelSpringTestHelper.java      |  14 +-
 .../apache/camel/test/spring/RouteCoverage.java |  39 +++
 .../test/spring/RouteCoverageEventNotifier.java |  96 ++++++
 ...ringRouteProcessorDumpRouteCoverageTest.java |  55 ++++
 .../test/spring/CamelSpringRunnerPlainTest.java |   5 +
 .../camel/test/junit4/CamelTestSupport.java     |   4 +-
 .../java/sample/camel/SampleCamelRouter.java    |   2 +-
 .../apache/camel/parser/graph/RenderRoute.java  |   4 +
 .../java/RoasterJavaDslTwoRoutesTest.java       |   2 +-
 .../org/apache/camel/maven/CoverageMojo.java    | 323 +++++++++++++++++++
 15 files changed, 594 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/702f4a98/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelAnnotationsHandler.java
----------------------------------------------------------------------
diff --git a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelAnnotationsHandler.java b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelAnnotationsHandler.java
index 8902f4f..c27f7783 100644
--- a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelAnnotationsHandler.java
+++ b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelAnnotationsHandler.java
@@ -69,6 +69,21 @@ public final class CamelAnnotationsHandler {
         }
     }
 
+    /**
+     * Handles disabling of JMX on Camel contexts based on {@link DisableJmx}.
+     *
+     * @param context the initialized Spring context
+     * @param testClass the test class being executed
+     */
+    public static void handleRouteCoverage(ConfigurableApplicationContext context, Class<?> testClass) {
+        if (testClass.isAnnotationPresent(RouteCoverage.class)) {
+            LOGGER.info("Enabling RouteCoverage");
+            // JMX must be enabled to turn on route coverage
+            System.clearProperty(JmxSystemPropertyKeys.DISABLED);
+            System.setProperty("CamelTestRouteCoverage", "true");
+        }
+    }
+
     public static void handleProvidesBreakpoint(ConfigurableApplicationContext context, Class<?> testClass) throws Exception {
         Collection<Method> methods = getAllMethods(testClass);
         final List<Breakpoint> breakpoints = new LinkedList<Breakpoint>();

http://git-wip-us.apache.org/repos/asf/camel/blob/702f4a98/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringBootExecutionListener.java
----------------------------------------------------------------------
diff --git a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringBootExecutionListener.java b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringBootExecutionListener.java
index 546462d..f693011 100644
--- a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringBootExecutionListener.java
+++ b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringBootExecutionListener.java
@@ -42,6 +42,7 @@ public class CamelSpringBootExecutionListener extends AbstractTestExecutionListe
         ConfigurableApplicationContext context = (ConfigurableApplicationContext) testContext.getApplicationContext();
 
         // Post CamelContext(s) instantiation but pre CamelContext(s) start setup
+        CamelAnnotationsHandler.handleRouteCoverage(context, testClass);
         CamelAnnotationsHandler.handleProvidesBreakpoint(context, testClass);
         CamelAnnotationsHandler.handleShutdownTimeout(context, testClass);
         CamelAnnotationsHandler.handleMockEndpoints(context, testClass);

http://git-wip-us.apache.org/repos/asf/camel/blob/702f4a98/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringDelegatingTestContextLoader.java
----------------------------------------------------------------------
diff --git a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringDelegatingTestContextLoader.java b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringDelegatingTestContextLoader.java
index 380fac0..9595d8a 100644
--- a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringDelegatingTestContextLoader.java
+++ b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringDelegatingTestContextLoader.java
@@ -77,6 +77,7 @@ public class CamelSpringDelegatingTestContextLoader extends DelegatingSmartConte
         AnnotationConfigUtils.registerAnnotationConfigProcessors((BeanDefinitionRegistry) context);
 
         // Post CamelContext(s) instantiation but pre CamelContext(s) start setup
+        CamelAnnotationsHandler.handleRouteCoverage(context, testClass);
         CamelAnnotationsHandler.handleProvidesBreakpoint(context, testClass);
         CamelAnnotationsHandler.handleShutdownTimeout(context, testClass);
         CamelAnnotationsHandler.handleMockEndpoints(context, testClass);

http://git-wip-us.apache.org/repos/asf/camel/blob/702f4a98/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestContextLoader.java
----------------------------------------------------------------------
diff --git a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestContextLoader.java b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestContextLoader.java
index b595ce3..939a57c 100644
--- a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestContextLoader.java
+++ b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestContextLoader.java
@@ -107,7 +107,7 @@ public class CamelSpringTestContextLoader extends AbstractContextLoader {
     public ApplicationContext loadContext(String... locations) throws Exception {
         
         Class<?> testClass = getTestClass();
-        
+
         if (LOG.isDebugEnabled()) {
             LOG.debug("Loading ApplicationContext for locations [" + StringUtils.arrayToCommaDelimitedString(locations) + "].");
         }
@@ -153,6 +153,7 @@ public class CamelSpringTestContextLoader extends AbstractContextLoader {
         SpringCamelContext.setNoStart(false);
         
         // Post CamelContext(s) instantiation but pre CamelContext(s) start setup
+        handleRouteCoverage(context, testClass);
         handleProvidesBreakpoint(context, testClass);
         handleShutdownTimeout(context, testClass);
         handleMockEndpoints(context, testClass);
@@ -204,7 +205,6 @@ public class CamelSpringTestContextLoader extends AbstractContextLoader {
         
         if (mergedConfig != null) {
             parentContext = mergedConfig.getParentApplicationContext();
-
         }
         
         if (testClass.isAnnotationPresent(ExcludeRoutes.class)) {
@@ -258,7 +258,7 @@ public class CamelSpringTestContextLoader extends AbstractContextLoader {
      */
     protected void handleDisableJmx(GenericApplicationContext context, Class<?> testClass) {
         CamelSpringTestHelper.setOriginalJmxDisabledValue(System.getProperty(JmxSystemPropertyKeys.DISABLED));
-        
+
         if (testClass.isAnnotationPresent(DisableJmx.class)) {
             if (testClass.getAnnotation(DisableJmx.class).value()) {
                 LOG.info("Disabling Camel JMX globally as DisableJmx annotation was found and disableJmx is set to true.");
@@ -267,12 +267,34 @@ public class CamelSpringTestContextLoader extends AbstractContextLoader {
                 LOG.info("Enabling Camel JMX as DisableJmx annotation was found and disableJmx is set to false.");
                 System.clearProperty(JmxSystemPropertyKeys.DISABLED);
             }
-        } else {
+        } else if (!testClass.isAnnotationPresent(RouteCoverage.class)) {
+            // route coverage need JMX so do not disable it by default
             LOG.info("Disabling Camel JMX globally for tests by default.  Use the DisableJMX annotation to override the default setting.");
             System.setProperty(JmxSystemPropertyKeys.DISABLED, "true");
         }
     }
-    
+
+    /**
+     * Handles disabling of JMX on Camel contexts based on {@link DisableJmx}.
+     *
+     * @param context the initialized Spring context
+     * @param testClass the test class being executed
+     */
+    private void handleRouteCoverage(GenericApplicationContext context, Class<?> testClass) throws Exception {
+        if (testClass.isAnnotationPresent(RouteCoverage.class)) {
+            System.setProperty("CamelTestRouteCoverage", "true");
+
+            CamelSpringTestHelper.doToSpringCamelContexts(context, new DoToSpringCamelContextsStrategy() {
+
+                @Override
+                public void execute(String contextName, SpringCamelContext camelContext) throws Exception {
+                    LOG.info("Enabling RouteCoverage");
+                    camelContext.getManagementStrategy().addEventNotifier(new RouteCoverageEventNotifier(testClass.getName(), (String) -> getTestMethod().getName()));
+                }
+            });
+        }
+    }
+
     /**
      * Handles the processing of the {@link ProvidesBreakpoint} annotation on a test class.  Exists here
      * as it is needed in 
@@ -504,4 +526,14 @@ public class CamelSpringTestContextLoader extends AbstractContextLoader {
     protected Class<?> getTestClass() {
         return CamelSpringTestHelper.getTestClass();
     }
+
+    /**
+     * Returns the test method under test.
+     *
+     * @return the method that is being executed
+     * @see CamelSpringTestHelper
+     */
+    protected Method getTestMethod() {
+        return CamelSpringTestHelper.getTestMethod();
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/702f4a98/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestContextLoaderTestExecutionListener.java
----------------------------------------------------------------------
diff --git a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestContextLoaderTestExecutionListener.java b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestContextLoaderTestExecutionListener.java
index d8533d1..100b998 100644
--- a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestContextLoaderTestExecutionListener.java
+++ b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestContextLoaderTestExecutionListener.java
@@ -45,5 +45,6 @@ public class CamelSpringTestContextLoaderTestExecutionListener extends AbstractT
     @Override
     public void prepareTestInstance(TestContext testContext) throws Exception {
         CamelSpringTestHelper.setTestClass(testContext.getTestClass());
-    }    
+        CamelSpringTestHelper.setTestContext(testContext);
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/702f4a98/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestHelper.java
----------------------------------------------------------------------
diff --git a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestHelper.java b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestHelper.java
index aa75c0e..1c426b4 100644
--- a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestHelper.java
+++ b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/CamelSpringTestHelper.java
@@ -27,6 +27,7 @@ import java.util.Set;
 import org.apache.camel.spring.SpringCamelContext;
 
 import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.TestContext;
 
 /**
  * Helper that provides state information across the levels of Spring Test that do not expose the
@@ -41,7 +42,8 @@ public final class CamelSpringTestHelper {
     
     private static ThreadLocal<String> originalJmxDisabledValue = new ThreadLocal<String>();
     private static ThreadLocal<Class<?>> testClazz = new ThreadLocal<Class<?>>();
-    
+    private static ThreadLocal<TestContext> testContext = new ThreadLocal<TestContext>();
+
     private CamelSpringTestHelper() {
     }
     
@@ -60,7 +62,15 @@ public final class CamelSpringTestHelper {
     public static void setTestClass(Class<?> testClass) {
         testClazz.set(testClass);
     }
-    
+
+    public static Method getTestMethod() {
+        return testContext.get().getTestMethod();
+    }
+
+    public static void setTestContext(TestContext context) {
+        testContext.set(context);
+    }
+
     /**
      * Returns all methods defined in {@code clazz} and its superclasses/interfaces.
      */

http://git-wip-us.apache.org/repos/asf/camel/blob/702f4a98/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/RouteCoverage.java
----------------------------------------------------------------------
diff --git a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/RouteCoverage.java b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/RouteCoverage.java
new file mode 100644
index 0000000..df92663
--- /dev/null
+++ b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/RouteCoverage.java
@@ -0,0 +1,39 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.spring;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Enables dumping route coverage statistic.
+ * The route coverage status is written as xml files in the <tt>target/camel-route-coverage</tt> directory after the test has finished.
+ * <p/>
+ * This allows tooling or manual inspection of the stats, so you can generate a route trace diagram of which EIPs
+ * have been in use and which have not. Similar concepts as a code coverage report.
+ */
+@Documented
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface RouteCoverage {
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/702f4a98/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/RouteCoverageEventNotifier.java
----------------------------------------------------------------------
diff --git a/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/RouteCoverageEventNotifier.java b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/RouteCoverageEventNotifier.java
new file mode 100644
index 0000000..7be9370
--- /dev/null
+++ b/components/camel-test-spring/src/main/java/org/apache/camel/test/spring/RouteCoverageEventNotifier.java
@@ -0,0 +1,96 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.spring;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.EventObject;
+import java.util.function.Function;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.api.management.mbean.ManagedCamelContextMBean;
+import org.apache.camel.management.event.CamelContextStoppingEvent;
+import org.apache.camel.support.EventNotifierSupport;
+import org.apache.camel.util.IOHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RouteCoverageEventNotifier extends EventNotifierSupport {
+
+    private static final Logger LOG = LoggerFactory.getLogger(RouteCoverageEventNotifier.class);
+
+    private final String testClassName;
+    private final Function testMethodName;
+
+    public RouteCoverageEventNotifier(String testClassName, Function testMethodName) {
+        this.testClassName = testClassName;
+        this.testMethodName = testMethodName;
+    }
+
+    @Override
+    public boolean isEnabled(EventObject event) {
+        return event instanceof CamelContextStoppingEvent;
+    }
+
+    @Override
+    public void notify(EventObject event) throws Exception {
+        CamelContext context = ((CamelContextStoppingEvent) event).getContext();
+        try {
+            String className = this.getClass().getSimpleName();
+            String dir = "target/camel-route-coverage";
+            String testName = (String) testMethodName.apply(this);
+            String name = className + "-" + testName + ".xml";
+
+            ManagedCamelContextMBean managedCamelContext = context.getManagedCamelContext();
+            if (managedCamelContext == null) {
+                LOG.warn("Cannot dump route coverage to file as JMX is not enabled. Override useJmx() method to enable JMX in the unit test classes.");
+            } else {
+                String xml = managedCamelContext.dumpRoutesCoverageAsXml();
+                String combined = "<camelRouteCoverage>\n" + gatherTestDetailsAsXml(testName) + xml + "\n</camelRouteCoverage>";
+
+                File file = new File(dir);
+                // ensure dir exists
+                file.mkdirs();
+                file = new File(dir, name);
+
+                LOG.info("Dumping route coverage to file: " + file);
+                InputStream is = new ByteArrayInputStream(combined.getBytes());
+                OutputStream os = new FileOutputStream(file, false);
+                IOHelper.copyAndCloseInput(is, os);
+                IOHelper.close(os);
+            }
+        } catch (Exception e) {
+            LOG.warn("Error during dumping route coverage statistic. This exception is ignored.", e);
+        }
+    }
+
+    /**
+     * Gathers test details as xml
+     */
+    private String gatherTestDetailsAsXml(String testName) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("<test>\n");
+        sb.append("  <class>").append(testClassName).append("</class>\n");
+        sb.append("  <method>").append(testName).append("</method>\n");
+        sb.append("</test>\n");
+        return sb.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/702f4a98/components/camel-test-spring/src/test/java/org/apache/camel/test/spring/CamelSpringRouteProcessorDumpRouteCoverageTest.java
----------------------------------------------------------------------
diff --git a/components/camel-test-spring/src/test/java/org/apache/camel/test/spring/CamelSpringRouteProcessorDumpRouteCoverageTest.java b/components/camel-test-spring/src/test/java/org/apache/camel/test/spring/CamelSpringRouteProcessorDumpRouteCoverageTest.java
new file mode 100644
index 0000000..b910e7b
--- /dev/null
+++ b/components/camel-test-spring/src/test/java/org/apache/camel/test/spring/CamelSpringRouteProcessorDumpRouteCoverageTest.java
@@ -0,0 +1,55 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.spring;
+
+import java.io.File;
+
+import org.apache.camel.management.ManagedManagementStrategy;
+import org.apache.camel.test.junit4.TestSupport;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+@RouteCoverage
+public class CamelSpringRouteProcessorDumpRouteCoverageTest extends CamelSpringRunnerPlainTest {
+
+    @BeforeClass
+    public static void prepareFiles() throws Exception {
+        TestSupport.deleteDirectory("target/camel-route-coverage");
+    }
+
+    @Test
+    public void testJmx() throws Exception {
+        // JMX is enabled with route coverage
+        assertEquals(ManagedManagementStrategy.class, camelContext.getManagementStrategy().getClass());
+    }
+
+    @Override
+    public void testRouteCoverage() throws Exception{
+        camelContext.stop();
+        camelContext2.stop();
+
+        // there should be files
+        String[] names = new File("target/camel-route-coverage").list();
+        assertNotNull(names);
+        assertTrue(names.length > 0);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/702f4a98/components/camel-test-spring/src/test/java/org/apache/camel/test/spring/CamelSpringRunnerPlainTest.java
----------------------------------------------------------------------
diff --git a/components/camel-test-spring/src/test/java/org/apache/camel/test/spring/CamelSpringRunnerPlainTest.java b/components/camel-test-spring/src/test/java/org/apache/camel/test/spring/CamelSpringRunnerPlainTest.java
index 17fa2d6..39b2d88 100644
--- a/components/camel-test-spring/src/test/java/org/apache/camel/test/spring/CamelSpringRunnerPlainTest.java
+++ b/components/camel-test-spring/src/test/java/org/apache/camel/test/spring/CamelSpringRunnerPlainTest.java
@@ -118,6 +118,11 @@ public class CamelSpringRunnerPlainTest {
         assertNull(camelContext2.getDebugger());
     }
 
+    @Test
+    public void testRouteCoverage() throws Exception {
+        // noop
+    }
+
 }
 // end::example[]
 // END SNIPPET: e1

http://git-wip-us.apache.org/repos/asf/camel/blob/702f4a98/components/camel-test/src/main/java/org/apache/camel/test/junit4/CamelTestSupport.java
----------------------------------------------------------------------
diff --git a/components/camel-test/src/main/java/org/apache/camel/test/junit4/CamelTestSupport.java b/components/camel-test/src/main/java/org/apache/camel/test/junit4/CamelTestSupport.java
index 632b8b0..2206822 100644
--- a/components/camel-test/src/main/java/org/apache/camel/test/junit4/CamelTestSupport.java
+++ b/components/camel-test/src/main/java/org/apache/camel/test/junit4/CamelTestSupport.java
@@ -391,7 +391,8 @@ public abstract class CamelTestSupport extends TestSupport {
         log.info("Took: " + TimeUtils.printDuration(time) + " (" + time + " millis)");
 
         // if we should dump route stats, then write that to a file
-        if (isDumpRouteCoverage()) {
+        boolean coverage = System.getProperty("CamelTestRouteCoverage", "false").equalsIgnoreCase("true") || isDumpRouteCoverage();
+        if (coverage) {
             String className = this.getClass().getSimpleName();
             String dir = "target/camel-route-coverage";
             String name = className + "-" + getTestMethodName() + ".xml";
@@ -485,7 +486,6 @@ public abstract class CamelTestSupport extends TestSupport {
 
         builder.append(routesSummary);
         log.info(builder.toString());
-
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/camel/blob/702f4a98/examples/camel-example-spring-boot/src/main/java/sample/camel/SampleCamelRouter.java
----------------------------------------------------------------------
diff --git a/examples/camel-example-spring-boot/src/main/java/sample/camel/SampleCamelRouter.java b/examples/camel-example-spring-boot/src/main/java/sample/camel/SampleCamelRouter.java
index 4e0422e..20cf957 100644
--- a/examples/camel-example-spring-boot/src/main/java/sample/camel/SampleCamelRouter.java
+++ b/examples/camel-example-spring-boot/src/main/java/sample/camel/SampleCamelRouter.java
@@ -29,7 +29,7 @@ public class SampleCamelRouter extends RouteBuilder {
 
     @Override
     public void configure() throws Exception {
-        from("timer:hello?period={{timer.period}}")
+        from("timer:hello?period={{timer.period}}").routeId("hello")
                 .transform(method("myBean", "saySomething"))
                 .to("stream:out");
     }

http://git-wip-us.apache.org/repos/asf/camel/blob/702f4a98/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/graph/RenderRoute.java
----------------------------------------------------------------------
diff --git a/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/graph/RenderRoute.java b/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/graph/RenderRoute.java
index 2b1cd1c..a1f4807 100644
--- a/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/graph/RenderRoute.java
+++ b/tooling/camel-route-parser/src/main/java/org/apache/camel/parser/graph/RenderRoute.java
@@ -24,6 +24,10 @@ import javax.imageio.ImageIO;
 
 import org.apache.camel.parser.model.CamelNodeDetails;
 
+/**
+ * @deprecated  experiment to render a route via Java image
+ */
+@Deprecated
 public class RenderRoute {
 
     public static void main(String[] args) {

http://git-wip-us.apache.org/repos/asf/camel/blob/702f4a98/tooling/camel-route-parser/src/test/java/org/apache/camel/parser/java/RoasterJavaDslTwoRoutesTest.java
----------------------------------------------------------------------
diff --git a/tooling/camel-route-parser/src/test/java/org/apache/camel/parser/java/RoasterJavaDslTwoRoutesTest.java b/tooling/camel-route-parser/src/test/java/org/apache/camel/parser/java/RoasterJavaDslTwoRoutesTest.java
index 4e555a9..23ccef6 100644
--- a/tooling/camel-route-parser/src/test/java/org/apache/camel/parser/java/RoasterJavaDslTwoRoutesTest.java
+++ b/tooling/camel-route-parser/src/test/java/org/apache/camel/parser/java/RoasterJavaDslTwoRoutesTest.java
@@ -51,7 +51,7 @@ public class RoasterJavaDslTwoRoutesTest extends CamelTestSupport {
         assertEquals("src/test/java/org/apache/camel/parser/java/TwoRoutesRouteBuilder.java", details2.getFileName());
 
         assertEquals("foo", details.getRouteId());
-        assertEquals("bar", details.getRouteId());
+        assertEquals("bar", details2.getRouteId());
 
         String tree = details.dump(0);
         LOG.info("\n" + tree);

http://git-wip-us.apache.org/repos/asf/camel/blob/702f4a98/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/CoverageMojo.java
----------------------------------------------------------------------
diff --git a/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/CoverageMojo.java b/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/CoverageMojo.java
new file mode 100644
index 0000000..e529587
--- /dev/null
+++ b/tooling/maven/camel-maven-plugin/src/main/java/org/apache/camel/maven/CoverageMojo.java
@@ -0,0 +1,323 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.maven;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.camel.maven.helper.EndpointHelper;
+import org.apache.camel.parser.RouteBuilderParser;
+import org.apache.camel.parser.model.CamelEndpointDetails;
+import org.apache.camel.parser.model.CamelNodeDetails;
+import org.apache.camel.parser.model.CamelRouteDetails;
+import org.apache.camel.parser.model.CamelSimpleExpressionDetails;
+import org.apache.maven.model.Resource;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.mojo.exec.AbstractExecMojo;
+import org.jboss.forge.roaster.Roaster;
+import org.jboss.forge.roaster.model.JavaType;
+import org.jboss.forge.roaster.model.source.JavaClassSource;
+
+/**
+ * Performs route coverage reports after running Camel unit tests with camel-test modules
+ *
+ * @goal coverage
+ * @threadSafe
+ */
+public class CoverageMojo extends AbstractExecMojo {
+
+    /**
+     * The maven project.
+     *
+     * @parameter property="project"
+     * @required
+     * @readonly
+     */
+    protected MavenProject project;
+
+    /**
+     * Whether to fail if a route was not fully covered
+     *
+     * @parameter property="camel.failOnError"
+     *            default-value="false"
+     */
+    private boolean failOnError;
+
+    /**
+     * Whether to include test source code
+     *
+     * @parameter property="camel.includeTest"
+     *            default-value="false"
+     */
+    private boolean includeTest;
+
+    /**
+     * To filter the names of java and xml files to only include files matching any of the given list of patterns (wildcard and regular expression).
+     * Multiple values can be separated by comma.
+     *
+     * @parameter property="camel.includes"
+     */
+    private String includes;
+
+    /**
+     * To filter the names of java and xml files to exclude files matching any of the given list of patterns (wildcard and regular expression).
+     * Multiple values can be separated by comma.
+     *
+     * @parameter property="camel.excludes"
+     */
+    private String excludes;
+
+    // CHECKSTYLE:OFF
+    @Override
+    public void execute() throws MojoExecutionException, MojoFailureException {
+
+        List<CamelEndpointDetails> endpoints = new ArrayList<>();
+        List<CamelSimpleExpressionDetails> simpleExpressions = new ArrayList<>();
+        List<CamelRouteDetails> routeIds = new ArrayList<>();
+        Set<File> javaFiles = new LinkedHashSet<File>();
+        Set<File> xmlFiles = new LinkedHashSet<File>();
+
+        // find all java route builder classes
+        List list = project.getCompileSourceRoots();
+        for (Object obj : list) {
+            String dir = (String) obj;
+            findJavaFiles(new File(dir), javaFiles);
+        }
+        // find all xml routes
+        list = project.getResources();
+        for (Object obj : list) {
+            Resource dir = (Resource) obj;
+            findXmlFiles(new File(dir.getDirectory()), xmlFiles);
+        }
+
+        if (includeTest) {
+            list = project.getTestCompileSourceRoots();
+            for (Object obj : list) {
+                String dir = (String) obj;
+                findJavaFiles(new File(dir), javaFiles);
+            }
+            list = project.getTestResources();
+            for (Object obj : list) {
+                Resource dir = (Resource) obj;
+                findXmlFiles(new File(dir.getDirectory()), xmlFiles);
+            }
+        }
+
+        List<CamelNodeDetails> routeTrees = new ArrayList<>();
+
+        for (File file : javaFiles) {
+            if (matchFile(file)) {
+                try {
+
+                    // parse the java source code and find Camel RouteBuilder classes
+                    String fqn = file.getPath();
+                    String baseDir = ".";
+                    JavaType out = Roaster.parse(file);
+                    // we should only parse java classes (not interfaces and enums etc)
+                    if (out != null && out instanceof JavaClassSource) {
+                        JavaClassSource clazz = (JavaClassSource) out;
+                        List<CamelNodeDetails> result = RouteBuilderParser.parseRouteBuilderTree(clazz, baseDir, fqn, true);
+                        routeTrees.addAll(result);
+                    }
+                } catch (Exception e) {
+                    getLog().warn("Error parsing java file " + file + " code due " + e.getMessage(), e);
+                }
+            }
+        }
+        for (File file : xmlFiles) {
+            if (matchFile(file)) {
+                try {
+                    // TODO: implement me
+                } catch (Exception e) {
+                    getLog().warn("Error parsing xml file " + file + " code due " + e.getMessage(), e);
+                }
+            }
+        }
+
+        getLog().info("Discovered " + routeTrees.size() + " routes");
+
+        // skip any routes which has no route id assigned
+
+        long anonymous = routeTrees.stream().filter(t -> t.getRouteId() == null).count();
+        if (anonymous > 0) {
+            getLog().warn("Discovered " + anonymous + " anonymous routes. Add route ids to these routes for route coverage support");
+        }
+
+        routeTrees = routeTrees.stream().filter(t -> t.getRouteId() != null).collect(Collectors.toList());
+
+        routeTrees.forEach(t -> {
+            String routeId = t.getRouteId();
+            String fileName = asRelativeFile(t.getFileName());
+            String tree = t.dump(4);
+
+            getLog().info("Route " + routeId + " discovered in file " + fileName);
+            getLog().info("\n" + tree + "\n");
+        });
+
+        int notCovered = 0;
+
+        if (failOnError && (notCovered > 0)) {
+            throw new MojoExecutionException("Some routes are not fully covered");
+        }
+    }
+    // CHECKSTYLE:ON
+
+    private static int countRouteId(List<CamelRouteDetails> details, String routeId) {
+        int answer = 0;
+        for (CamelRouteDetails detail : details) {
+            if (routeId.equals(detail.getRouteId())) {
+                answer++;
+            }
+        }
+        return answer;
+    }
+
+    private void findJavaFiles(File dir, Set<File> javaFiles) {
+        File[] files = dir.isDirectory() ? dir.listFiles() : null;
+        if (files != null) {
+            for (File file : files) {
+                if (file.getName().endsWith(".java")) {
+                    javaFiles.add(file);
+                } else if (file.isDirectory()) {
+                    findJavaFiles(file, javaFiles);
+                }
+            }
+        }
+    }
+
+    private void findXmlFiles(File dir, Set<File> xmlFiles) {
+        File[] files = dir.isDirectory() ? dir.listFiles() : null;
+        if (files != null) {
+            for (File file : files) {
+                if (file.getName().endsWith(".xml")) {
+                    xmlFiles.add(file);
+                } else if (file.isDirectory()) {
+                    findXmlFiles(file, xmlFiles);
+                }
+            }
+        }
+    }
+
+    private boolean matchFile(File file) {
+        if (excludes == null && includes == null) {
+            return true;
+        }
+
+        // exclude take precedence
+        if (excludes != null) {
+            for (String exclude : excludes.split(",")) {
+                exclude = exclude.trim();
+                // try both with and without directory in the name
+                String fqn = stripRootPath(asRelativeFile(file.getAbsolutePath()));
+                boolean match = EndpointHelper.matchPattern(fqn, exclude) || EndpointHelper.matchPattern(file.getName(), exclude);
+                if (match) {
+                    return false;
+                }
+            }
+        }
+
+        // include
+        if (includes != null) {
+            for (String include : includes.split(",")) {
+                include = include.trim();
+                // try both with and without directory in the name
+                String fqn = stripRootPath(asRelativeFile(file.getAbsolutePath()));
+                boolean match = EndpointHelper.matchPattern(fqn, include) || EndpointHelper.matchPattern(file.getName(), include);
+                if (match) {
+                    return true;
+                }
+            }
+            // did not match any includes
+            return false;
+        }
+
+        // was not excluded nor failed include so its accepted
+        return true;
+    }
+
+    private String asRelativeFile(String name) {
+        String answer = name;
+
+        String base = project.getBasedir().getAbsolutePath();
+        if (name.startsWith(base)) {
+            answer = name.substring(base.length());
+            // skip leading slash for relative path
+            if (answer.startsWith(File.separator)) {
+                answer = answer.substring(1);
+            }
+        }
+        return answer;
+    }
+
+    private String stripRootPath(String name) {
+        // strip out any leading source / resource directory
+
+        List list = project.getCompileSourceRoots();
+        for (Object obj : list) {
+            String dir = (String) obj;
+            dir = asRelativeFile(dir);
+            if (name.startsWith(dir)) {
+                return name.substring(dir.length() + 1);
+            }
+        }
+        list = project.getTestCompileSourceRoots();
+        for (Object obj : list) {
+            String dir = (String) obj;
+            dir = asRelativeFile(dir);
+            if (name.startsWith(dir)) {
+                return name.substring(dir.length() + 1);
+            }
+        }
+        List resources = project.getResources();
+        for (Object obj : resources) {
+            Resource resource = (Resource) obj;
+            String dir = asRelativeFile(resource.getDirectory());
+            if (name.startsWith(dir)) {
+                return name.substring(dir.length() + 1);
+            }
+        }
+        resources = project.getTestResources();
+        for (Object obj : resources) {
+            Resource resource = (Resource) obj;
+            String dir = asRelativeFile(resource.getDirectory());
+            if (name.startsWith(dir)) {
+                return name.substring(dir.length() + 1);
+            }
+        }
+
+        return name;
+    }
+
+    private static String asPackageName(String name) {
+        return name.replace(File.separator, ".");
+    }
+
+    private static String asSimpleClassName(String className) {
+        int dot = className.lastIndexOf('.');
+        if (dot > 0) {
+            return className.substring(dot + 1);
+        } else {
+            return className;
+        }
+    }
+}