001/*
002 * (C) Copyright 2006-2017 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     bstefanescu
018 */
019package org.nuxeo.runtime.test.runner;
020
021import java.io.IOException;
022import java.lang.annotation.Annotation;
023import java.net.URL;
024import java.nio.file.Path;
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.List;
028
029import org.junit.ClassRule;
030import org.junit.Rule;
031import org.junit.rules.MethodRule;
032import org.junit.rules.TestRule;
033import org.junit.runner.Description;
034import org.junit.runner.notification.RunNotifier;
035import org.junit.runners.BlockJUnit4ClassRunner;
036import org.junit.runners.model.FrameworkMethod;
037import org.junit.runners.model.InitializationError;
038import org.junit.runners.model.Statement;
039import org.junit.runners.model.TestClass;
040import org.nuxeo.runtime.test.TargetResourceLocator;
041import org.nuxeo.runtime.test.runner.FeaturesLoader.Direction;
042
043import com.google.inject.Binder;
044import com.google.inject.Guice;
045import com.google.inject.Injector;
046import com.google.inject.Module;
047import com.google.inject.Stage;
048import com.google.inject.name.Names;
049
050/**
051 * A Test Case runner that can be extended through features and provide injection though Guice.
052 *
053 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
054 */
055public class FeaturesRunner extends BlockJUnit4ClassRunner {
056
057    protected static final AnnotationScanner scanner = new AnnotationScanner();
058
059    /**
060     * Guice injector.
061     */
062    protected Injector injector;
063
064    protected final FeaturesLoader loader = new FeaturesLoader(this);
065
066    protected final TargetResourceLocator locator;
067
068    public static AnnotationScanner getScanner() {
069        return scanner;
070    }
071
072    public FeaturesRunner(Class<?> classToRun) throws InitializationError {
073        super(classToRun);
074        locator = new TargetResourceLocator(classToRun);
075        try {
076            loader.loadFeatures(getTargetTestClass());
077        } catch (Throwable t) {
078            throw new InitializationError(Collections.singletonList(t));
079        }
080    }
081
082    public Class<?> getTargetTestClass() {
083        return super.getTestClass().getJavaClass();
084    }
085
086    /**
087     * May return null if the test class was not yet instantiated
088     */
089    public Object getTargetTestInstance() {
090        return underTest;
091    }
092
093    public Path getTargetTestBasepath() {
094        return locator.getBasepath();
095    }
096
097    public URL getTargetTestResource(String name) throws IOException {
098        return locator.getTargetTestResource(name);
099    }
100
101    public Iterable<RunnerFeature> getFeatures() {
102        return loader.features();
103    }
104
105    /**
106     * @since 5.6
107     */
108    public <T extends Annotation> T getConfig(Class<T> type) {
109        List<T> configs = new ArrayList<>();
110        T annotation = scanner.getAnnotation(getTargetTestClass(), type);
111        if (annotation != null) {
112            configs.add(annotation);
113        }
114        loader.apply(Direction.BACKWARD, holder -> {
115            T hAnnotation = scanner.getAnnotation(holder.type, type);
116            if (hAnnotation != null) {
117                configs.add(hAnnotation);
118            }
119        });
120        return Defaults.of(type, configs);
121    }
122
123    /**
124     * Get the annotation on the test method, if no annotation has been found, get the annotation from the test class
125     * (See {@link #getConfig(Class)})
126     *
127     * @since 5.7
128     */
129    public <T extends Annotation> T getConfig(FrameworkMethod method, Class<T> type) {
130        T config = method.getAnnotation(type);
131        if (config != null) {
132            return config;
133        }
134        // if not define, try to get the config of the class
135        return getConfig(type);
136
137    }
138
139    @Override
140    protected List<FrameworkMethod> computeTestMethods() {
141        List<FrameworkMethod> methods = super.computeTestMethods();
142        // sort a copy
143        methods = new ArrayList<>(methods);
144        MethodSorter.sortMethodsUsingSourceOrder(methods);
145        return methods;
146    }
147
148    protected void initialize() throws Exception {
149        for (RunnerFeature each : getFeatures()) {
150            each.initialize(this);
151        }
152    }
153
154    protected void beforeRun() throws Exception {
155        loader.apply(Direction.FORWARD, holder -> holder.feature.beforeRun(FeaturesRunner.this));
156    }
157
158    protected void beforeMethodRun(final FrameworkMethod method, final Object test) throws Exception {
159        loader.apply(Direction.FORWARD, holder -> holder.feature.beforeMethodRun(FeaturesRunner.this, method, test));
160    }
161
162    protected void afterMethodRun(final FrameworkMethod method, final Object test) throws Exception {
163        loader.apply(Direction.FORWARD, holder -> holder.feature.afterMethodRun(FeaturesRunner.this, method, test));
164    }
165
166    protected void afterRun() throws Exception {
167        loader.apply(Direction.BACKWARD, holder -> holder.feature.afterRun(FeaturesRunner.this));
168    }
169
170    protected void start() throws Exception {
171        loader.apply(Direction.FORWARD, holder -> holder.feature.start(FeaturesRunner.this));
172    }
173
174    protected void stop() throws Exception {
175        loader.apply(Direction.BACKWARD, holder -> holder.feature.stop(FeaturesRunner.this));
176    }
177
178    protected void beforeSetup() throws Exception {
179        loader.apply(Direction.FORWARD, holder -> holder.feature.beforeSetup(FeaturesRunner.this));
180        injector.injectMembers(underTest);
181    }
182
183    protected void afterTeardown() {
184        loader.apply(Direction.BACKWARD, holder -> holder.feature.afterTeardown(FeaturesRunner.this));
185    }
186
187    public Injector getInjector() {
188        return injector;
189    }
190
191    protected Injector onInjector(final RunNotifier aNotifier) {
192        return Guice.createInjector(Stage.DEVELOPMENT, new Module() {
193
194            @Override
195            public void configure(Binder aBinder) {
196                aBinder.bind(FeaturesRunner.class).toInstance(FeaturesRunner.this);
197                aBinder.bind(RunNotifier.class).toInstance(aNotifier);
198                aBinder.bind(TargetResourceLocator.class).toInstance(locator);
199            }
200
201        });
202    }
203
204    protected class BeforeClassStatement extends Statement {
205        protected final Statement next;
206
207        protected BeforeClassStatement(Statement aStatement) {
208            next = aStatement;
209        }
210
211        @Override
212        public void evaluate() throws Throwable {
213            initialize();
214            start();
215            beforeRun();
216            injector = injector.createChildInjector(loader.onModule());
217            try {
218                next.evaluate();
219            } finally {
220                injector = injector.getParent();
221            }
222        }
223
224    }
225
226    protected class AfterClassStatement extends Statement {
227        protected final Statement previous;
228
229        protected AfterClassStatement(Statement aStatement) {
230            previous = aStatement;
231        }
232
233        @Override
234        public void evaluate() throws Throwable {
235            previous.evaluate();
236            try {
237                afterRun();
238            } finally {
239                stop();
240            }
241        }
242    }
243
244    @Override
245    protected Statement withAfterClasses(Statement statement) {
246        Statement actual = statement;
247        actual = super.withAfterClasses(actual);
248        actual = new AfterClassStatement(actual);
249        return actual;
250    }
251
252    @Override
253    protected Statement classBlock(final RunNotifier aNotifier) {
254        injector = onInjector(aNotifier);
255        return super.classBlock(aNotifier);
256    }
257
258    @Override
259    protected List<TestRule> classRules() {
260        final RulesFactory<ClassRule, TestRule> factory = new RulesFactory<>(ClassRule.class, TestRule.class);
261
262        factory.withRule((base, description) -> new BeforeClassStatement(base)).withRules(super.classRules());
263        loader.apply(Direction.FORWARD, holder -> factory.withRules(holder.testClass, null));
264
265        return factory.build();
266    }
267
268    protected class BeforeMethodRunStatement extends Statement {
269
270        protected final Statement next;
271
272        protected final FrameworkMethod method;
273
274        protected final Object target;
275
276        protected BeforeMethodRunStatement(FrameworkMethod aMethod, Object aTarget, Statement aStatement) {
277            method = aMethod;
278            target = aTarget;
279            next = aStatement;
280        }
281
282        @Override
283        public void evaluate() throws Throwable {
284            beforeMethodRun(method, target);
285            next.evaluate();
286        }
287
288    }
289
290    protected class BeforeSetupStatement extends Statement {
291
292        protected final Statement next;
293
294        protected BeforeSetupStatement(Statement aStatement) {
295            next = aStatement;
296        }
297
298        @Override
299        public void evaluate() throws Throwable {
300            beforeSetup();
301            next.evaluate();
302        }
303
304    }
305
306    @Override
307    protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) {
308        Statement actual = statement;
309        actual = new BeforeMethodRunStatement(method, target, actual);
310        actual = super.withBefores(method, target, actual);
311        actual = new BeforeSetupStatement(actual);
312        return actual;
313    }
314
315    protected class AfterMethodRunStatement extends Statement {
316
317        protected final Statement previous;
318
319        protected final FrameworkMethod method;
320
321        protected final Object target;
322
323        protected AfterMethodRunStatement(FrameworkMethod aMethod, Object aTarget, Statement aStatement) {
324            method = aMethod;
325            target = aTarget;
326            previous = aStatement;
327        }
328
329        @Override
330        public void evaluate() throws Throwable {
331            try {
332                previous.evaluate();
333            } finally {
334                afterMethodRun(method, target);
335            }
336        }
337
338    }
339
340    protected class AfterTeardownStatement extends Statement {
341
342        protected final Statement previous;
343
344        protected AfterTeardownStatement(Statement aStatement) {
345            previous = aStatement;
346        }
347
348        @Override
349        public void evaluate() throws Throwable {
350            try {
351                previous.evaluate();
352            } finally {
353                afterTeardown();
354            }
355        }
356
357    }
358
359    @Override
360    protected Statement withAfters(FrameworkMethod method, Object target, Statement statement) {
361        Statement actual = statement;
362        actual = new AfterMethodRunStatement(method, target, actual);
363        actual = super.withAfters(method, target, actual);
364        actual = new AfterTeardownStatement(actual);
365        return actual;
366    }
367
368    @Override
369    protected List<TestRule> getTestRules(Object target) {
370        final RulesFactory<Rule, TestRule> factory = new RulesFactory<>(Rule.class, TestRule.class);
371        loader.apply(Direction.FORWARD, holder -> factory.withRules(holder.testClass, holder.feature));
372        factory.withRules(getTestClass(), target);
373        return factory.build();
374    }
375
376    @Override
377    protected List<MethodRule> rules(Object target) {
378        final RulesFactory<Rule, MethodRule> factory = new RulesFactory<>(Rule.class, MethodRule.class);
379        loader.apply(Direction.FORWARD, holder -> factory.withRules(holder.testClass, holder.feature));
380        factory.withRules(getTestClass(), target);
381        return factory.build();
382    }
383
384    protected Object underTest;
385
386    @Override
387    public Object createTest() throws Exception {
388        underTest = super.createTest();
389        loader.apply(Direction.FORWARD, holder -> holder.feature.testCreated(underTest));
390        // TODO replace underTest member with a binding
391        // Class<?> testType = underTest.getClass();
392        // injector.getInstance(Binder.class).bind(testType)
393        // .toInstance(testType.cast(underTest));
394        return underTest;
395    }
396
397    @Override
398    protected void validateZeroArgConstructor(List<Throwable> errors) {
399        // Guice can inject constructors with parameters so we don't want this
400        // method to trigger an error
401    }
402
403    @Override
404    public String toString() {
405        return "FeaturesRunner [fTest=" + getTargetTestClass() + "]";
406    }
407
408    protected class RulesFactory<A extends Annotation, R> {
409
410        protected Statement build(final Statement base, final String name) {
411            return new Statement() {
412
413                @Override
414                public void evaluate() throws Throwable {
415                    injector = injector.createChildInjector(new Module() {
416
417                        @SuppressWarnings({ "unchecked", "rawtypes" })
418                        @Override
419                        public void configure(Binder binder) {
420                            for (Object each : rules) {
421                                binder.bind((Class) each.getClass()).annotatedWith(Names.named(name)).toInstance(each);
422                                binder.requestInjection(each);
423                            }
424                        }
425
426                    });
427
428                    try {
429                        base.evaluate();
430                    } finally {
431                        injector = injector.getParent();
432                    }
433
434                }
435
436            };
437
438        }
439
440        protected class BindRule implements TestRule, MethodRule {
441
442            @Override
443            public Statement apply(Statement base, FrameworkMethod method, Object target) {
444                Statement statement = build(base, "method");
445                for (Object each : rules) {
446                    statement = ((MethodRule) each).apply(statement, method, target);
447                }
448                return statement;
449            }
450
451            @Override
452            public Statement apply(Statement base, Description description) {
453                if (rules.isEmpty()) {
454                    return base;
455                }
456                Statement statement = build(base, "test");
457                for (Object each : rules) {
458                    statement = ((TestRule) each).apply(statement, description);
459                }
460                return statement;
461            }
462
463        }
464
465        protected final Class<A> annotationType;
466
467        protected final Class<R> ruleType;
468
469        protected ArrayList<R> rules = new ArrayList<>();
470
471        protected RulesFactory(Class<A> anAnnotationType, Class<R> aRuleType) {
472            annotationType = anAnnotationType;
473            ruleType = aRuleType;
474        }
475
476        public RulesFactory<A, R> withRules(List<R> someRules) {
477            this.rules.addAll(someRules);
478            return this;
479        }
480
481        public RulesFactory<A, R> withRule(R aRule) {
482            injector.injectMembers(aRule);
483            rules.add(aRule);
484            return this;
485        }
486
487        public RulesFactory<A, R> withRules(TestClass aType, Object aTest) {
488            for (R each : aType.getAnnotatedFieldValues(aTest, annotationType, ruleType)) {
489                withRule(each);
490            }
491
492            for (FrameworkMethod each : aType.getAnnotatedMethods(annotationType)) {
493                if (ruleType.isAssignableFrom(each.getMethod().getReturnType())) {
494                    withRule(onMethod(ruleType, each, aTest));
495                }
496            }
497            return this;
498        }
499
500        public List<R> build() {
501            return Collections.singletonList(ruleType.cast(new BindRule()));
502        }
503
504        protected R onMethod(Class<R> aRuleType, FrameworkMethod aMethod, Object aTarget, Object... someParms) {
505            try {
506                return aRuleType.cast(aMethod.invokeExplosively(aTarget, someParms));
507            } catch (Throwable cause) {
508                throw new RuntimeException("Errors in rules factory " + aMethod, cause);
509            }
510        }
511
512    }
513
514    public <T extends RunnerFeature> T getFeature(Class<T> aType) {
515        return loader.getFeature(aType);
516    }
517
518}