1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
61
62
63
64
65
66
67
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
80
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
196
197
198
199
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
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
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 }