1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package vogar.target.junit;
18
19import java.lang.annotation.Annotation;
20import java.lang.reflect.Method;
21import java.util.ArrayList;
22import java.util.HashMap;
23import java.util.List;
24import java.util.Map;
25import java.util.Set;
26import junit.framework.AssertionFailedError;
27import org.junit.runners.BlockJUnit4ClassRunner;
28import org.junit.runners.model.FrameworkMethod;
29import org.junit.runners.model.InitializationError;
30import org.junit.runners.model.RunnerBuilder;
31
32/**
33 * A specialization of {@link BlockJUnit4ClassRunner} to implement behavior required by Vogar.
34 *
35 * <ol>
36 * <li>Defers validation of test methods,
37 * see {@link ValidateTestMethodWhenRunBlockJUnit4ClassRunner}.</li>
38 * <li>Applies global rules, see {@link ApplyGlobalRulesBlockJUnit4ClassRunner}</li>
39 * <li>Selects either explicitly requested methods, or all methods.</li>
40 * </ol>
41 */
42public class VogarBlockJUnit4ClassRunner
43        extends ValidateTestMethodWhenRunBlockJUnit4ClassRunner {
44
45    private final RunnerParams runnerParams;
46
47    /**
48     * Used by annotation runner.
49     */
50    @SuppressWarnings("unused")
51    public VogarBlockJUnit4ClassRunner(Class<?> klass, RunnerBuilder suiteBuilder)
52            throws InitializationError {
53        this(klass, ((VogarRunnerBuilder) suiteBuilder).getRunnerParams());
54    }
55
56    public VogarBlockJUnit4ClassRunner(Class<?> klass, RunnerParams runnerParams)
57            throws InitializationError {
58        super(klass, runnerParams.getTestRule());
59        this.runnerParams = runnerParams;
60    }
61
62    @Override
63    protected List<FrameworkMethod> getChildren() {
64        // Overridden to handle requested methods.
65        Set<String> requestedMethodNames = JUnitUtils.mergeQualificationAndArgs(
66                runnerParams.getQualification(), runnerParams.getArgs());
67        List<FrameworkMethod> methods = super.getChildren();
68
69        // If specific methods have been requested then select them from all the methods that were
70        // found. If they cannot be found then add a fake one that will report the method as
71        // missing.
72        if (!requestedMethodNames.isEmpty()) {
73            // Store all the methods in a map by name. That should be safe as test methods do not
74            // have parameters so there can only be one method in a class with each name.
75            Map<String, FrameworkMethod> map = new HashMap<>();
76            for (FrameworkMethod method : methods) {
77                map.put(method.getName(), method);
78            }
79
80            methods = new ArrayList<>();
81            for (final String name : requestedMethodNames) {
82                FrameworkMethod method = map.get(name);
83                if (method == null) {
84                    // The method could not be found so add one that when invoked will report the
85                    // method as missing.
86                    methods.add(new MissingFrameworkMethod(name));
87                } else {
88                    methods.add(method);
89                }
90            }
91        }
92        return methods;
93    }
94
95    /**
96     * A {@link FrameworkMethod} that is used when a specific method has been requested but no
97     * suitable {@link Method} exists.
98     *
99     * <p>It overrides a number of methods that are called during normal processing in order to
100     * avoid throwing a NPE. It also overrides {@link #validatePublicVoidNoArg(boolean, List)} to
101     * report the method as being missing. It relies on a {@link ValidateMethodStatement} to call
102     * that method immediately prior to invoking the method.
103     */
104    private static class MissingFrameworkMethod extends FrameworkMethod {
105        private static final Annotation[] NO_ANNOTATIONS = new Annotation[0];
106        private static final Method DUMMY_METHOD;
107        static {
108            DUMMY_METHOD = Object.class.getMethods()[0];
109        }
110        private final String name;
111
112        public MissingFrameworkMethod(String name) {
113            super(DUMMY_METHOD);
114            this.name = name;
115        }
116
117        @Override
118        public String getName() {
119            // Overridden to avoid NPE.
120            return name;
121        }
122
123        @Override
124        public void validatePublicVoidNoArg(boolean isStatic, List<Throwable> errors) {
125            // Overridden to report the method as missing.
126            errors.add(new AssertionFailedError("Method \"" + name + "\" not found"));
127        }
128
129        @Override
130        public Annotation[] getAnnotations() {
131            // Overridden to avoid NPE.
132            return NO_ANNOTATIONS;
133        }
134
135        @Override
136        public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
137            // Overridden to avoid NPE.
138            return null;
139        }
140    }
141}
142