This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-13491 in repository https://gitbox.apache.org/repos/asf/camel.git
commit ffa6a29eb392649e6d77cedade94023751ae6ba5 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Thu Jun 6 08:34:40 2019 +0200 CAMEL-13491: Fixed camel-test with isCreateCamelContextPerClass = true to correctly setup/teardown only once before/after class and reset in between test methods. This is a little bit tricky to do with JUnit 4 --- .../camel/test/junit4/CamelTearDownRule.java | 46 +++++++ .../apache/camel/test/junit4/CamelTestSupport.java | 74 +++++------ .../org/apache/camel/test/junit4/TestSupport.java | 5 +- .../CreateCamelContextPerTestFalseTest.java | 116 ++++++++++++++++ .../CreateCamelContextPerTestTrueTest.java | 148 +++++++++++++++++++++ 5 files changed, 349 insertions(+), 40 deletions(-) diff --git a/components/camel-test/src/main/java/org/apache/camel/test/junit4/CamelTearDownRule.java b/components/camel-test/src/main/java/org/apache/camel/test/junit4/CamelTearDownRule.java new file mode 100644 index 0000000..23c89b8 --- /dev/null +++ b/components/camel-test/src/main/java/org/apache/camel/test/junit4/CamelTearDownRule.java @@ -0,0 +1,46 @@ +/** + * 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.junit4; + +import org.junit.rules.ExternalResource; + +public class CamelTearDownRule extends ExternalResource { + + private final ThreadLocal<CamelTestSupport> testSupport; + + public CamelTearDownRule(ThreadLocal<CamelTestSupport> testSupport) { + this.testSupport = testSupport; + } + + @Override + protected void before() throws Throwable { + super.before(); + } + + @Override + protected void after() { + CamelTestSupport support = testSupport.get(); + if (support != null && support.isCreateCamelContextPerClass()) { + try { + support.tearDownCreateCamelContextPerClass(); + } catch (Throwable e) { + // ignore + } + } + super.after(); + } +} 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 02c167b..ce1ca20 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 @@ -88,6 +88,7 @@ import org.apache.camel.util.StopWatch; import org.apache.camel.util.TimeUtils; import org.junit.After; import org.junit.Before; +import org.junit.ClassRule; import org.junit.Rule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -120,8 +121,11 @@ public abstract class CamelTestSupport extends TestSupport { private final DebugBreakpoint breakpoint = new DebugBreakpoint(); private final StopWatch watch = new StopWatch(); private final Map<String, String> fromEndpoints = new HashMap<>(); - private final AtomicInteger tests = new AtomicInteger(0); + private static final ThreadLocal<AtomicInteger> TESTS = new ThreadLocal<>(); + private static final ThreadLocal<CamelTestSupport> INSTANCE = new ThreadLocal<>(); private CamelTestWatcher camelTestWatcher = new CamelTestWatcher(); + @ClassRule + public static final CamelTearDownRule CAMEL_TEAR_DOWN_RULE = new CamelTearDownRule(INSTANCE); /** * Use the RouteBuilder or not @@ -298,24 +302,25 @@ public abstract class CamelTestSupport extends TestSupport { log.info("********************************************************************************"); if (isCreateCamelContextPerClass()) { - while (true) { - int v = tests.get(); - if (tests.compareAndSet(v, v + 1)) { - if (v == 0) { - // test is per class, so only setup once (the first time) - doSpringBootCheck(); - setupResources(); - doPreSetup(); - doSetUp(); - doPostSetup(); - } else { - // and in between tests we must do IoC and reset mocks - postProcessTest(); - resetMocks(); - } - - break; - } + INSTANCE.set(this); + AtomicInteger v = TESTS.get(); + if (v == null) { + v = new AtomicInteger(); + TESTS.set(v); + } + if (v.getAndIncrement() == 0) { + LOG.debug("Setup CamelContext before running first test"); + // test is per class, so only setup once (the first time) + doSpringBootCheck(); + setupResources(); + doPreSetup(); + doSetUp(); + doPostSetup(); + } else { + LOG.debug("Reset between test methods"); + // and in between tests we must do IoC and reset mocks + postProcessTest(); + resetMocks(); } } else { // test is per test so always setup @@ -510,27 +515,9 @@ public abstract class CamelTestSupport extends TestSupport { log.info("********************************************************************************"); if (isCreateCamelContextPerClass()) { - while (true) { - int v = tests.get(); - if (v <= 0) { - LOG.warn("Test already teared down"); - break; - } - - if (tests.compareAndSet(v, v - 1)) { - if (v == 1) { - LOG.debug("tearDown test"); - doStopTemplates(threadConsumer.get(), threadTemplate.get(), threadFluentTemplate.get()); - doStopCamelContext(threadCamelContext.get(), threadService.get()); - doPostTearDown(); - cleanupResources(); - } - - break; - } - } + // will tear down test specially in CamelTearDownRule } else { - LOG.debug("tearDown test"); + LOG.debug("tearDown()"); doStopTemplates(consumer, template, fluentTemplate); doStopCamelContext(context, camelContextService); doPostTearDown(); @@ -538,6 +525,15 @@ public abstract class CamelTestSupport extends TestSupport { } } + void tearDownCreateCamelContextPerClass() throws Exception { + LOG.debug("tearDownCreateCamelContextPerClass()"); + TESTS.remove(); + doStopTemplates(threadConsumer.get(), threadTemplate.get(), threadFluentTemplate.get()); + doStopCamelContext(threadCamelContext.get(), threadService.get()); + doPostTearDown(); + cleanupResources(); + } + /** * Strategy to perform any post action, after {@link CamelContext} is stopped */ diff --git a/components/camel-test/src/main/java/org/apache/camel/test/junit4/TestSupport.java b/components/camel-test/src/main/java/org/apache/camel/test/junit4/TestSupport.java index ddf10e1..3f47d96 100644 --- a/components/camel-test/src/main/java/org/apache/camel/test/junit4/TestSupport.java +++ b/components/camel-test/src/main/java/org/apache/camel/test/junit4/TestSupport.java @@ -20,6 +20,7 @@ import java.io.File; import java.util.Collection; import java.util.List; import java.util.Locale; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.camel.CamelContext; import org.apache.camel.Endpoint; @@ -38,6 +39,8 @@ import org.apache.camel.support.PredicateAssertHelper; import org.junit.Assert; import org.junit.Rule; import org.junit.rules.TestName; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,8 +52,8 @@ public abstract class TestSupport extends Assert { protected static final String LS = System.lineSeparator(); private static final Logger LOG = LoggerFactory.getLogger(TestSupport.class); protected Logger log = LoggerFactory.getLogger(getClass()); - private TestName testName = new TestName(); + private CamelTestWatcher camelTestWatcher = new CamelTestWatcher(); // Builder methods for expressions used when testing // ------------------------------------------------------------------------- diff --git a/components/camel-test/src/test/java/org/apache/camel/test/patterns/CreateCamelContextPerTestFalseTest.java b/components/camel-test/src/test/java/org/apache/camel/test/patterns/CreateCamelContextPerTestFalseTest.java new file mode 100644 index 0000000..059d88b --- /dev/null +++ b/components/camel-test/src/test/java/org/apache/camel/test/patterns/CreateCamelContextPerTestFalseTest.java @@ -0,0 +1,116 @@ +/* + * 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.patterns; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.camel.CamelContext; +import org.apache.camel.EndpointInject; +import org.apache.camel.Produce; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.AfterClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CreateCamelContextPerTestFalseTest extends CamelTestSupport { + + private static final Logger LOG = LoggerFactory.getLogger(CreateCamelContextPerTestFalseTest.class); + + private static final AtomicInteger CREATED_CONTEXTS = new AtomicInteger(); + private static final AtomicInteger POST_TEAR_DOWN = new AtomicInteger(); + + @EndpointInject("mock:result") + protected MockEndpoint resultEndpoint; + + @Produce("direct:start") + protected ProducerTemplate template; + + @Override + public boolean isCreateCamelContextPerClass() { + return false; + } + + @Override + protected CamelContext createCamelContext() throws Exception { + LOG.info("createCamelContext()"); + CREATED_CONTEXTS.incrementAndGet(); + return super.createCamelContext(); + } + + @Override + protected void doPostTearDown() throws Exception { + LOG.info("doPostTearDown()"); + POST_TEAR_DOWN.incrementAndGet(); + super.doPostTearDown(); + } + + @Test + public void testSendMatchingMessage() throws Exception { + String expectedBody = "<matched/>"; + + resultEndpoint.expectedBodiesReceived(expectedBody); + + template.sendBodyAndHeader(expectedBody, "foo", "bar"); + + resultEndpoint.assertIsSatisfied(); + + assertTrue("Should create 1 or more CamelContext per test class", CREATED_CONTEXTS.get() >= 1); + } + + @Test + public void testSendAnotherMatchingMessage() throws Exception { + String expectedBody = "<matched/>"; + + resultEndpoint.expectedBodiesReceived(expectedBody); + + template.sendBodyAndHeader(expectedBody, "foo", "bar"); + + resultEndpoint.assertIsSatisfied(); + + assertTrue("Should create 1 or more CamelContext per test class", CREATED_CONTEXTS.get() >= 1); + } + + @Test + public void testSendNotMatchingMessage() throws Exception { + resultEndpoint.expectedMessageCount(0); + + template.sendBodyAndHeader("<notMatched/>", "foo", "notMatchedHeaderValue"); + + resultEndpoint.assertIsSatisfied(); + + assertTrue("Should create 1 or more CamelContext per test class", CREATED_CONTEXTS.get() >= 1); + } + + @AfterClass + public static void validateTearDown() throws Exception { + assertEquals(3, CREATED_CONTEXTS.get()); + assertEquals(3, POST_TEAR_DOWN.get()); + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + public void configure() { + from("direct:start").filter(header("foo").isEqualTo("bar")).to("mock:result"); + } + }; + } +} diff --git a/components/camel-test/src/test/java/org/apache/camel/test/patterns/CreateCamelContextPerTestTrueTest.java b/components/camel-test/src/test/java/org/apache/camel/test/patterns/CreateCamelContextPerTestTrueTest.java new file mode 100644 index 0000000..dd1e9db --- /dev/null +++ b/components/camel-test/src/test/java/org/apache/camel/test/patterns/CreateCamelContextPerTestTrueTest.java @@ -0,0 +1,148 @@ +/* + * 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.patterns; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.camel.CamelContext; +import org.apache.camel.EndpointInject; +import org.apache.camel.Produce; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.apache.camel.util.StopWatch; +import org.junit.AfterClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CreateCamelContextPerTestTrueTest extends CamelTestSupport { + + private static final Logger LOG = LoggerFactory.getLogger(CreateCamelContextPerTestTrueTest.class); + + private static final AtomicInteger CREATED_CONTEXTS = new AtomicInteger(); + private static final AtomicInteger POST_TEAR_DOWN = new AtomicInteger(); + private static final CountDownLatch LATCH = new CountDownLatch(1); + + @EndpointInject("mock:result") + protected MockEndpoint resultEndpoint; + + @Produce("direct:start") + protected ProducerTemplate template; + + @Override + public boolean isCreateCamelContextPerClass() { + return true; + } + + @Override + protected CamelContext createCamelContext() throws Exception { + LOG.info("createCamelContext()"); + CREATED_CONTEXTS.incrementAndGet(); + return super.createCamelContext(); + } + + @Override + protected void doPostTearDown() throws Exception { + LOG.info("doPostTearDown()"); + POST_TEAR_DOWN.incrementAndGet(); + super.doPostTearDown(); + LATCH.await(5, TimeUnit.SECONDS); + } + + @Test + public void testSendMatchingMessage() throws Exception { + String expectedBody = "<matched/>"; + + resultEndpoint.expectedBodiesReceived(expectedBody); + + template.sendBodyAndHeader(expectedBody, "foo", "bar"); + + resultEndpoint.assertIsSatisfied(); + + assertEquals("Should only create 1 CamelContext per test class", 1, CREATED_CONTEXTS.get()); + assertEquals("Should not call postTearDown yet", 0, POST_TEAR_DOWN.get()); + } + + @Test + public void testSendAnotherMatchingMessage() throws Exception { + String expectedBody = "<matched/>"; + + resultEndpoint.expectedBodiesReceived(expectedBody); + + template.sendBodyAndHeader(expectedBody, "foo", "bar"); + + resultEndpoint.assertIsSatisfied(); + + assertEquals("Should only create 1 CamelContext per test class", 1, CREATED_CONTEXTS.get()); + assertEquals("Should not call postTearDown yet", 0, POST_TEAR_DOWN.get()); + } + + @Test + public void testSendNotMatchingMessage() throws Exception { + resultEndpoint.expectedMessageCount(0); + + template.sendBodyAndHeader("<notMatched/>", "foo", "notMatchedHeaderValue"); + + resultEndpoint.assertIsSatisfied(); + + assertEquals("Should only create 1 CamelContext per test class", 1, CREATED_CONTEXTS.get()); + assertEquals("Should not call postTearDown yet", 0, POST_TEAR_DOWN.get()); + } + + @AfterClass + public static void shouldTearDown() throws Exception { + // we are called before doPostTearDown so lets wait for that to be called + Runnable r = () -> { + try { + StopWatch watch = new StopWatch(); + while (watch.taken() < 5000) { + LOG.debug("Checking for tear down called correctly"); + if (POST_TEAR_DOWN.get() == 1) { + LATCH.countDown(); + break; + } else { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + break; + } + } + } + } finally { + LOG.info("Should only call postTearDown 1 time per test class, called: " + POST_TEAR_DOWN.get()); + assertEquals("Should only call postTearDown 1 time per test class", 1, POST_TEAR_DOWN.get()); + } + }; + Thread t = new Thread(r); + t.setDaemon(false); + t.setName("shouldTearDown checker"); + t.start(); + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + public void configure() { + from("direct:start").filter(header("foo").isEqualTo("bar")).to("mock:result"); + } + }; + } +}