/* * Copyright 2010 - 2012 Toni Menzel, Harald Wellmann * * Licensed 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.ops4j.pax.exam.junit.impl; import java.io.IOException; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.junit.internal.runners.model.ReflectiveCallable; import org.junit.internal.runners.statements.Fail; import org.junit.runner.Description; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; import org.ops4j.pax.exam.ExamConfigurationException; import org.ops4j.pax.exam.ExceptionHelper; import org.ops4j.pax.exam.TestAddress; import org.ops4j.pax.exam.TestContainerException; import org.ops4j.pax.exam.TestDirectory; import org.ops4j.pax.exam.TestInstantiationInstruction; import org.ops4j.pax.exam.TestProbeBuilder; import org.ops4j.pax.exam.spi.ExamReactor; import org.ops4j.pax.exam.spi.StagedExamReactor; import org.ops4j.pax.exam.spi.reactors.ReactorManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Probe runner, executing tests via an invoker. Used for all operation modes except CDI. * * @author Toni Menzel * @author Harald Wellmann */ public class ProbeRunner extends BlockJUnit4ClassRunner { private static final Logger LOG = LoggerFactory.getLogger(ProbeRunner.class); /** * Reactor manager singleton. */ private ReactorManager manager; /** * Staged reactor for this test class. This may actually be a reactor already staged for a * previous test class, depending on the reactor strategy. */ private StagedExamReactor stagedReactor; private Map<FrameworkMethod, TestAddress> methodToTestAddressMap = new LinkedHashMap<FrameworkMethod, TestAddress>(); public ProbeRunner(Class<?> klass) throws InitializationError { super(klass); LOG.info("creating PaxExam runner for {}", klass); try { Object testClassInstance = klass.newInstance(); manager = ReactorManager.getInstance(); ExamReactor examReactor = manager.prepareReactor(klass, testClassInstance); addTestsToReactor(examReactor, klass, testClassInstance); stagedReactor = manager.stageReactor(); } catch (InstantiationException | IllegalAccessException | IOException | ExamConfigurationException exc) { throw new InitializationError(exc); } } /** * We decorate the super method by reactor setup and teardown. This method is called once per * class. Note that the given reactor strategy decides whether or not the setup and teardown * actually happens at this level. */ @Override public void run(RunNotifier notifier) { LOG.info("running test class {}", getTestClass().getName()); Class<?> testClass = getTestClass().getJavaClass(); try { manager.beforeClass(stagedReactor, testClass); super.run(notifier); } // CHECKSTYLE:SKIP : catch all wanted catch (Throwable e) { // rethrowing the exception does not help, we have to use the notifier here Description description = Description.createSuiteDescription(testClass); notifier.fireTestFailure(new Failure(description, e)); } finally { manager.afterClass(stagedReactor, testClass); } } /** * Override to avoid running BeforeClass and AfterClass by the driver. They shall only be run by * the container when using a probe invoker. */ @Override protected Statement classBlock(final RunNotifier notifier) { Statement statement = childrenInvoker(notifier); return statement; } /** * Override to avoid running Before, After and Rule methods by the driver. They shall only be * run by the container when using a probe invoker. */ @Override protected Statement methodBlock(FrameworkMethod method) { Object test; try { ReflectiveCallable reflectiveCallable = new ReflectiveCallable() { @Override // CHECKSTYLE:SKIP : Base class API protected Object runReflectiveCall() throws Throwable { return createTest(); } }; test = reflectiveCallable.run(); } // CHECKSTYLE:SKIP : ReflectiveCallable API catch (Throwable e) { return new Fail(e); } Statement statement = methodInvoker(method, test); return statement; } /** * When using a probe invoker, we replace the test methods of this class by a potentially larger * set of decorated test methods. Each original test method may give rise to multiple copies per * test container or configuration. */ @Override protected List<FrameworkMethod> getChildren() { if (methodToTestAddressMap.isEmpty()) { fillChildren(); } return new ArrayList<FrameworkMethod>(methodToTestAddressMap.keySet()); } private void fillChildren() { Set<TestAddress> targets = stagedReactor.getTargets(); TestDirectory testDirectory = TestDirectory.getInstance(); boolean mangleMethodNames = manager.getNumConfigurations() > 1; for (TestAddress address : targets) { FrameworkMethod frameworkMethod = (FrameworkMethod) manager.lookupTestMethod(address .root()); // The reactor may contain targets which do not belong to the current test class if (frameworkMethod == null) { continue; } Class<?> frameworkMethodClass = frameworkMethod.getMethod().getDeclaringClass(); String className = getTestClass().getJavaClass().getName(); String methodName = frameworkMethod.getName(); if (frameworkMethodClass.isAssignableFrom(getTestClass().getJavaClass())) { FrameworkMethod method = mangleMethodNames ? new DecoratedFrameworkMethod(address, frameworkMethod) : frameworkMethod; testDirectory.add(address, new TestInstantiationInstruction(className + ";" + methodName)); methodToTestAddressMap.put(method, address); } } } /** * Adds test methods to the reactor, mapping method names to test addresses which are used by * the probe invoker. * <p> * Note that when a collection of test classes is passed to an external JUnit runner like * Eclipse or Maven Surefire, this method is invoked (via the constructor of this runner) for * each class <em>before</em> the {@link #run(RunNotifier)} method is invoked for any class. * <p> * This way, we can register all test methods in the reactor before the actual test execution * starts. * * @param reactor * @param testClass * @param testClassInstance * @throws IOException * @throws ExamConfigurationException */ private void addTestsToReactor(ExamReactor reactor, Class<?> testClass, Object testClassInstance) throws IOException, ExamConfigurationException { TestProbeBuilder probe = manager.createProbeBuilder(testClassInstance); // probe.setAnchor( testClass ); for (FrameworkMethod s : super.getChildren()) { // record the method -> adress matching TestAddress address = delegateTest(testClassInstance, probe, s); if (address == null) { address = probe.addTest(testClass, s.getMethod().getName()); } manager.storeTestMethod(address, s); } reactor.addProbe(probe); } /** * FIXME What is this doing, and what is the use case? Parameterized methods break JUnit's * default behaviour, and most of these non-standard signatures introduced in 2.0.0 have been * dropped since 2.3.0. * * @param testClassInstance * @param probe * @param s * @return test address */ private TestAddress delegateTest(Object testClassInstance, TestProbeBuilder probe, FrameworkMethod s) { try { Class<?>[] types = s.getMethod().getParameterTypes(); if (types.length == 1 && types[0].isAssignableFrom(TestProbeBuilder.class)) { // do some backtracking: return (TestAddress) s.getMethod().invoke(testClassInstance, probe); } else { return null; } } // CHECKSTYLE:SKIP : catch all wanted catch (Throwable e) { throw new TestContainerException("Problem delegating to test.", e); } } /** * When using a probe invoker, we replace the super method and invoke the test method indirectly * via the reactor. */ @Override protected synchronized Statement methodInvoker(final FrameworkMethod method, final Object test) { return new Statement() { @Override // CHECKSTYLE:SKIP : Statement API public void evaluate() throws Throwable { TestAddress address = methodToTestAddressMap.get(method); TestAddress root = address.root(); LOG.debug("Invoke " + method.getName() + " @ " + address + " Arguments: " + root.arguments()); try { stagedReactor.invoke(address); } // CHECKSTYLE:SKIP : StagedExamReactor API catch (Exception e) { Throwable t = ExceptionHelper.unwind(e); throw t; } } }; } }