1/* 2 * Copyright (C) 2009 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 17package vogar.target; 18 19import java.io.File; 20import java.lang.reflect.Field; 21import java.lang.reflect.Modifier; 22import java.net.Authenticator; 23import java.net.CookieHandler; 24import java.net.ResponseCache; 25import java.text.DateFormat; 26import java.util.Locale; 27import java.util.HashMap; 28import java.util.TimeZone; 29import java.util.logging.ConsoleHandler; 30import java.util.logging.LogManager; 31import java.util.logging.Logger; 32import java.util.prefs.BackingStoreException; 33import java.util.prefs.Preferences; 34import javax.net.ssl.HostnameVerifier; 35import javax.net.ssl.HttpsURLConnection; 36import javax.net.ssl.SSLSocketFactory; 37import vogar.util.IoUtils; 38 39/** 40 * This class resets the VM to a relatively pristine state. Useful to defend 41 * against tests that muck with system properties and other global state. 42 */ 43public final class TestEnvironment { 44 45 private final HostnameVerifier defaultHostnameVerifier; 46 private final SSLSocketFactory defaultSSLSocketFactory; 47 48 /** The DateFormat.is24Hour field. Not present on older versions of Android or the RI. */ 49 private static final Field dateFormatIs24HourField; 50 static { 51 Field f; 52 try { 53 Class<?> dateFormatClass = Class.forName("java.text.DateFormat"); 54 f = dateFormatClass.getDeclaredField("is24Hour"); 55 } catch (ClassNotFoundException | NoSuchFieldException e) { 56 f = null; 57 } 58 dateFormatIs24HourField = f; 59 } 60 private final Boolean defaultDateFormatIs24Hour; 61 62 private static final String JAVA_RUNTIME_VERSION = System.getProperty("java.runtime.version"); 63 private static final String JAVA_VM_INFO = System.getProperty("java.vm.info"); 64 private static final String JAVA_VM_VERSION = System.getProperty("java.vm.version"); 65 private static final String JAVA_VM_VENDOR = System.getProperty("java.vm.vendor"); 66 private static final String JAVA_VM_NAME = System.getProperty("java.vm.name"); 67 68 private final String tmpDir; 69 70 public TestEnvironment() { 71 this.tmpDir = System.getProperty("java.io.tmpdir"); 72 if (tmpDir == null || tmpDir.length() == 0) { 73 throw new AssertionError("tmpDir is null or empty: " + tmpDir); 74 } 75 System.setProperties(null); // Reset. 76 77 // From "L" release onwards, calling System.setProperties(null) clears the java.io.tmpdir, 78 // so we set it again. No-op on earlier releases. 79 System.setProperty("java.io.tmpdir", tmpDir); 80 81 String userHome = System.getProperty("user.home"); 82 String userDir = System.getProperty("user.dir"); 83 if (userHome == null || userDir == null) { 84 throw new NullPointerException("user.home=" + userHome + ", user.dir=" + userDir); 85 } 86 87 defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier(); 88 defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory(); 89 defaultDateFormatIs24Hour = hasDateFormatIs24Hour() ? getDateFormatIs24Hour() : null; 90 91 disableSecurity(); 92 } 93 94 public void reset() { 95 // Reset system properties. 96 System.setProperties(null); 97 98 // From "L" release onwards, calling System.setProperties(null) clears the java.io.tmpdir, 99 // so we set it again. No-op on earlier releases. 100 System.setProperty("java.io.tmpdir", tmpDir); 101 102 if (JAVA_RUNTIME_VERSION != null) { 103 System.setProperty("java.runtime.version", JAVA_RUNTIME_VERSION); 104 } 105 if (JAVA_VM_INFO != null) { 106 System.setProperty("java.vm.info", JAVA_VM_INFO); 107 } 108 if (JAVA_VM_VERSION != null) { 109 System.setProperty("java.vm.version", JAVA_VM_VERSION); 110 } 111 if (JAVA_VM_VENDOR != null) { 112 System.setProperty("java.vm.vendor", JAVA_VM_VENDOR); 113 } 114 if (JAVA_VM_NAME != null) { 115 System.setProperty("java.vm.name", JAVA_VM_NAME); 116 } 117 118 // Require writable java.home and user.dir directories for preferences 119 if ("Dalvik".equals(System.getProperty("java.vm.name"))) { 120 String javaHome = tmpDir + "/java.home"; 121 IoUtils.safeMkdirs(new File(javaHome)); 122 System.setProperty("java.home", javaHome); 123 } 124 String userHome = System.getProperty("user.home"); 125 if (userHome.length() == 0) { 126 userHome = tmpDir + "/user.home"; 127 IoUtils.safeMkdirs(new File(userHome)); 128 System.setProperty("user.home", userHome); 129 } 130 131 // Localization 132 Locale.setDefault(Locale.US); 133 TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles")); 134 if (hasDateFormatIs24Hour()) { 135 setDateFormatIs24Hour(defaultDateFormatIs24Hour); 136 } 137 138 // Preferences 139 // Temporarily silence the java.util.prefs logger, which otherwise emits 140 // an unactionable warning. See RI bug 4751540. 141 Logger loggerToMute = Logger.getLogger("java.util.prefs"); 142 boolean usedParentHandlers = loggerToMute.getUseParentHandlers(); 143 loggerToMute.setUseParentHandlers(false); 144 try { 145 // resetPreferences(Preferences.systemRoot()); 146 resetPreferences(Preferences.userRoot()); 147 } finally { 148 loggerToMute.setUseParentHandlers(usedParentHandlers); 149 } 150 151 // HttpURLConnection 152 Authenticator.setDefault(null); 153 CookieHandler.setDefault(null); 154 ResponseCache.setDefault(null); 155 HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier); 156 HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory); 157 158 // Logging 159 LogManager.getLogManager().reset(); 160 Logger.getLogger("").addHandler(new ConsoleHandler()); 161 162 // Cleanup to force CloseGuard warnings etc 163 System.gc(); 164 System.runFinalization(); 165 } 166 167 private static void resetPreferences(Preferences root) { 168 try { 169 root.sync(); 170 } catch (BackingStoreException e) { 171 // Indicates that Preferences is probably not working. It's not really supported on 172 // Android so ignore. 173 return; 174 } 175 176 try { 177 for (String child : root.childrenNames()) { 178 root.node(child).removeNode(); 179 } 180 root.clear(); 181 root.flush(); 182 } catch (BackingStoreException e) { 183 throw new RuntimeException(e); 184 } 185 } 186 187 /** A class that always returns TRUE. */ 188 @SuppressWarnings("serial") 189 public static class LyingMap extends HashMap<Object, Boolean> { 190 @Override 191 public Boolean get(Object key) { 192 return Boolean.TRUE; 193 } 194 } 195 196 /** 197 * Does what is necessary to disable security checks for testing security-related classes. 198 */ 199 @SuppressWarnings("unchecked") 200 private static void disableSecurity() { 201 try { 202 Class<?> securityBrokerClass = Class.forName("javax.crypto.JceSecurity"); 203 204 Field modifiersField = Field.class.getDeclaredField("modifiers"); 205 modifiersField.setAccessible(true); 206 207 Field verifyMapField = securityBrokerClass.getDeclaredField("verificationResults"); 208 modifiersField.setInt(verifyMapField, verifyMapField.getModifiers() & ~Modifier.FINAL); 209 verifyMapField.setAccessible(true); 210 verifyMapField.set(null, new LyingMap()); 211 212 Field restrictedField = securityBrokerClass.getDeclaredField("isRestricted"); 213 restrictedField.setAccessible(true); 214 restrictedField.set(null, Boolean.FALSE); 215 } catch (Exception ignored) { 216 } 217 } 218 219 private static boolean hasDateFormatIs24Hour() { 220 return dateFormatIs24HourField != null; 221 } 222 223 private static Boolean getDateFormatIs24Hour() { 224 try { 225 return (Boolean) dateFormatIs24HourField.get(null); 226 } catch (IllegalAccessException e) { 227 Error e2 = new AssertionError("Unable to get java.text.DateFormat.is24Hour"); 228 e2.initCause(e); 229 throw e2; 230 } 231 } 232 233 private static void setDateFormatIs24Hour(Boolean value) { 234 try { 235 dateFormatIs24HourField.set(null, value); 236 } catch (IllegalAccessException e) { 237 Error e2 = new AssertionError("Unable to set java.text.DateFormat.is24Hour"); 238 e2.initCause(e); 239 throw e2; 240 } 241 } 242} 243