1 /*
2  * Copyright (C) 2023 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 
17 package platform.test.runner.parameterized;
18 
19 import com.google.common.base.Ascii;
20 import com.google.common.collect.ImmutableList;
21 
22 import org.junit.runner.Runner;
23 import org.junit.runners.Suite;
24 import org.junit.runners.model.InitializationError;
25 import org.junit.runners.model.TestClass;
26 
27 import java.lang.reflect.Constructor;
28 import java.text.MessageFormat;
29 import java.util.ArrayList;
30 import java.util.List;
31 
32 /**
33  * Combines parameterization functionality of the JUnit {@code Parameterized} runner with the
34  * multi-platform dispatch functionality of the {@code AndroidJUnit4} runner.
35  *
36  * <p>Tests which run on Robolectric only don't need to use this runner; they can use {@link
37  * org.robolectric.ParameterizedRobolectricTestRunner} or {@link
38  * com.google.android.testing.rsl.robolectric.junit.ParametrizedRslTestRunner} instead.
39  *
40  * @see org.junit.runners.Parameterized
41  * @see androidx.test.runners.AndroidJunit4
42  */
43 public final class ParameterizedAndroidJunit4 extends Suite {
44 
45     private static final String JUNIT_RUNNER_SYSTEM_PROPERTY = "android.junit.runner";
46     private static final String JAVA_RUNTIME_SYSTEM_PROPERTY = "java.runtime.name";
47     private static final String JAVA_RUNTIME_ANDROID = "android";
48 
49     private static final String ROBOLECTRIC_RUNNER_CLASS_NAME =
50             "org.robolectric.RobolectricTestRunner";
51     private static final String ROBOLECTRIC_PARAMETERIZED_RUNNER_CLASS_NAME =
52             "platform.test.runner.parameterized.RobolectricParameterizedRunner";
53 
54     private static final String ANDROID_RUNNER_CLASS_NAME =
55             "androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner";
56     private static final String ANDROID_PARAMETERIZED_RUNNER_CLASS_NAME =
57             "platform.test.runner.parameterized.AndroidParameterizedRunner";
58 
59     private final ArrayList<Runner> mRunners = new ArrayList<>();
60 
ParameterizedAndroidJunit4(Class<?> klass)61     public ParameterizedAndroidJunit4(Class<?> klass) throws Throwable {
62         super(klass, ImmutableList.of());
63         TestClass testClass = getTestClass();
64         Parameters parameters =
65                 ParameterizedRunnerDelegate.getParametersMethod(testClass)
66                         .getAnnotation(Parameters.class);
67         List<Object> parametersList = ParameterizedRunnerDelegate.getParametersList(testClass);
68         for (int i = 0; i < parametersList.size(); i++) {
69             Object parametersObj = parametersList.get(i);
70             Object[] parameterArray =
71                     (parametersObj instanceof Object[])
72                             ? (Object[]) parametersObj
73                             : new Object[] {parametersObj};
74             mRunners.add(makeTestRunner(testClass, parameters, i, parameterArray));
75         }
76     }
77 
78     @SuppressWarnings("unchecked")
makeTestRunner( TestClass testClass, Parameters parameters, int i, Object[] parameterArray)79     private static Runner makeTestRunner(
80             TestClass testClass, Parameters parameters, int i, Object[] parameterArray)
81             throws InitializationError {
82         String parameterizedRunnerClassName =
83                 isRunningOnAndroid()
84                         ? ANDROID_PARAMETERIZED_RUNNER_CLASS_NAME
85                         : ROBOLECTRIC_PARAMETERIZED_RUNNER_CLASS_NAME;
86         try {
87             Class<? extends Runner> parameterizedRunnerClass =
88                     (Class<? extends Runner>)
89                             testClass
90                                     .getJavaClass()
91                                     .getClassLoader()
92                                     .loadClass(parameterizedRunnerClassName);
93             Constructor<? extends Runner> ctor =
94                     parameterizedRunnerClass.getConstructor(Class.class, int.class, String.class);
95             return ctor.newInstance(
96                     testClass.getJavaClass(), i, nameFor(parameters.name(), i, parameterArray));
97         } catch (Exception e) {
98             throw new InitializationError(e);
99         }
100     }
101 
102     @Override
getChildren()103     protected List<Runner> getChildren() {
104         return mRunners;
105     }
106 
107     /**
108      * Returns true if the test is running as an Android instrumentation test, on a real Android
109      * device or emulator.
110      */
isRunningOnAndroid()111     static boolean isRunningOnAndroid() {
112         String runnerClassName = System.getProperty(JUNIT_RUNNER_SYSTEM_PROPERTY, null);
113         if (runnerClassName != null) {
114             if (ROBOLECTRIC_RUNNER_CLASS_NAME.equals(runnerClassName)) {
115                 return false;
116             } else if (ANDROID_RUNNER_CLASS_NAME.equals(runnerClassName)) {
117                 return true;
118             } else {
119                 throw new IllegalStateException(
120                         "Don't know how to parameterized test runner class " + runnerClassName);
121             }
122         } else {
123             return Ascii.toLowerCase(System.getProperty(JAVA_RUNTIME_SYSTEM_PROPERTY))
124                     .contains(JAVA_RUNTIME_ANDROID);
125         }
126     }
127 
nameFor(String namePattern, int index, Object[] parameters)128     private static String nameFor(String namePattern, int index, Object[] parameters) {
129         String finalPattern = namePattern.replace("{index}", Integer.toString(index));
130         String name = MessageFormat.format(finalPattern, parameters);
131         return "[" + name + "]";
132     }
133 }
134