View Javadoc
1   /*
2    * The MIT License
3    * Copyright © 2016 AdvisedTesting
4    *
5    * Permission is hereby granted, free of charge, to any person obtaining a copy
6    * of this software and associated documentation files (the "Software"), to deal
7    * in the Software without restriction, including without limitation the rights
8    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9    * copies of the Software, and to permit persons to whom the Software is
10   * furnished to do so, subject to the following conditions:
11   *
12   * The above copyright notice and this permission notice shall be included in
13   * all copies or substantial portions of the Software.
14   *
15   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21   * THE SOFTWARE.
22   */
23  package com.github.advisedtesting.junit4;
24  
25  import static com.github.advisedtesting.core.internal.AdviceAnnotationEvaluator.inspect;
26  import static com.github.advisedtesting.core.internal.ExceptionEvaluator.convertExceptionIfPossible;
27  
28  import java.lang.annotation.Annotation;
29  import java.lang.reflect.AccessibleObject;
30  import java.lang.reflect.Method;
31  import java.util.ArrayList;
32  import java.util.Arrays;
33  import java.util.Collections;
34  import java.util.List;
35  import java.util.stream.Collectors;
36  
37  import org.aopalliance.intercept.MethodInterceptor;
38  import org.junit.AssumptionViolatedException;
39  import org.junit.Rule;
40  import org.junit.Test;
41  import org.junit.internal.runners.model.EachTestNotifier;
42  import org.junit.rules.RunRules;
43  import org.junit.rules.TestRule;
44  import org.junit.runner.notification.RunNotifier;
45  import org.junit.runners.BlockJUnit4ClassRunner;
46  import org.junit.runners.model.FrameworkMethod;
47  import org.junit.runners.model.InitializationError;
48  import org.junit.runners.model.Statement;
49  import org.junit.runners.model.TestClass;
50  
51  import com.github.advisedtesting.core.ConstraintException;
52  import com.github.advisedtesting.core.ContextAwareMethodInvocation;
53  import com.github.advisedtesting.core.ObjectFactory;
54  import com.github.advisedtesting.core.internal.ProviderAwareObjectFactoryAggregate;
55  import com.github.advisedtesting.core.internal.TestContext;
56  
57  public class Junit4AopClassRunner extends BlockJUnit4ClassRunner {
58  
59    /**
60     * <p>
61     * Hold the instances of each {@link MethodInterceptor}.
62     * </p>
63     * <p>
64     * this is a bit sad, but in order for instances to be shared across junit 4 tests, and
65     * with the lack of a reference to a long lived object, the static context appears to be 
66     * necessary.
67     * </p>
68     */ 
69    private static final TestContext CONTEXT = new TestContext();
70    private final Class<?> targetClass;
71    
72    public Junit4AopClassRunner(final Class<?> klass) throws InitializationError {
73      super(klass);
74      targetClass = klass;
75    }
76  
77    @Override
78    protected void validateTestMethods(List<Throwable> errors) {
79      // No Op
80      // TODO: validate with the object factories?
81    }
82  
83    @Override
84    protected void runChild(final FrameworkMethod method, final RunNotifier notifier) {
85      runContextualizedLeaf(method, notifier);
86    }
87  
88    @Override
89    protected List<FrameworkMethod> computeTestMethods() {
90      return getTestClass().getAnnotatedMethods(Test.class);
91    }
92  
93    private final void runContextualizedLeaf(final FrameworkMethod frameworkMethod, final RunNotifier notifier) {
94      final EachTestNotifier eachNotifier = new EachTestNotifier(notifier, describeChild(frameworkMethod));
95      eachNotifier.fireTestStarted();
96      ProviderAwareObjectFactoryAggregate registrar = new ProviderAwareObjectFactoryAggregate();
97      List<Annotation> annotations = adviceAnnotations(frameworkMethod);
98      Collections.reverse(annotations);
99      DelayedConstructionStatement delayedStatement = new DelayedConstructionStatement(frameworkMethod, targetClass, registrar);
100     Statement statement = delayedStatement;
101     for (Annotation annotation : annotations) {
102       statement = new AdvisedStatement(statement, CONTEXT, registrar, annotation);
103     }
104     try {
105       statement.evaluate();
106     } catch (final Throwable th) {
107       final ConstraintException contraintException = convertExceptionIfPossible(th, ConstraintException.class);
108       if (contraintException != null) {
109         eachNotifier.addFailedAssumption(new AssumptionViolatedException(contraintException.getMessage(), contraintException));
110       } else {
111         eachNotifier.addFailure(th);
112       }
113     } finally {
114       eachNotifier.fireTestFinished();
115     }
116   }
117 
118   private List<Annotation> adviceAnnotations(final FrameworkMethod frameworkMethod) {
119     List<Annotation> annotations = new ArrayList<>();
120     for (Annotation annotation : inspect(frameworkMethod.getMethod().getAnnotations())) {
121       if (CONTEXT.isAdviceAnnotation(annotation)) {
122         annotations.add(annotation);
123       }
124     }
125     return annotations;
126   }
127 
128   public static class AdvisedStatement extends Statement {
129 
130     private final Statement advised;
131     private final TestContext context;
132     private final ProviderAwareObjectFactoryAggregate registry;
133     private final Annotation annotation;
134 
135     public AdvisedStatement(Statement advised, TestContext context, ProviderAwareObjectFactoryAggregate registry,
136             Annotation annotation) {
137       this.advised = advised;
138       this.context = context;
139       this.registry = registry;
140       this.annotation = annotation;
141     }
142 
143     @Override
144     public void evaluate() throws Throwable {
145       ClassLoader classloader = Thread.currentThread().getContextClassLoader();
146       MethodInterceptor advisor = context.getAdviceFor(annotation, classloader);
147       advisor.invoke(new ContextAwareMethodInvocation() {
148 
149         @Override
150         public void registerObjectFactory(ObjectFactory factory) {
151           registry.register(annotation, factory);
152         }
153 
154         @Override
155         public ObjectFactory getCurrentContextFactory() {
156           return registry;
157         }
158 
159         @Override
160         public Object proceed() throws Throwable {
161           advised.evaluate();
162           return null;
163         }
164 
165         @Override
166         public Object getThis() {
167           return null;
168         }
169 
170         @Override
171         public AccessibleObject getStaticPart() {
172           return null;
173         }
174 
175         @Override
176         public Object[] getArguments() {
177           return new Object[] {};
178         }
179 
180         @Override
181         public Method getMethod() {
182           return null;
183         }
184 
185         @Override
186         public Annotation getTargetAnnotation() {
187           return annotation;
188         }
189       });
190     }
191 
192   }
193   
194   /**
195    * Get rules for this test object.
196    * 
197    * @param target
198    *          the test case instance
199    * @return a list of TestRules that should be applied when executing this test
200    */
201   @Override
202   protected List<TestRule> getTestRules(Object target) {
203     TestClass testClass = new TestClass(target.getClass());
204     List<TestRule> result = testClass.getAnnotatedMethodValues(target, Rule.class, TestRule.class);
205 
206     result.addAll(testClass.getAnnotatedFieldValues(target, Rule.class, TestRule.class));
207 
208     return result;
209   }
210 
211   public class DelayedConstructionStatement extends Statement {
212     private final String testName;
213     private final List<String> parameterTypes;
214     private final String targetClass;
215     private final ProviderAwareObjectFactoryAggregate registry;
216     private boolean wrapped = false;
217     private Statement wrappedStatement = null;
218 
219     public DelayedConstructionStatement(FrameworkMethod fmethod, Class<?> targetClass,
220             ProviderAwareObjectFactoryAggregate registry) {
221       this.testName = fmethod.getMethod().getName();
222       this.parameterTypes = Arrays.asList(fmethod.getMethod().getParameterTypes()).stream().map(clazz -> clazz.getName())
223               .collect(Collectors.toList());
224       this.targetClass = targetClass.getName();
225       this.registry = registry;
226     }
227 
228     @Override
229     @SuppressWarnings("deprecation")
230     public void evaluate() throws Throwable {
231       if (wrapped) {
232         wrappedStatement.evaluate();
233       } else {
234         wrapped = true;
235         ClassLoader loader = Thread.currentThread().getContextClassLoader();
236         Class<?> targetClassInLoader = Class.forName(targetClass, true, loader);
237         Object target = targetClassInLoader.newInstance();
238         List<Class<?>> parameters = new ArrayList<>();
239         for (String className : parameterTypes) {
240           parameters.add(Class.forName(className, true, loader));
241         }
242         Method method = targetClassInLoader.getMethod(testName, parameters.toArray(new Class[] {}));
243         FrameworkMethod fmethod = new FrameworkMethod(method);
244         Statement newTarget = new IvokationMethodWithArguments(target, fmethod, registry.getArgumentsFor(method));
245         //simulates standard statement processing by junit 4.
246         newTarget = possiblyExpectingExceptions(fmethod, target, newTarget);
247         newTarget = withPotentialTimeout(fmethod, target, newTarget);
248         newTarget = withBefores(fmethod, target, newTarget);
249         newTarget = withAfters(fmethod, target, newTarget);
250         //newTarget = withRules(fmethod, target, newTarget);
251         List<TestRule> testRules = getTestRules(target);
252         newTarget = new RunRules(newTarget, testRules, describeChild(fmethod));
253         newTarget.evaluate();
254       }
255     }
256   }
257   
258   public static class IvokationMethodWithArguments extends Statement {
259 
260     private final Object target;
261     private final FrameworkMethod method;
262     private final Object[] arguments;
263     
264     
265     
266     public IvokationMethodWithArguments(Object target, FrameworkMethod method, Object[] arguments) {
267       super();
268       this.target = target;
269       this.method = method;
270       this.arguments = arguments;
271     }
272 
273     @Override
274     public void evaluate() throws Throwable {
275       method.invokeExplosively(target, arguments);
276     }
277     
278   }
279 }