blob: 05118813991aa2b3836ad75db7744cb39382b59c [file] [log] [blame]
package org.robolectric.android.internal;
import static android.os.Build.VERSION_CODES.O;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import static org.robolectric.annotation.ConscryptMode.Mode.OFF;
import static org.robolectric.annotation.ConscryptMode.Mode.ON;
import static org.robolectric.annotation.LooperMode.Mode.LEGACY;
import android.app.Application;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.util.DisplayMetrics;
import androidx.test.core.app.ApplicationProvider;
import java.io.File;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.crypto.Cipher;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.BootstrapDeferringRobolectricTestRunner;
import org.robolectric.BootstrapDeferringRobolectricTestRunner.BootstrapWrapperI;
import org.robolectric.BootstrapDeferringRobolectricTestRunner.RoboInject;
import org.robolectric.RoboSettings;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.android.DeviceConfig;
import org.robolectric.android.DeviceConfig.ScreenSize;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.ConscryptMode;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.experimental.LazyApplication;
import org.robolectric.annotation.experimental.LazyApplication.LazyLoad;
import org.robolectric.manifest.AndroidManifest;
import org.robolectric.manifest.RoboNotFoundException;
import org.robolectric.plugins.HierarchicalConfigurationStrategy.ConfigurationImpl;
import org.robolectric.res.ResourceTable;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowApplication;
import org.robolectric.shadows.ShadowLooper;
@RunWith(BootstrapDeferringRobolectricTestRunner.class)
@LooperMode(LEGACY)
public class AndroidTestEnvironmentTest {
@RoboInject BootstrapWrapperI bootstrapWrapper;
@Test
public void setUpApplicationState_configuresGlobalScheduler() {
bootstrapWrapper.callSetUpApplicationState();
assertThat(RuntimeEnvironment.getMasterScheduler())
.isSameInstanceAs(ShadowLooper.getShadowMainLooper().getScheduler());
assertThat(RuntimeEnvironment.getMasterScheduler())
.isSameInstanceAs(ShadowApplication.getInstance().getForegroundThreadScheduler());
}
@Test
public void setUpApplicationState_setsBackgroundScheduler_toBeSameAsForeground_whenAdvancedScheduling() {
RoboSettings.setUseGlobalScheduler(true);
try {
bootstrapWrapper.callSetUpApplicationState();
final ShadowApplication shadowApplication =
Shadow.extract(ApplicationProvider.getApplicationContext());
assertThat(shadowApplication.getBackgroundThreadScheduler())
.isSameInstanceAs(shadowApplication.getForegroundThreadScheduler());
assertThat(RuntimeEnvironment.getMasterScheduler())
.isSameInstanceAs(RuntimeEnvironment.getMasterScheduler());
} finally {
RoboSettings.setUseGlobalScheduler(false);
}
}
@Test
public void setUpApplicationState_setsBackgroundScheduler_toBeDifferentToForeground_byDefault() {
bootstrapWrapper.callSetUpApplicationState();
final ShadowApplication shadowApplication =
Shadow.extract(ApplicationProvider.getApplicationContext());
assertThat(shadowApplication.getBackgroundThreadScheduler())
.isNotSameInstanceAs(shadowApplication.getForegroundThreadScheduler());
}
@Test
public void setUpApplicationState_setsMainThread() {
RuntimeEnvironment.setMainThread(new Thread());
assertThat(RuntimeEnvironment.isMainThread()).isFalse();
bootstrapWrapper.callSetUpApplicationState();
assertThat(RuntimeEnvironment.isMainThread()).isTrue();
}
@Test
public void setUpApplicationState_setsMainThread_onAnotherThread() throws InterruptedException {
final AtomicBoolean res = new AtomicBoolean();
Thread t =
new Thread(() -> {
bootstrapWrapper.callSetUpApplicationState();
res.set(RuntimeEnvironment.isMainThread());
});
t.start();
t.join();
assertThat(res.get()).isTrue();
assertThat(RuntimeEnvironment.isMainThread()).isFalse();
}
/**
* Checks that crypto primitives that are available in an Android environment are also available
* in Robolectric via {@link BouncyCastleProvider}.
*/
@Test
@ConscryptMode(ON)
public void testWhenConscryptModeOn_ConscryptInstalled()
throws CertificateException, NoSuchAlgorithmException {
bootstrapWrapper.callSetUpApplicationState();
CertificateFactory factory = CertificateFactory.getInstance("X.509");
assertThat(factory.getProvider().getName()).isEqualTo("Conscrypt");
MessageDigest digest = MessageDigest.getInstance("SHA256");
assertThat(digest.getProvider().getName()).isEqualTo("Conscrypt");
}
@Test
@ConscryptMode(ON)
public void testWhenConscryptModeOn_BouncyCastleInstalled() throws GeneralSecurityException {
bootstrapWrapper.callSetUpApplicationState();
Cipher aesCipher = Cipher.getInstance("RSA/None/OAEPWithSHA-256AndMGF1Padding");
assertThat(aesCipher.getProvider().getName()).isEqualTo(BouncyCastleProvider.PROVIDER_NAME);
}
@Test
@ConscryptMode(OFF)
public void testWhenConscryptModeOff_ConscryptNotInstalled()
throws CertificateException, NoSuchAlgorithmException {
bootstrapWrapper.callSetUpApplicationState();
CertificateFactory factory = CertificateFactory.getInstance("X.509");
assertThat(factory.getProvider().getName()).isNotEqualTo("Conscrypt");
MessageDigest digest = MessageDigest.getInstance("SHA256");
assertThat(digest.getProvider().getName()).isNotEqualTo("Conscrypt");
}
@Test
@ConscryptMode(OFF)
public void testWhenConscryptModeOff_BouncyCastleInstalled() throws GeneralSecurityException {
bootstrapWrapper.callSetUpApplicationState();
MessageDigest digest = MessageDigest.getInstance("SHA256");
assertThat(digest.getProvider().getName()).isEqualTo(BouncyCastleProvider.PROVIDER_NAME);
Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
assertThat(aesCipher.getProvider().getName()).isEqualTo(BouncyCastleProvider.PROVIDER_NAME);
}
@Test
public void setUpApplicationState_setsVersionQualifierFromSdk() {
String givenQualifiers = "";
ConfigurationImpl config = new ConfigurationImpl();
config.put(Config.class, new Config.Builder().setQualifiers(givenQualifiers).build());
config.put(LooperMode.Mode.class, LEGACY);
bootstrapWrapper.changeConfig(config);
bootstrapWrapper.callSetUpApplicationState();
assertThat(RuntimeEnvironment.getQualifiers()).contains("v" + Build.VERSION.RESOURCES_SDK_INT);
}
@Test
public void setUpApplicationState_setsVersionQualifierFromSdkWithOtherQualifiers() {
String givenQualifiers = "large-land";
ConfigurationImpl config = new ConfigurationImpl();
config.put(Config.class, new Config.Builder().setQualifiers(givenQualifiers).build());
config.put(LooperMode.Mode.class, LEGACY);
bootstrapWrapper.changeConfig(config);
bootstrapWrapper.callSetUpApplicationState();
String optsForO = RuntimeEnvironment.getApiLevel() >= O
? "nowidecg-lowdr-"
: "";
assertThat(RuntimeEnvironment.getQualifiers())
.contains("large-notlong-notround-" + optsForO + "land-notnight-mdpi-finger-keyssoft"
+ "-nokeys-navhidden-nonav-v"
+ Build.VERSION.RESOURCES_SDK_INT);
}
@Test
public void setUpApplicationState_shouldCreateStorageDirs() {
bootstrapWrapper.callSetUpApplicationState();
ApplicationInfo applicationInfo = ApplicationProvider.getApplicationContext()
.getApplicationInfo();
assertThat(applicationInfo.sourceDir).isNotNull();
assertThat(new File(applicationInfo.sourceDir).exists()).isTrue();
assertThat(applicationInfo.publicSourceDir).isNotNull();
assertThat(new File(applicationInfo.publicSourceDir).exists()).isTrue();
assertThat(applicationInfo.dataDir).isNotNull();
assertThat(new File(applicationInfo.dataDir).isDirectory()).isTrue();
}
@Test
@Config(minSdk = Build.VERSION_CODES.N)
public void setUpApplicationState_shouldCreateStorageDirs_Nplus() {
bootstrapWrapper.callSetUpApplicationState();
ApplicationInfo applicationInfo = ApplicationProvider.getApplicationContext()
.getApplicationInfo();
assertThat(applicationInfo.credentialProtectedDataDir).isNotNull();
assertThat(new File(applicationInfo.credentialProtectedDataDir).isDirectory()).isTrue();
assertThat(applicationInfo.deviceProtectedDataDir).isNotNull();
assertThat(new File(applicationInfo.deviceProtectedDataDir).isDirectory()).isTrue();
}
@Test
public void tearDownApplication_invokesOnTerminate() {
List<String> events = new ArrayList<>();
RuntimeEnvironment.application =
new Application() {
@Override
public void onTerminate() {
super.onTerminate();
events.add("terminated");
}
};
bootstrapWrapper.tearDownApplication();
assertThat(events).containsExactly("terminated");
}
@Test
public void testResourceNotFound() {
// not relevant for binary resources mode
assumeTrue(bootstrapWrapper.isLegacyResources());
try {
bootstrapWrapper.changeAppManifest(new ThrowingManifest(bootstrapWrapper.getAppManifest()));
bootstrapWrapper.callSetUpApplicationState();
fail("Expected to throw");
} catch (Resources.NotFoundException expected) {
// expected
}
}
/** Can't use Mockito for classloader issues */
static class ThrowingManifest extends AndroidManifest {
public ThrowingManifest(AndroidManifest androidManifest) {
super(
androidManifest.getAndroidManifestFile(),
androidManifest.getResDirectory(),
androidManifest.getAssetsDirectory(),
androidManifest.getLibraryManifests(),
null,
androidManifest.getApkFile());
}
@Override
public void initMetaData(ResourceTable resourceTable) throws RoboNotFoundException {
throw new RoboNotFoundException("This is just a test");
}
}
@Test
@Config(qualifiers = "b+fr+Cyrl+UK")
public void localeIsSet() {
bootstrapWrapper.callSetUpApplicationState();
assertThat(Locale.getDefault().getLanguage()).isEqualTo("fr");
assertThat(Locale.getDefault().getScript()).isEqualTo("Cyrl");
assertThat(Locale.getDefault().getCountry()).isEqualTo("UK");
}
@Test
@Config(qualifiers = "w123dp-h456dp")
public void whenNotPrefixedWithPlus_setQualifiers_shouldNotBeBasedOnPreviousConfig() {
bootstrapWrapper.callSetUpApplicationState();
RuntimeEnvironment.setQualifiers("land");
assertThat(RuntimeEnvironment.getQualifiers()).contains("w470dp-h320dp");
assertThat(RuntimeEnvironment.getQualifiers()).contains("-land-");
}
@Test
@Config(qualifiers = "w100dp-h125dp")
public void whenDimensAndSizeSpecified_setQualifiers_should() {
bootstrapWrapper.callSetUpApplicationState();
RuntimeEnvironment.setQualifiers("+xlarge");
Configuration configuration = Resources.getSystem().getConfiguration();
assertThat(configuration.screenWidthDp).isEqualTo(ScreenSize.xlarge.width);
assertThat(configuration.screenHeightDp).isEqualTo(ScreenSize.xlarge.height);
assertThat(DeviceConfig.getScreenSize(configuration)).isEqualTo(ScreenSize.xlarge);
}
@Test
@Config(qualifiers = "w123dp-h456dp")
public void whenPrefixedWithPlus_setQualifiers_shouldBeBasedOnPreviousConfig() {
bootstrapWrapper.callSetUpApplicationState();
RuntimeEnvironment.setQualifiers("+w124dp");
assertThat(RuntimeEnvironment.getQualifiers()).contains("w124dp-h456dp");
}
@Test
@Config(fontScale = 1.3f)
public void setFontScale_updatesFontScale() {
bootstrapWrapper.callSetUpApplicationState();
Context context = ApplicationProvider.getApplicationContext();
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
assertThat(context.getResources().getConfiguration().fontScale).isEqualTo(1.3f);
assertThat(displayMetrics.scaledDensity).isEqualTo(displayMetrics.density * 1.3f);
}
@Test
public void fontScaleNotSet_stillSetToDefault() {
bootstrapWrapper.callSetUpApplicationState();
Context context = ApplicationProvider.getApplicationContext();
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
assertThat(context.getResources().getConfiguration().fontScale).isEqualTo(1.0f);
assertThat(displayMetrics.scaledDensity).isEqualTo(displayMetrics.density);
}
@LazyApplication(LazyLoad.ON)
@Test
public void resetState_doesNotLoadApplication() {
RuntimeEnvironment.application = null;
assertThat(RuntimeEnvironment.application).isNull();
bootstrapWrapper.resetState();
assertThat(RuntimeEnvironment.application).isNull();
}
@LazyApplication(LazyLoad.ON)
@Test
public void tearDownApplication_doesNotLoadApplication() {
bootstrapWrapper.callSetUpApplicationState();
RuntimeEnvironment.application = null;
bootstrapWrapper.tearDownApplication();
assertThat(RuntimeEnvironment.application).isNull();
}
@LazyApplication(LazyLoad.ON)
@Test
@Config(qualifiers = "w480dp-h640dp-land-hdpi")
public void systemResources_getDisplayMetrics_correctValues() {
bootstrapWrapper.callSetUpApplicationState();
DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
assertThat(displayMetrics.densityDpi).isEqualTo(DisplayMetrics.DENSITY_HIGH);
assertThat(RuntimeEnvironment.getQualifiers()).contains("w640dp-h480dp");
assertThat(RuntimeEnvironment.getQualifiers()).contains("land");
}
}