/* * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * bstefanescu */ package org.eclipse.ecr.testlib.runner; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; 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 com.google.inject.Binder; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Module; /** * A Test Case runner that can be extended through features and provide injection * though Guice. * * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> */ public class FeaturesRunner extends BlockJUnit4ClassRunner { protected static final AnnotationScanner scanner = new AnnotationScanner(); /** * Guice injector. */ protected Injector injector; protected List<RunnerFeature> features; public static AnnotationScanner getScanner() { return scanner; } public FeaturesRunner(Class<?> classToRun) throws InitializationError { super(classToRun); try { loadFeatures(getTestClass().getJavaClass()); initialize(); } catch (Throwable t) { throw new InitializationError(Collections.singletonList(t)); } } public Class<?> getTargetTestClass() { return super.getTestClass().getJavaClass(); } protected void loadFeature(HashSet<Class<?>> cycles, LinkedHashSet<Class<? extends RunnerFeature>> features, Class<? extends RunnerFeature> clazz) throws Exception { if (features.contains(clazz)) { return; } if (cycles.contains(clazz)) { throw new IllegalStateException("Cycle detected in features dependencies of "+clazz); } cycles.add(clazz); scanner.scan(clazz); // load required features from annotation List<Features> annos = scanner.getAnnotations(clazz, Features.class); if (annos != null) { for (Features anno : annos) { for (Class<? extends RunnerFeature> cl : anno.value()) { if (!features.contains(cl)) { loadFeature(cycles, features, cl); } } } } features.add(clazz); // add at the end to ensure requirements are added first } protected void loadFeatures(Class<?> classToRun) throws Exception { scanner.scan(classToRun); LinkedHashSet<Class<? extends RunnerFeature>> features = new LinkedHashSet<Class<? extends RunnerFeature>>(); // load required features from annotation List<Features> annos = scanner.getAnnotations(classToRun, Features.class); if (annos != null) { for (Features anno : annos) { for (Class<? extends RunnerFeature> cl : anno.value()) { if (!features.contains(cl)) { loadFeature(new HashSet<Class<?>>(), features, cl); } } } } // register collected features this.features = new ArrayList<RunnerFeature>(); for (Class<? extends RunnerFeature> fc : features) { RunnerFeature rf = fc.newInstance(); this.features.add(rf); } } public <T extends RunnerFeature> T getFeature(Class<T> type) { for (RunnerFeature rf : features) { if (rf.getClass() == type) { return type.cast(rf); } } return null; } public List<RunnerFeature> getFeatures() { return features; } protected void initialize() throws Exception { for (RunnerFeature feature : features) { feature.initialize(this); } } protected void beforeRun() throws Exception { for (RunnerFeature feature : features) { feature.beforeRun(this); } } protected void beforeMethodRun(FrameworkMethod method, Object test) throws Exception { for (RunnerFeature feature : features) { feature.beforeMethodRun(this, method, test); } } protected void afterMethodRun(FrameworkMethod method, Object test) throws Exception { for (RunnerFeature feature : features) { feature.afterMethodRun(this, method, test); } } protected void afterRun() throws Exception { for (RunnerFeature feature : features) { feature.afterRun(this); } } protected void start() throws Exception { for (RunnerFeature feature : features) { feature.start(this); } } protected void stop() throws Exception { for (RunnerFeature feature : features) { feature.stop(this); } } protected void configureBindings(Binder binder) { binder.bind(FeaturesRunner.class).toInstance(this); for (RunnerFeature feature : features) { feature.configure(this, binder); } } /** * Gets the Guice injector. */ public Injector getInjector() { return injector; } public void resetInjector() { injector = createInjector(); } protected Injector createInjector() { Module module = new Module() { @Override public void configure(Binder arg0) { configureBindings(arg0); } }; // build injector return Guice.createInjector(module); } @Override public void run(final RunNotifier notifier) { try { try { start(); // create injector resetInjector(); try { beforeRun(); super.run(notifier); // launch tests } finally { afterRun(); } } finally { stop(); } } catch (Throwable e) { notifier.fireTestFailure(new Failure(getDescription(), e)); } } @Override public Object createTest() { // Return a Guice injected test class return injector.getInstance(getTestClass().getJavaClass()); } @Override protected void validateZeroArgConstructor(List<Throwable> errors) { // Guice can inject constructors with parameters so we don't want this // method to trigger an error } @Override protected Statement methodInvoker(FrameworkMethod method, Object test) { return new InvokeMethod(method, test); } protected class InvokeMethod extends Statement { protected final FrameworkMethod testMethod; protected final Object target; protected InvokeMethod(FrameworkMethod testMethod, Object target) { this.testMethod = testMethod; this.target= target; } @Override public void evaluate() throws Throwable { beforeMethodRun(testMethod, target); try { testMethod.invokeExplosively(target); } finally { afterMethodRun(testMethod, target); } } } }