/*
* Copyright 2008 Alin Dreghiciu.
*
* 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;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import org.junit.Test;
import org.junit.internal.runners.ClassRoadie;
import org.junit.internal.runners.InitializationError;
import org.junit.internal.runners.MethodValidator;
import org.junit.internal.runners.TestClass;
import org.junit.internal.runners.TestMethod;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.manipulation.Filter;
import org.junit.runner.manipulation.Filterable;
import org.junit.runner.manipulation.NoTestsRemainException;
import org.junit.runner.manipulation.Sortable;
import org.junit.runner.manipulation.Sorter;
import org.junit.runner.notification.RunNotifier;
import static org.ops4j.pax.exam.Constants.*;
import static org.ops4j.pax.exam.CoreOptions.*;
import org.ops4j.pax.exam.Info;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.OptionUtils;
import static org.ops4j.pax.exam.junit.JUnitOptions.*;
import org.ops4j.pax.exam.junit.internal.JUnit4MethodRoadie;
import org.ops4j.pax.exam.junit.internal.JUnit4TestMethod;
import org.ops4j.pax.exam.junit.options.JUnitBundlesOption;
import org.ops4j.pax.exam.options.CompositeOption;
import org.ops4j.pax.exam.options.DefaultCompositeOption;
import org.ops4j.pax.exam.options.FrameworkOption;
/**
* JUnit4 Runner to be used with the {@link org.junit.runner.RunWith} annotation to run with Pax Exam.
* The class is basically a copy of {@link org.junit.internal.runners.JUnit4ClassRunner} addapted to Pax Exam, keeping
* as much as possible the original implementation.
* It was not possible to just extend and override the JUnit4ClassRunner due to internal list fTestMethods that is a
* list of Methods, and we have to keep extra info about the test methods and Method is a final class.
*
* @author Alin Dreghiciu (adreghiciu@gmail.com)
* @since 0.3.0, December 16, 2008
*/
public class JUnit4TestRunner
extends Runner
implements Filterable, Sortable
{
private final List<JUnit4TestMethod> m_testMethods;
private final TestClass m_testClass;
public JUnit4TestRunner( Class<?> klass )
throws InitializationError
{
m_testClass = new TestClass( klass );
try
{
m_testMethods = getTestMethods();
}
catch( Exception e )
{
throw new InitializationError( e );
}
validate();
}
protected List<JUnit4TestMethod> getTestMethods()
throws Exception
{
final Collection<JUnit4ConfigMethod> configMethods = getConfigurationMethods();
final List<JUnit4TestMethod> methods = new ArrayList<JUnit4TestMethod>();
final Collection<Method> testMethods = m_testClass.getAnnotatedMethods( Test.class );
for( Method testMethod : testMethods )
{
final Option configOptions = getOptions( testMethod, configMethods );
final FrameworkOption[] frameworkOptions = OptionUtils.filter( FrameworkOption.class, configOptions );
final Option[] filteredOptions = OptionUtils.remove( FrameworkOption.class, configOptions );
if( frameworkOptions.length == 0 )
{
methods.add( new JUnit4TestMethod( testMethod, m_testClass, null, filteredOptions ) );
}
else
{
for( FrameworkOption frameworkOption : frameworkOptions )
{
methods.add( new JUnit4TestMethod( testMethod, m_testClass, frameworkOption, filteredOptions ) );
}
}
}
return methods;
}
/**
* Finds the configuration methods based on the configured {@link ConfigurationStrategy}.
*
* @return collection of configuration methods (cannot be null but can be empty)
*
* @throws Exception - If test instance cannot be created
* - Re-thrown while finding the configuration methods
*/
protected Collection<JUnit4ConfigMethod> getConfigurationMethods()
throws Exception
{
final Object testInstance = m_testClass.getJavaClass().newInstance();
ConfigurationStrategy configStrategy = m_testClass.getJavaClass().getAnnotation( ConfigurationStrategy.class );
if( configStrategy == null )
{
configStrategy = DefaultConfigurationStrategy.class.getAnnotation( ConfigurationStrategy.class );
}
final Class<? extends JUnit4ConfigMethods>[] configMethodsClasses = configStrategy.value();
final List<JUnit4ConfigMethod> configMethods = new ArrayList<JUnit4ConfigMethod>();
for( final Class<? extends JUnit4ConfigMethods> configMethodsClass : configMethodsClasses )
{
final Collection<? extends JUnit4ConfigMethod> methods =
configMethodsClass.newInstance().getConfigMethods( m_testClass, testInstance );
if( methods != null )
{
configMethods.addAll( methods );
}
}
Configuration profileConfiguration = m_testClass.getJavaClass().getAnnotation( Configuration.class );
if( profileConfiguration != null )
{
for( final Class<? extends CompositeOption> options : profileConfiguration.extend() )
{
configMethods.add( new JUnit4ConfigMethod()
{
public boolean matches( Method testMethod )
{
// match all
return true;
}
public Option[] getOptions()
throws Exception
{
return options.newInstance().getOptions();
}
}
);
}
}
return configMethods;
}
protected void validate()
throws InitializationError
{
MethodValidator methodValidator = new MethodValidator( m_testClass );
// skip the validation bellow as we may have BundleContext as parameter
// methodValidator.validateMethodsForDefaultRunner();
methodValidator.assertValid();
}
@Override
public void run( final RunNotifier notifier )
{
new ClassRoadie( notifier, m_testClass, getDescription(), new Runnable()
{
public void run()
{
runMethods( notifier );
}
}
).runProtected();
}
protected void runMethods( final RunNotifier notifier )
{
for( JUnit4TestMethod method : m_testMethods )
{
invokeTestMethod( method, notifier );
}
}
@Override
public Description getDescription()
{
Description spec = Description.createSuiteDescription( getName(), classAnnotations() );
List<JUnit4TestMethod> testMethods = m_testMethods;
for( JUnit4TestMethod method : testMethods )
{
spec.addChild( methodDescription( method ) );
}
return spec;
}
protected Annotation[] classAnnotations()
{
return m_testClass.getJavaClass().getAnnotations();
}
protected String getName()
{
return getTestClass().getName();
}
protected Object createTest()
throws Exception
{
return getTestClass().getConstructor().newInstance();
}
protected void invokeTestMethod( JUnit4TestMethod method, RunNotifier notifier )
{
Description description = methodDescription( method );
Object test;
try
{
test = createTest();
}
catch( InvocationTargetException e )
{
notifier.testAborted( description, e.getCause() );
return;
}
catch( Exception e )
{
notifier.testAborted( description, e );
return;
}
new JUnit4MethodRoadie( test, method, notifier, description ).run();
}
protected TestMethod wrapMethod( Method method )
{
return new TestMethod( method, m_testClass );
}
protected String testName( Method method )
{
return method.getName();
}
protected String testName( JUnit4TestMethod method )
{
return method.getName();
}
protected Description methodDescription( Method method )
{
return Description.createTestDescription( getTestClass().getJavaClass(), testName( method ),
testAnnotations( method )
);
}
protected Description methodDescription( JUnit4TestMethod method )
{
return Description.createTestDescription( getTestClass().getJavaClass(), testName( method ),
testAnnotations( method.getTestMethod() )
);
}
protected Annotation[] testAnnotations( Method method )
{
return method.getAnnotations();
}
public void filter( Filter filter )
throws NoTestsRemainException
{
for( Iterator<JUnit4TestMethod> iter = m_testMethods.iterator(); iter.hasNext(); )
{
JUnit4TestMethod method = iter.next();
if( !filter.shouldRun( methodDescription( method.getTestMethod() ) ) )
{
iter.remove();
}
}
if( m_testMethods.isEmpty() )
{
throw new NoTestsRemainException();
}
}
public void sort( final Sorter sorter )
{
Collections.sort( m_testMethods, new Comparator<JUnit4TestMethod>()
{
public int compare( JUnit4TestMethod o1, JUnit4TestMethod o2 )
{
return sorter.compare( methodDescription( o1 ), methodDescription( o2 ) );
}
}
);
}
protected TestClass getTestClass()
{
return m_testClass;
}
private static Option getOptions( final Method methodName,
final Collection<JUnit4ConfigMethod> configMethods )
throws Exception
{
// always add the junit extender
final DefaultCompositeOption option = new DefaultCompositeOption(
mavenBundle()
.groupId( "org.ops4j.pax.exam" )
.artifactId( "pax-exam" )
.version( Info.getPaxExamVersion() )
.update( Info.isPaxExamSnapshotVersion() )
.startLevel( START_LEVEL_SYSTEM_BUNDLES ),
mavenBundle()
.groupId( "org.ops4j.pax.exam" )
.artifactId( "pax-exam-junit-extender" )
.version( Info.getPaxExamVersion() )
.update( Info.isPaxExamSnapshotVersion() )
.startLevel( START_LEVEL_SYSTEM_BUNDLES ),
mavenBundle()
.groupId( "org.ops4j.pax.exam" )
.artifactId( "pax-exam-junit-extender-impl" )
.version( Info.getPaxExamVersion() )
.update( Info.isPaxExamSnapshotVersion() )
.startLevel( START_LEVEL_SYSTEM_BUNDLES )
);
// add options based on available configuration options from the test itself
for( JUnit4ConfigMethod configMethod : configMethods )
{
if( configMethod.matches( methodName ) )
{
option.add( configMethod.getOptions() );
}
}
// add junit bundles, if the user did not add junit bundles into configuration
if( OptionUtils.filter( JUnitBundlesOption.class, option ).length == 0 )
{
option.add( junitBundles() );
}
return option;
}
@ConfigurationStrategy
private class DefaultConfigurationStrategy
{
}
}