package org.robolectric;
import android.os.Build;
import javax.annotation.Nonnull;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
import org.junit.runner.notification.RunListener;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.JUnit4;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
import static android.os.Build.VERSION_CODES.M;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
@RunWith(JUnit4.class)
public class RobolectricTestRunnerMultiApiTest {
private final static int[] APIS_FOR_TEST = {
JELLY_BEAN, JELLY_BEAN_MR1, JELLY_BEAN_MR2, KITKAT, LOLLIPOP, LOLLIPOP_MR1, M,
};
private RobolectricTestRunner runner;
private Properties properties;
private RunNotifier runNotifier;
private MyRunListener runListener;
private int numSupportedApis;
private SdkPicker sdkPicker;
@Before
public void setUp() {
numSupportedApis = APIS_FOR_TEST.length;
properties = new Properties();
runListener = new MyRunListener();
runNotifier = new RunNotifier();
runNotifier.addListener(runListener);
sdkPicker = new SdkPicker(properties, APIS_FOR_TEST);
}
@Test
public void createChildrenForEachSupportedApi() throws Throwable {
runner = runnerOf(TestWithNoConfig.class);
assertThat(apisFor(runner.getChildren())).containsExactly(
JELLY_BEAN, JELLY_BEAN_MR1, JELLY_BEAN_MR2, KITKAT, LOLLIPOP, LOLLIPOP_MR1, M);
}
@Test
public void withConfigSdkLatest_shouldUseLatestSupported() throws Throwable {
runner = runnerOf(TestMethodWithNewestSdk.class);
assertThat(apisFor(runner.getChildren())).containsExactly(M);
}
@Test
public void withConfigSdkAndMinMax_shouldUseMinMax() throws Throwable {
runner = runnerOf(TestMethodWithSdkAndMinMax.class);
try {
runner.getChildren();
fail();
} catch (IllegalArgumentException e) {
assertThat(e.getMessage()).contains("sdk and minSdk/maxSdk may not be specified together" +
" (sdk=[16], minSdk=19, maxSdk=21)");
}
}
@Test
public void withEnabledSdks_createChildrenForEachSupportedSdk() throws Throwable {
properties.setProperty("robolectric.enabledSdks", "16,17");
runner = runnerOf(TestWithNoConfig.class);
assertThat(runner.getChildren()).hasSize(2);
}
@Test
public void shouldAddApiLevelToNameOfAllButHighestNumberedMethodName() throws Throwable {
runner = runnerOf(TestMethodUpToAndIncludingLollipop.class);
assertThat(runner.getChildren().get(0).getName()).isEqualTo("testSomeApiLevel[16]");
assertThat(runner.getChildren().get(1).getName()).isEqualTo("testSomeApiLevel[17]");
assertThat(runner.getChildren().get(2).getName()).isEqualTo("testSomeApiLevel[18]");
assertThat(runner.getChildren().get(3).getName()).isEqualTo("testSomeApiLevel[19]");
assertThat(runner.getChildren().get(4).getName()).isEqualTo("testSomeApiLevel");
}
@Test
public void noConfig() throws Throwable {
runner = runnerOf(TestWithNoConfig.class);
assertThat(apisFor(runner.getChildren())).containsExactly(
JELLY_BEAN, JELLY_BEAN_MR1, JELLY_BEAN_MR2, KITKAT, LOLLIPOP, LOLLIPOP_MR1, M);
runner.run(runNotifier);
assertThat(runListener.ignored).isEmpty();
assertThat(runListener.finished).hasSize(numSupportedApis);
}
@Test
public void classConfigWithSdkGroup() throws Throwable {
runner = runnerOf(TestClassConfigWithSdkGroup.class);
assertThat(apisFor(runner.getChildren())).containsExactly(JELLY_BEAN, LOLLIPOP);
runner.run(runNotifier);
assertThat(runListener.ignored).isEmpty();
// Test method should be run for JellyBean and Lollipop
assertThat(runListener.finished).hasSize(2);
}
@Test
public void methodConfigWithSdkGroup() throws Throwable {
runner = runnerOf(TestMethodConfigWithSdkGroup.class);
assertThat(apisFor(runner.getChildren())).containsExactly(JELLY_BEAN, LOLLIPOP);
runner.run(runNotifier);
assertThat(runListener.ignored).isEmpty();
// Test method should be run for JellyBean and Lollipop
assertThat(runListener.finished).hasSize(2);
}
@Test
public void classConfigMinSdk() throws Throwable {
runner = runnerOf(TestClassLollipopAndUp.class);
assertThat(apisFor(runner.getChildren())).containsExactly(LOLLIPOP, LOLLIPOP_MR1, M);
runner.run(runNotifier);
assertThat(runListener.ignored).isEmpty();
int sdksAfterAndIncludingLollipop = 3;
assertThat(runListener.finished).hasSize(sdksAfterAndIncludingLollipop);
}
@Test
public void classConfigMaxSdk() throws Throwable {
runner = runnerOf(TestClassUpToAndIncludingLollipop.class);
assertThat(apisFor(runner.getChildren())).containsExactly(JELLY_BEAN, JELLY_BEAN_MR1, JELLY_BEAN_MR2, KITKAT, LOLLIPOP);
runner.run(runNotifier);
assertThat(runListener.ignored).isEmpty();
int sdksUpToAndIncludingLollipop = 5;
assertThat(runListener.finished).hasSize(sdksUpToAndIncludingLollipop);
}
@Test
public void classConfigWithMinSdkAndMaxSdk() throws Throwable {
runner = runnerOf(TestClassBetweenJellyBeanMr2AndLollipop.class);
assertThat(apisFor(runner.getChildren())).containsExactly(JELLY_BEAN_MR2, KITKAT, LOLLIPOP);
runner.run(runNotifier);
assertThat(runListener.ignored).isEmpty();
// Since test method should only be run once
int sdksInclusivelyBetweenJellyBeanMr2AndLollipop = 3;
assertThat(runListener.finished).hasSize(sdksInclusivelyBetweenJellyBeanMr2AndLollipop);
}
@Test
public void methodConfigMinSdk() throws Throwable {
runner = runnerOf(TestMethodLollipopAndUp.class);
assertThat(apisFor(runner.getChildren())).containsExactly(LOLLIPOP, LOLLIPOP_MR1, M);
runner.run(runNotifier);
assertThat(runListener.ignored).isEmpty();
int sdksAfterAndIncludingLollipop = 3;
assertThat(runListener.finished).hasSize(sdksAfterAndIncludingLollipop);
}
@Test
public void methodConfigMaxSdk() throws Throwable {
runner = runnerOf(TestMethodUpToAndIncludingLollipop.class);
assertThat(apisFor(runner.getChildren())).containsExactly(JELLY_BEAN, JELLY_BEAN_MR1, JELLY_BEAN_MR2, KITKAT, LOLLIPOP);
runner.run(runNotifier);
assertThat(runListener.ignored).isEmpty();
int sdksUpToAndIncludingLollipop = 5;
assertThat(runListener.finished).hasSize(sdksUpToAndIncludingLollipop);
}
@Test
public void methodConfigWithMinSdkAndMaxSdk() throws Throwable {
runner = runnerOf(TestMethodBetweenJellyBeanMr2AndLollipop.class);
assertThat(apisFor(runner.getChildren())).containsExactly(JELLY_BEAN_MR2, KITKAT, LOLLIPOP);
runner.run(runNotifier);
assertThat(runListener.ignored).isEmpty();
int sdksInclusivelyBetweenJellyBeanMr2AndLollipop = 3;
assertThat(runListener.finished).hasSize(sdksInclusivelyBetweenJellyBeanMr2AndLollipop);
}
///////////////////////////
@Nonnull
private RobolectricTestRunner runnerOf(Class<?> testClass) throws InitializationError {
return new RobolectricTestRunner(testClass) {
@Nonnull @Override
protected SdkPicker createSdkPicker() {
return sdkPicker;
}
};
}
@Config(sdk = Config.ALL_SDKS)
public static class TestWithNoConfig {
@Test public void test() {}
}
@Config(sdk = {JELLY_BEAN, LOLLIPOP})
public static class TestClassConfigWithSdkGroup {
@Test public void testShouldRunApi18() {
assertThat(Build.VERSION.SDK_INT).isIn(JELLY_BEAN, LOLLIPOP);
}
}
@Config(sdk = Config.ALL_SDKS)
public static class TestMethodConfigWithSdkGroup {
@Config(sdk = {JELLY_BEAN, LOLLIPOP})
@Test public void testShouldRunApi16() {
assertThat(Build.VERSION.SDK_INT).isIn(JELLY_BEAN, LOLLIPOP);
}
}
@Config(minSdk = LOLLIPOP)
public static class TestClassLollipopAndUp {
@Test public void testSomeApiLevel() {
assertThat(Build.VERSION.SDK_INT).isGreaterThanOrEqualTo(LOLLIPOP);
}
}
@Config(maxSdk = LOLLIPOP)
public static class TestClassUpToAndIncludingLollipop {
@Test public void testSomeApiLevel() {
assertThat(Build.VERSION.SDK_INT).isLessThanOrEqualTo(LOLLIPOP);
}
}
@Config(minSdk = JELLY_BEAN_MR2, maxSdk = LOLLIPOP)
public static class TestClassBetweenJellyBeanMr2AndLollipop {
@Test public void testSomeApiLevel() {
assertThat(Build.VERSION.SDK_INT).isBetween(JELLY_BEAN_MR2, LOLLIPOP);
}
}
@Config(sdk = Config.ALL_SDKS)
public static class TestMethodLollipopAndUp {
@Config(minSdk = LOLLIPOP)
@Test public void testSomeApiLevel() {
assertThat(Build.VERSION.SDK_INT).isGreaterThanOrEqualTo(LOLLIPOP);
}
}
@Config(sdk = Config.ALL_SDKS)
public static class TestMethodUpToAndIncludingLollipop {
@Config(maxSdk = LOLLIPOP)
@Test public void testSomeApiLevel() {
assertThat(Build.VERSION.SDK_INT).isLessThanOrEqualTo(LOLLIPOP);
}
}
@Config(sdk = Config.ALL_SDKS)
public static class TestMethodBetweenJellyBeanMr2AndLollipop {
@Config(minSdk = JELLY_BEAN_MR2, maxSdk = LOLLIPOP)
@Test public void testSomeApiLevel() {
assertThat(Build.VERSION.SDK_INT).isBetween(JELLY_BEAN_MR2, LOLLIPOP);
}
}
public static class TestMethodWithNewestSdk {
@Config(sdk = Config.NEWEST_SDK)
@Test
public void testWithLatest() {
assertThat(Build.VERSION.SDK_INT).isEqualTo(M);
}
}
@Config(sdk = Config.ALL_SDKS)
public static class TestMethodWithSdkAndMinMax {
@Config(sdk = JELLY_BEAN, minSdk = KITKAT, maxSdk = LOLLIPOP)
@Test public void testWithKitKatAndLollipop() {
assertThat(Build.VERSION.SDK_INT).isBetween(KITKAT, LOLLIPOP);
}
}
private static List<Integer> apisFor(List<FrameworkMethod> children) {
List<Integer> apis = new ArrayList<>();
for (FrameworkMethod child : children) {
apis.add(((RobolectricTestRunner.RobolectricFrameworkMethod) child).sdkConfig.getApiLevel());
}
return apis;
}
private static class MyRunListener extends RunListener {
private List<String> started = new ArrayList<>();
private List<String> finished = new ArrayList<>();
private List<String> ignored = new ArrayList<>();
@Override
public void testStarted(Description description) throws Exception {
started.add(description.getDisplayName());
}
@Override
public void testFinished(Description description) throws Exception {
finished.add(description.getDisplayName());
}
@Override
public void testIgnored(Description description) throws Exception {
ignored.add(description.getDisplayName());
}
}
}