1 /* 2 * Copyright (C) 2023 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 17 package android.platform.test.ravenwood; 18 19 import static com.android.ravenwood.common.RavenwoodCommonUtils.log; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.app.Instrumentation; 24 import android.content.Context; 25 import android.platform.test.annotations.DisabledOnRavenwood; 26 27 import androidx.test.platform.app.InstrumentationRegistry; 28 29 import com.android.ravenwood.common.RavenwoodCommonUtils; 30 31 import org.junit.rules.TestRule; 32 import org.junit.runner.Description; 33 import org.junit.runners.model.Statement; 34 35 import java.util.Objects; 36 import java.util.regex.Pattern; 37 38 /** 39 * Reach out to g/ravenwood if you need any features in it. 40 */ 41 public final class RavenwoodRule implements TestRule { 42 private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG; 43 44 static final boolean IS_ON_RAVENWOOD = RavenwoodCommonUtils.isOnRavenwood(); 45 46 /** 47 * When this flag is enabled, all tests will be unconditionally run on Ravenwood to detect 48 * cases where a test is able to pass despite being marked as {@link DisabledOnRavenwood}. 49 * 50 * This is typically helpful for internal maintainers discovering tests that had previously 51 * been ignored, but now have enough Ravenwood-supported functionality to be enabled. 52 */ 53 private static final boolean RUN_DISABLED_TESTS = "1".equals( 54 System.getenv("RAVENWOOD_RUN_DISABLED_TESTS")); 55 56 /** 57 * When using ENABLE_PROBE_IGNORED, you may still want to skip certain tests, 58 * for example because the test would crash the JVM. 59 * 60 * This regex defines the tests that should still be disabled even if ENABLE_PROBE_IGNORED 61 * is set. 62 * 63 * Before running each test class and method, we check if this pattern can be found in 64 * the full test name (either [class full name], or [class full name] + "#" + [method name]), 65 * and if so, we skip it. 66 * 67 * For example, if you want to skip an entire test class, use: 68 * RAVENWOOD_REALLY_DISABLE='\.CustomTileDefaultsRepositoryTest$' 69 * 70 * For example, if you want to skip an entire test class, use: 71 * RAVENWOOD_REALLY_DISABLE='\.CustomTileDefaultsRepositoryTest#testSimple$' 72 * 73 * To ignore multiple classes, use (...|...), for example: 74 * RAVENWOOD_REALLY_DISABLE='\.(ClassA|ClassB)$' 75 * 76 * Because we use a regex-find, setting "." would disable all tests. 77 */ 78 private static final Pattern REALLY_DISABLED_PATTERN = Pattern.compile( 79 Objects.requireNonNullElse(System.getenv("RAVENWOOD_REALLY_DISABLED"), "")); 80 81 private static final boolean HAS_REALLY_DISABLE_PATTERN = 82 !REALLY_DISABLED_PATTERN.pattern().isEmpty(); 83 84 static { 85 if (RUN_DISABLED_TESTS) { log(TAG, "$RAVENWOOD_RUN_DISABLED_TESTS enabled: force running all tests")86 log(TAG, "$RAVENWOOD_RUN_DISABLED_TESTS enabled: force running all tests"); 87 if (HAS_REALLY_DISABLE_PATTERN) { log(TAG, "$RAVENWOOD_REALLY_DISABLED=" + REALLY_DISABLED_PATTERN.pattern())88 log(TAG, "$RAVENWOOD_REALLY_DISABLED=" + REALLY_DISABLED_PATTERN.pattern()); 89 } 90 } 91 } 92 93 final RavenwoodTestProperties mProperties = new RavenwoodTestProperties(); 94 95 public static class Builder { 96 97 private final RavenwoodRule mRule = new RavenwoodRule(); 98 Builder()99 public Builder() { 100 } 101 102 /** 103 * @deprecated no longer used. We always use an app UID. 104 */ 105 @Deprecated setProcessSystem()106 public Builder setProcessSystem() { 107 return this; 108 } 109 110 /** 111 * @deprecated no longer used. We always use an app UID. 112 */ 113 @Deprecated setProcessApp()114 public Builder setProcessApp() { 115 return this; 116 } 117 118 /** 119 * @deprecated no longer used. 120 */ 121 @Deprecated setPackageName(@onNull String packageName)122 public Builder setPackageName(@NonNull String packageName) { 123 return this; 124 } 125 126 /** 127 * @deprecated no longer used. Main thread is always available. 128 */ 129 @Deprecated setProvideMainThread(boolean provideMainThread)130 public Builder setProvideMainThread(boolean provideMainThread) { 131 return this; 132 } 133 134 /** 135 * Configure the given system property as immutable for the duration of the test. 136 * Read access to the key is allowed, and write access will fail. When {@code value} is 137 * {@code null}, the value is left as undefined. 138 * 139 * All properties in the {@code debug.*} namespace are automatically mutable, with no 140 * developer action required. 141 * 142 * Has no effect on non-Ravenwood environments. 143 */ setSystemPropertyImmutable(@onNull String key, @Nullable Object value)144 public Builder setSystemPropertyImmutable(@NonNull String key, @Nullable Object value) { 145 mRule.mProperties.setValue(key, value); 146 mRule.mProperties.setAccessReadOnly(key); 147 return this; 148 } 149 150 /** 151 * Configure the given system property as mutable for the duration of the test. 152 * Both read and write access to the key is allowed, and its value will be reset between 153 * each test. When {@code value} is {@code null}, the value is left as undefined. 154 * 155 * All properties in the {@code debug.*} namespace are automatically mutable, with no 156 * developer action required. 157 * 158 * Has no effect on non-Ravenwood environments. 159 */ setSystemPropertyMutable(@onNull String key, @Nullable Object value)160 public Builder setSystemPropertyMutable(@NonNull String key, @Nullable Object value) { 161 mRule.mProperties.setValue(key, value); 162 mRule.mProperties.setAccessReadWrite(key); 163 return this; 164 } 165 166 /** 167 * @deprecated no longer used. All supported services are available. 168 */ 169 @Deprecated setServicesRequired(@onNull Class<?>.... services)170 public Builder setServicesRequired(@NonNull Class<?>... services) { 171 return this; 172 } 173 build()174 public RavenwoodRule build() { 175 return mRule; 176 } 177 } 178 179 /** 180 * @deprecated replaced by {@link #isOnRavenwood()} 181 */ 182 @Deprecated isUnderRavenwood()183 public static boolean isUnderRavenwood() { 184 return IS_ON_RAVENWOOD; 185 } 186 187 /** 188 * Return if the current process is running on a Ravenwood test environment. 189 */ isOnRavenwood()190 public static boolean isOnRavenwood() { 191 return IS_ON_RAVENWOOD; 192 } 193 ensureOnRavenwood(String featureName)194 private static void ensureOnRavenwood(String featureName) { 195 if (!IS_ON_RAVENWOOD) { 196 throw new RuntimeException(featureName + " is only supported on Ravenwood."); 197 } 198 } 199 200 /** 201 * @deprecated Use 202 * {@code androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().getContext()} 203 * instead. 204 */ 205 @Deprecated getContext()206 public Context getContext() { 207 return InstrumentationRegistry.getInstrumentation().getContext(); 208 } 209 210 /** 211 * @deprecated Use 212 * {@code androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()} 213 * instead. 214 */ 215 @Deprecated getInstrumentation()216 public Instrumentation getInstrumentation() { 217 return InstrumentationRegistry.getInstrumentation(); 218 } 219 220 @Override apply(Statement base, Description description)221 public Statement apply(Statement base, Description description) { 222 if (!IS_ON_RAVENWOOD) { 223 return base; 224 } 225 return new Statement() { 226 @Override 227 public void evaluate() throws Throwable { 228 RavenwoodAwareTestRunner.onRavenwoodRuleEnter(description, RavenwoodRule.this); 229 try { 230 base.evaluate(); 231 } finally { 232 RavenwoodAwareTestRunner.onRavenwoodRuleExit(description, RavenwoodRule.this); 233 } 234 } 235 }; 236 } 237 238 /** 239 * Returns the "real" result from {@link System#currentTimeMillis()}. 240 * 241 * Currently, it's the same thing as calling {@link System#currentTimeMillis()}, 242 * but this one is guaranteeed to return the real value, even when Ravenwood supports 243 * injecting a time to{@link System#currentTimeMillis()}. 244 */ 245 public long realCurrentTimeMillis() { 246 return System.currentTimeMillis(); 247 } 248 249 /** 250 * Equivalent to setting the ANDROID_LOG_TAGS environmental variable. 251 * 252 * See https://developer.android.com/tools/logcat#filteringOutput for the string format. 253 * 254 * NOTE: this works only on Ravenwood. 255 */ 256 public static void setAndroidLogTags(@Nullable String androidLogTags) { 257 ensureOnRavenwood("RavenwoodRule.setAndroidLogTags()"); 258 try { 259 Class<?> logRavenwoodClazz = Class.forName("android.util.Log_ravenwood"); 260 var setter = logRavenwoodClazz.getMethod("setLogLevels", String.class); 261 setter.invoke(null, androidLogTags); 262 } catch (ReflectiveOperationException e) { 263 throw new RuntimeException(e); 264 } 265 } 266 267 /** 268 * Set a log level for a given tag. Pass NULL to {@code tag} to change the default level. 269 * 270 * NOTE: this works only on Ravenwood. 271 */ 272 public static void setLogLevel(@Nullable String tag, int level) { 273 ensureOnRavenwood("RavenwoodRule.setLogLevel()"); 274 try { 275 Class<?> logRavenwoodClazz = Class.forName("android.util.Log_ravenwood"); 276 var setter = logRavenwoodClazz.getMethod("setLogLevel", String.class, int.class); 277 setter.invoke(null, tag, level); 278 } catch (ReflectiveOperationException e) { 279 throw new RuntimeException(e); 280 } 281 } 282 283 // Below are internal to ravenwood. Don't use them from normal tests... 284 285 public static class RavenwoodPrivate { 286 private RavenwoodPrivate() { 287 } 288 289 private volatile Boolean mRunDisabledTestsOverride = null; 290 291 private volatile Pattern mReallyDisabledPattern = null; 292 293 public boolean isRunningDisabledTests() { 294 if (mRunDisabledTestsOverride != null) { 295 return mRunDisabledTestsOverride; 296 } 297 return RUN_DISABLED_TESTS; 298 } 299 300 public Pattern getReallyDisabledPattern() { 301 if (mReallyDisabledPattern != null) { 302 return mReallyDisabledPattern; 303 } 304 return REALLY_DISABLED_PATTERN; 305 } 306 307 public void overrideRunDisabledTest(boolean runDisabledTests, 308 @Nullable String reallyDisabledPattern) { 309 mRunDisabledTestsOverride = runDisabledTests; 310 mReallyDisabledPattern = 311 reallyDisabledPattern == null ? null : Pattern.compile(reallyDisabledPattern); 312 } 313 314 public void resetRunDisabledTest() { 315 mRunDisabledTestsOverride = null; 316 mReallyDisabledPattern = null; 317 } 318 } 319 320 private static final RavenwoodPrivate sRavenwoodPrivate = new RavenwoodPrivate(); 321 322 public static RavenwoodPrivate private$ravenwood() { 323 return sRavenwoodPrivate; 324 } 325 } 326