/* * Copyright 2011 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.invoker.junit.internal; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Method; import java.util.List; import org.junit.runner.Description; import org.junit.runner.JUnitCore; import org.junit.runner.Request; import org.junit.runner.Result; import org.junit.runner.notification.Failure; import org.ops4j.pax.exam.ProbeInvoker; import org.ops4j.pax.exam.TestContainerException; import org.ops4j.pax.exam.WrappedTestContainerException; import org.ops4j.pax.exam.util.Injector; import org.osgi.framework.BundleContext; /** * A ProbeInvoker which delegates the test method invocation to JUnit. * <p> * By doing so, JUnit can handle {@code @Before}, {@code @After} and {@code @Rule} annotations in * the usual way. * <p> * The test method to be executed is defined by an encoded instruction from * {@code org.ops4j.pax.exam.spi.intern.DefaultTestAddress}. * * @author Harald Wellmann * @since 2.3.0, August 2011 */ public class JUnitProbeInvoker implements ProbeInvoker { private BundleContext ctx; private String clazz; private String method; private Injector injector; private Class<?> testClass; public JUnitProbeInvoker(String encodedInstruction, BundleContext bundleContext, Injector injector) { // parse class and method out of expression: String[] parts = encodedInstruction.split(";"); clazz = parts[0]; method = parts[1]; ctx = bundleContext; this.injector = injector; try { testClass = ctx.getBundle().loadClass(clazz); } catch (ClassNotFoundException e) { throw new TestContainerException(e); } } public void call(Object... args) { if (!(findAndInvoke(args))) { throw new TestContainerException(" Test " + method + " not found in test class " + testClass.getName()); } } private boolean findAndInvoke(Object...args) { Integer index = null; try { /* * If args are present, we expect exactly one integer argument, defining the index of * the parameter set for a parameterized test. */ if (args.length > 0) { if (!(args[0] instanceof Integer)) { throw new TestContainerException("Integer argument expected"); } index = (Integer) args[0]; } // find matching method for (Method m : testClass.getMethods()) { if (m.getName().equals(method)) { // we assume its correct: invokeViaJUnit(m, index); return true; } } } catch (NoClassDefFoundError e) { throw new TestContainerException(e); } return false; } /** * Invokes a given method of a given test class via {@link JUnitCore} and injects dependencies * into the instantiated test class. * <p> * This requires building a {@code Request} which is aware of an {@code Injector} and a * {@code BundleContext}. * * @param testClass * @param testMethod * @throws TestContainerException */ private void invokeViaJUnit(final Method testMethod, Integer index) { Request classRequest = new ContainerTestRunnerClassRequest(testClass, injector, index); Description methodDescription = Description.createTestDescription(testClass, method); Request request = classRequest.filterWith(methodDescription); JUnitCore junit = new JUnitCore(); Result result = junit.run(request); List<Failure> failures = result.getFailures(); if (!failures.isEmpty()) { throw createTestContainerException(failures.toString(), failures.get(0).getException()); } } /** * Creates exception for test failure and makes sure it is serializable. * @param message * @param ex * @return serializable exception */ private TestContainerException createTestContainerException(String message, Throwable ex) { return isSerializable(ex) ? new TestContainerException(message, ex) : new WrappedTestContainerException(message, ex); } /** * Check if given exception is serializable by doing a serialization and * checking the exception * * @param ex exception to check * @return if the given exception is serializable */ private boolean isSerializable(Throwable ex) { try { new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(ex); return true; } // CHECKSTYLE:SKIP catch (Throwable ex2) { return false; } } }