1 package org.robolectric; 2 3 import static android.os.Build.VERSION_CODES.LOLLIPOP; 4 import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; 5 import static android.os.Build.VERSION_CODES.M; 6 import static android.os.Build.VERSION_CODES.N; 7 import static android.os.Build.VERSION_CODES.N_MR1; 8 import static android.os.Build.VERSION_CODES.O; 9 import static com.google.common.truth.Truth.assertThat; 10 import static org.junit.Assert.fail; 11 import static org.robolectric.RobolectricTestRunner.defaultInjector; 12 13 import android.os.Build; 14 import com.google.common.collect.Range; 15 import java.util.ArrayList; 16 import java.util.Arrays; 17 import java.util.List; 18 import java.util.stream.Collectors; 19 import javax.annotation.Nonnull; 20 import org.junit.After; 21 import org.junit.Before; 22 import org.junit.Test; 23 import org.junit.runner.Description; 24 import org.junit.runner.RunWith; 25 import org.junit.runner.notification.RunListener; 26 import org.junit.runner.notification.RunNotifier; 27 import org.junit.runners.JUnit4; 28 import org.junit.runners.model.FrameworkMethod; 29 import org.junit.runners.model.InitializationError; 30 import org.robolectric.annotation.Config; 31 import org.robolectric.pluginapi.Sdk; 32 import org.robolectric.pluginapi.SdkPicker; 33 import org.robolectric.plugins.DefaultSdkPicker; 34 import org.robolectric.plugins.SdkCollection; 35 import org.robolectric.util.TestUtil; 36 import org.robolectric.util.inject.Injector; 37 38 @RunWith(JUnit4.class) 39 public class RobolectricTestRunnerMultiApiTest { 40 41 private static final int[] APIS_FOR_TEST = {LOLLIPOP, LOLLIPOP_MR1, M, N, N_MR1, O}; 42 43 private static SdkPicker delegateSdkPicker; 44 private static final Injector INJECTOR = defaultInjector() 45 .bind(SdkPicker.class, (config, usesSdk) -> delegateSdkPicker.selectSdks(config, usesSdk)) 46 .build(); 47 48 private RobolectricTestRunner runner; 49 private RunNotifier runNotifier; 50 private MyRunListener runListener; 51 52 private int numSupportedApis; 53 private String priorResourcesMode; 54 private String priorAlwaysInclude; 55 56 private SdkCollection sdkCollection; 57 58 @Before setUp()59 public void setUp() { 60 numSupportedApis = APIS_FOR_TEST.length; 61 62 runListener = new MyRunListener(); 63 runNotifier = new RunNotifier(); 64 runNotifier.addListener(runListener); 65 sdkCollection = new SdkCollection(() -> map(APIS_FOR_TEST)); 66 delegateSdkPicker = new DefaultSdkPicker(sdkCollection, null); 67 68 priorResourcesMode = System.getProperty("robolectric.resourcesMode"); 69 70 priorAlwaysInclude = System.getProperty("robolectric.alwaysIncludeVariantMarkersInTestName"); 71 System.clearProperty("robolectric.alwaysIncludeVariantMarkersInTestName"); 72 } 73 74 @After tearDown()75 public void tearDown() throws Exception { 76 TestUtil.resetSystemProperty( 77 "robolectric.alwaysIncludeVariantMarkersInTestName", priorAlwaysInclude); 78 } 79 80 @Test createChildrenForEachSupportedApi()81 public void createChildrenForEachSupportedApi() throws Throwable { 82 runner = runnerOf(TestWithNoConfig.class); 83 assertThat(apisFor(runner.getChildren())) 84 .containsExactly(LOLLIPOP, LOLLIPOP_MR1, M, N, N_MR1, O); 85 } 86 87 @Test withConfigSdkLatest_shouldUseLatestSupported()88 public void withConfigSdkLatest_shouldUseLatestSupported() throws Throwable { 89 runner = runnerOf(TestMethodWithNewestSdk.class); 90 assertThat(apisFor(runner.getChildren())).containsExactly(O); 91 } 92 93 @Test withConfigSdkAndMinMax_shouldUseMinMax()94 public void withConfigSdkAndMinMax_shouldUseMinMax() throws Throwable { 95 runner = runnerOf(TestMethodWithSdkAndMinMax.class); 96 try { 97 runner.getChildren(); 98 fail(); 99 } catch (IllegalArgumentException e) { 100 assertThat(e.getMessage()) 101 .contains( 102 "sdk and minSdk/maxSdk may not be specified together" 103 + " (sdk=[23], minSdk=23, maxSdk=24)"); 104 } 105 } 106 107 @Test withEnabledSdks_createChildrenForEachSupportedSdk()108 public void withEnabledSdks_createChildrenForEachSupportedSdk() throws Throwable { 109 delegateSdkPicker = new DefaultSdkPicker(new SdkCollection(() -> map(21, 23)), null); 110 111 runner = runnerOf(TestWithNoConfig.class); 112 assertThat(runner.getChildren()).hasSize(2); 113 } 114 115 @Test shouldAddApiLevelToNameOfAllButHighestNumberedMethodName()116 public void shouldAddApiLevelToNameOfAllButHighestNumberedMethodName() throws Throwable { 117 runner = runnerOf(TestMethodUpToAndIncludingN.class); 118 assertThat(runner.getChildren().get(0).getName()).isEqualTo("testSomeApiLevel[21]"); 119 assertThat(runner.getChildren().get(1).getName()).isEqualTo("testSomeApiLevel[22]"); 120 assertThat(runner.getChildren().get(2).getName()).isEqualTo("testSomeApiLevel[23]"); 121 assertThat(runner.getChildren().get(3).getName()).isEqualTo("testSomeApiLevel"); 122 } 123 124 @Test noConfig()125 public void noConfig() throws Throwable { 126 runner = runnerOf(TestWithNoConfig.class); 127 assertThat(apisFor(runner.getChildren())) 128 .containsExactly(LOLLIPOP, LOLLIPOP_MR1, M, N, N_MR1, O); 129 runner.run(runNotifier); 130 131 assertThat(runListener.ignored).isEmpty(); 132 assertThat(runListener.finished).hasSize(numSupportedApis); 133 } 134 135 @Test classConfigWithSdkGroup()136 public void classConfigWithSdkGroup() throws Throwable { 137 runner = runnerOf(TestClassConfigWithSdkGroup.class); 138 assertThat(apisFor(runner.getChildren())).containsExactly(M, N); 139 140 runner.run(runNotifier); 141 142 assertThat(runListener.ignored).isEmpty(); 143 // Test method should be run for M and N 144 assertThat(runListener.finished).hasSize(2); 145 } 146 147 @Test methodConfigWithSdkGroup()148 public void methodConfigWithSdkGroup() throws Throwable { 149 runner = runnerOf(TestMethodConfigWithSdkGroup.class); 150 assertThat(apisFor(runner.getChildren())).containsExactly(M, N); 151 152 runner.run(runNotifier); 153 154 assertThat(runListener.ignored).isEmpty(); 155 // Test method should be run for M, N 156 assertThat(runListener.finished).hasSize(2); 157 } 158 159 @Test classConfigMinSdk()160 public void classConfigMinSdk() throws Throwable { 161 runner = runnerOf(TestClassNAndUp.class); 162 assertThat(apisFor(runner.getChildren())).containsExactly(N, N_MR1, O); 163 164 runner.run(runNotifier); 165 166 assertThat(runListener.ignored).isEmpty(); 167 int sdksAfterAndIncludingLollipop = 3; 168 assertThat(runListener.finished).hasSize(sdksAfterAndIncludingLollipop); 169 } 170 171 @Test classConfigMaxSdk()172 public void classConfigMaxSdk() throws Throwable { 173 runner = runnerOf(TestClassUpToAndIncludingN.class); 174 assertThat(apisFor(runner.getChildren())).containsExactly(LOLLIPOP, LOLLIPOP_MR1, M, N); 175 176 runner.run(runNotifier); 177 178 assertThat(runListener.ignored).isEmpty(); 179 int sdksUpToAndIncludingLollipop = 4; 180 assertThat(runListener.finished).hasSize(sdksUpToAndIncludingLollipop); 181 } 182 183 @Test classConfigWithMinSdkAndMaxSdk()184 public void classConfigWithMinSdkAndMaxSdk() throws Throwable { 185 runner = runnerOf(TestClassBetweenLollipopMr1AndN.class); 186 assertThat(apisFor(runner.getChildren())).containsExactly(LOLLIPOP_MR1, M, N); 187 188 runner.run(runNotifier); 189 190 assertThat(runListener.ignored).isEmpty(); 191 // Since test method should only be run once 192 int sdksInclusivelyInRange = 3; 193 assertThat(runListener.finished).hasSize(sdksInclusivelyInRange); 194 } 195 196 @Test methodConfigMinSdk()197 public void methodConfigMinSdk() throws Throwable { 198 runner = runnerOf(TestMethodNAndUp.class); 199 assertThat(apisFor(runner.getChildren())).containsExactly(N, N_MR1, O); 200 201 runner.run(runNotifier); 202 203 assertThat(runListener.ignored).isEmpty(); 204 int sdksAfterAndIncludingLollipop = 3; 205 assertThat(runListener.finished).hasSize(sdksAfterAndIncludingLollipop); 206 } 207 208 @Test methodConfigMaxSdk()209 public void methodConfigMaxSdk() throws Throwable { 210 runner = runnerOf(TestMethodUpToAndIncludingN.class); 211 assertThat(apisFor(runner.getChildren())).containsExactly(LOLLIPOP, LOLLIPOP_MR1, M, N); 212 213 runner.run(runNotifier); 214 215 assertThat(runListener.ignored).isEmpty(); 216 int sdksUpToAndIncludingLollipop = 4; 217 assertThat(runListener.finished).hasSize(sdksUpToAndIncludingLollipop); 218 } 219 220 @Test methodConfigWithMinSdkAndMaxSdk()221 public void methodConfigWithMinSdkAndMaxSdk() throws Throwable { 222 runner = runnerOf(TestMethodBetweenLollipopMr1AndN.class); 223 assertThat(apisFor(runner.getChildren())).containsExactly(LOLLIPOP_MR1, M, N); 224 225 runner.run(runNotifier); 226 227 assertThat(runListener.ignored).isEmpty(); 228 int sdksInclusivelyInRange = 3; 229 assertThat(runListener.finished).hasSize(sdksInclusivelyInRange); 230 } 231 232 /////////////////////////// 233 234 @Nonnull runnerOf(Class<?> testClass)235 private RobolectricTestRunner runnerOf(Class<?> testClass) throws InitializationError { 236 return new RobolectricTestRunner(testClass, INJECTOR); 237 } 238 239 @Config(sdk = Config.ALL_SDKS) 240 public static class TestWithNoConfig { test()241 @Test public void test() {} 242 } 243 244 @Config(sdk = {M, N}) 245 public static class TestClassConfigWithSdkGroup { testShouldRunApi18()246 @Test public void testShouldRunApi18() { 247 assertThat(Build.VERSION.SDK_INT).isIn(Range.closed(M, N)); 248 } 249 } 250 251 @Config(sdk = Config.ALL_SDKS) 252 public static class TestMethodConfigWithSdkGroup { 253 @Config(sdk = {M, N}) 254 @Test testShouldRunApi16()255 public void testShouldRunApi16() { 256 assertThat(Build.VERSION.SDK_INT).isIn(Range.closed(M, N)); 257 } 258 } 259 260 @Config(minSdk = N) 261 public static class TestClassNAndUp { testSomeApiLevel()262 @Test public void testSomeApiLevel() { 263 assertThat(Build.VERSION.SDK_INT).isAtLeast(N); 264 } 265 } 266 267 @Config(maxSdk = N) 268 public static class TestClassUpToAndIncludingN { testSomeApiLevel()269 @Test public void testSomeApiLevel() { 270 assertThat(Build.VERSION.SDK_INT).isAtMost(N); 271 } 272 } 273 274 @Config(minSdk = LOLLIPOP_MR1, maxSdk = N) 275 public static class TestClassBetweenLollipopMr1AndN { testSomeApiLevel()276 @Test public void testSomeApiLevel() { 277 assertThat(Build.VERSION.SDK_INT).isIn(Range.closed(LOLLIPOP_MR1, N)); 278 } 279 } 280 281 @Config(sdk = Config.ALL_SDKS) 282 public static class TestMethodNAndUp { 283 @Config(minSdk = N) 284 @Test testSomeApiLevel()285 public void testSomeApiLevel() { 286 assertThat(Build.VERSION.SDK_INT).isAtLeast(N); 287 } 288 } 289 290 @Config(sdk = Config.ALL_SDKS) 291 public static class TestMethodUpToAndIncludingN { 292 @Config(maxSdk = N) 293 @Test testSomeApiLevel()294 public void testSomeApiLevel() { 295 assertThat(Build.VERSION.SDK_INT).isAtMost(N); 296 } 297 } 298 299 @Config(sdk = Config.ALL_SDKS) 300 public static class TestMethodBetweenLollipopMr1AndN { 301 @Config(minSdk = LOLLIPOP_MR1, maxSdk = N) 302 @Test testSomeApiLevel()303 public void testSomeApiLevel() { 304 assertThat(Build.VERSION.SDK_INT).isIn(Range.closed(LOLLIPOP_MR1, N)); 305 } 306 } 307 308 public static class TestMethodWithNewestSdk { 309 @Config(sdk = Config.NEWEST_SDK) 310 @Test testWithLatest()311 public void testWithLatest() { 312 assertThat(Build.VERSION.SDK_INT).isEqualTo(O); 313 } 314 } 315 316 @Config(sdk = Config.ALL_SDKS) 317 public static class TestMethodWithSdkAndMinMax { 318 @Config(sdk = M, minSdk = M, maxSdk = N) 319 @Test testWithSdkRange()320 public void testWithSdkRange() { 321 assertThat(Build.VERSION.SDK_INT).isIn(Range.closed(M, N)); 322 } 323 } 324 apisFor(List<FrameworkMethod> children)325 private static List<Integer> apisFor(List<FrameworkMethod> children) { 326 List<Integer> apis = new ArrayList<>(); 327 for (FrameworkMethod child : children) { 328 apis.add( 329 ((RobolectricTestRunner.RobolectricFrameworkMethod) child).getSdk().getApiLevel()); 330 } 331 return apis; 332 } 333 334 private static class MyRunListener extends RunListener { 335 private List<String> started = new ArrayList<>(); 336 private List<String> finished = new ArrayList<>(); 337 private List<String> ignored = new ArrayList<>(); 338 339 @Override testStarted(Description description)340 public void testStarted(Description description) throws Exception { 341 started.add(description.getDisplayName()); 342 } 343 344 @Override testFinished(Description description)345 public void testFinished(Description description) throws Exception { 346 finished.add(description.getDisplayName()); 347 } 348 349 @Override testIgnored(Description description)350 public void testIgnored(Description description) throws Exception { 351 ignored.add(description.getDisplayName()); 352 } 353 } 354 map(int... sdkInts)355 private List<Sdk> map(int... sdkInts) { 356 SdkCollection allSdks = TestUtil.getSdkCollection(); 357 return Arrays.stream(sdkInts).mapToObj(allSdks::getSdk).collect(Collectors.toList()); 358 } 359 } 360