blob: f0dc866f41f8a4e2fd7a68bb0c75ae4808f23f4b [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package vogar.target;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.Authenticator;
import java.net.CookieHandler;
import java.net.ResponseCache;
import java.text.DateFormat;
import java.util.Locale;
import java.util.HashMap;
import java.util.TimeZone;
import java.util.logging.ConsoleHandler;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
import vogar.util.IoUtils;
/**
* This class resets the VM to a relatively pristine state. Useful to defend
* against tests that muck with system properties and other global state.
*/
public final class TestEnvironment {
private final HostnameVerifier defaultHostnameVerifier;
private final SSLSocketFactory defaultSSLSocketFactory;
/** The DateFormat.is24Hour field. Not present on older versions of Android or the RI. */
private static final Field dateFormatIs24HourField;
static {
Field f;
try {
Class<?> dateFormatClass = Class.forName("java.text.DateFormat");
f = dateFormatClass.getDeclaredField("is24Hour");
} catch (ClassNotFoundException | NoSuchFieldException e) {
f = null;
}
dateFormatIs24HourField = f;
}
private final Boolean defaultDateFormatIs24Hour;
private static final String JAVA_RUNTIME_VERSION = System.getProperty("java.runtime.version");
private static final String JAVA_VM_INFO = System.getProperty("java.vm.info");
private static final String JAVA_VM_VERSION = System.getProperty("java.vm.version");
private static final String JAVA_VM_VENDOR = System.getProperty("java.vm.vendor");
private static final String JAVA_VM_NAME = System.getProperty("java.vm.name");
private final String tmpDir;
public TestEnvironment() {
this.tmpDir = System.getProperty("java.io.tmpdir");
if (tmpDir == null || tmpDir.length() == 0) {
throw new AssertionError("tmpDir is null or empty: " + tmpDir);
}
System.setProperties(null); // Reset.
// On android, behaviour around clearing "java.io.tmpdir" is inconsistent.
// Some release set it to "null" and others set it to "/tmp" both values are
// wrong for normal apps (mode=activity), where the value that the framework
// sets must be used. We unconditionally restore that value here. This code
// should be correct on the host and on the jvm too, since tmpdir is assumed
// to be immutable.
System.setProperty("java.io.tmpdir", tmpDir);
String userHome = System.getProperty("user.home");
String userDir = System.getProperty("user.dir");
if (userHome == null || userDir == null) {
throw new NullPointerException("user.home=" + userHome + ", user.dir=" + userDir);
}
defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
defaultDateFormatIs24Hour = hasDateFormatIs24Hour() ? getDateFormatIs24Hour() : null;
disableSecurity();
}
private String createTempDirectory(String subDirName) {
String dirName = tmpDir + "/" + subDirName;
IoUtils.safeMkdirs(new File(dirName));
return dirName;
}
public void reset() {
// Reset system properties.
System.setProperties(null);
// On android, behaviour around clearing "java.io.tmpdir" is inconsistent.
// Some release set it to "null" and others set it to "/tmp" both values are
// wrong for normal apps (mode=activity), where the value that the framework
// sets must be used. We unconditionally restore that value here. This code
// should be correct on the host and on the jvm too, since tmpdir is assumed
// to be immutable.
System.setProperty("java.io.tmpdir", tmpDir);
// Require writable java.home and user.dir directories for preferences
if ("Dalvik".equals(System.getProperty("java.vm.name"))) {
setPropertyIfNull("java.home", createTempDirectory("java.home"));
setPropertyIfNull("dexmaker.dexcache", createTempDirectory("dexmaker.dexcache"));
} else {
// The mode --jvm has these properties writable.
if (JAVA_RUNTIME_VERSION != null) {
System.setProperty("java.runtime.version", JAVA_RUNTIME_VERSION);
}
if (JAVA_VM_INFO != null) {
System.setProperty("java.vm.info", JAVA_VM_INFO);
}
if (JAVA_VM_VERSION != null) {
System.setProperty("java.vm.version", JAVA_VM_VERSION);
}
if (JAVA_VM_VENDOR != null) {
System.setProperty("java.vm.vendor", JAVA_VM_VENDOR);
}
if (JAVA_VM_NAME != null) {
System.setProperty("java.vm.name", JAVA_VM_NAME);
}
}
String userHome = System.getProperty("user.home");
if (userHome.length() == 0) {
userHome = tmpDir + "/user.home";
IoUtils.safeMkdirs(new File(userHome));
System.setProperty("user.home", userHome);
}
// Localization
Locale.setDefault(Locale.US);
TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
if (hasDateFormatIs24Hour()) {
setDateFormatIs24Hour(defaultDateFormatIs24Hour);
}
// Preferences
// Temporarily silence the java.util.prefs logger, which otherwise emits
// an unactionable warning. See RI bug 4751540.
Logger loggerToMute = Logger.getLogger("java.util.prefs");
boolean usedParentHandlers = loggerToMute.getUseParentHandlers();
loggerToMute.setUseParentHandlers(false);
try {
// resetPreferences(Preferences.systemRoot());
resetPreferences(Preferences.userRoot());
} finally {
loggerToMute.setUseParentHandlers(usedParentHandlers);
}
// HttpURLConnection
Authenticator.setDefault(null);
CookieHandler.setDefault(null);
ResponseCache.setDefault(null);
HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier);
HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory);
// Logging
LogManager.getLogManager().reset();
Logger.getLogger("").addHandler(new ConsoleHandler());
// Cleanup to force CloseGuard warnings etc
System.gc();
System.runFinalization();
}
private static void resetPreferences(Preferences root) {
try {
root.sync();
} catch (BackingStoreException e) {
// Indicates that Preferences is probably not working. It's not really supported on
// Android so ignore.
return;
}
try {
for (String child : root.childrenNames()) {
root.node(child).removeNode();
}
root.clear();
root.flush();
} catch (BackingStoreException e) {
throw new RuntimeException(e);
}
}
/** A class that always returns TRUE. */
@SuppressWarnings("serial")
public static class LyingMap extends HashMap<Object, Boolean> {
@Override
public Boolean get(Object key) {
return Boolean.TRUE;
}
}
/**
* Does what is necessary to disable security checks for testing security-related classes.
*/
@SuppressWarnings("unchecked")
private static void disableSecurity() {
try {
Class<?> securityBrokerClass = Class.forName("javax.crypto.JceSecurity");
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
Field verifyMapField = securityBrokerClass.getDeclaredField("verificationResults");
modifiersField.setInt(verifyMapField, verifyMapField.getModifiers() & ~Modifier.FINAL);
verifyMapField.setAccessible(true);
verifyMapField.set(null, new LyingMap());
Field restrictedField = securityBrokerClass.getDeclaredField("isRestricted");
restrictedField.setAccessible(true);
restrictedField.set(null, Boolean.FALSE);
} catch (Exception ignored) {
}
}
private static boolean hasDateFormatIs24Hour() {
return dateFormatIs24HourField != null;
}
private static Boolean getDateFormatIs24Hour() {
try {
return (Boolean) dateFormatIs24HourField.get(null);
} catch (IllegalAccessException e) {
Error e2 = new AssertionError("Unable to get java.text.DateFormat.is24Hour");
e2.initCause(e);
throw e2;
}
}
private static void setDateFormatIs24Hour(Boolean value) {
try {
dateFormatIs24HourField.set(null, value);
} catch (IllegalAccessException e) {
Error e2 = new AssertionError("Unable to set java.text.DateFormat.is24Hour");
e2.initCause(e);
throw e2;
}
}
private static void setPropertyIfNull(String property, String value) {
if (System.getProperty(property) == null) {
System.setProperty(property, value);
}
}
}