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