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 android.os.Process.FIRST_APPLICATION_UID; 20 import static android.os.Process.SYSTEM_UID; 21 import static android.os.UserHandle.USER_SYSTEM; 22 23 import static org.junit.Assert.fail; 24 25 import android.app.Instrumentation; 26 import android.content.Context; 27 import android.platform.test.annotations.DisabledOnNonRavenwood; 28 import android.platform.test.annotations.DisabledOnRavenwood; 29 import android.platform.test.annotations.EnabledOnRavenwood; 30 import android.platform.test.annotations.IgnoreUnderRavenwood; 31 32 import com.android.ravenwood.common.RavenwoodCommonUtils; 33 34 import org.junit.Assume; 35 import org.junit.rules.TestRule; 36 import org.junit.runner.Description; 37 import org.junit.runners.model.Statement; 38 39 import java.util.ArrayList; 40 import java.util.List; 41 import java.util.Objects; 42 import java.util.concurrent.atomic.AtomicInteger; 43 import java.util.regex.Pattern; 44 45 /** 46 * {@code @Rule} that configures the Ravenwood test environment. This rule has no effect when 47 * tests are run on non-Ravenwood test environments. 48 * 49 * This rule initializes and resets the Ravenwood environment between each test method to offer a 50 * hermetic testing environment. 51 * 52 * By default, all tests are executed on Ravenwood, but annotations such as 53 * {@link DisabledOnRavenwood} and {@link EnabledOnRavenwood} can be used at both the method 54 * and class level to "ignore" tests that may not be ready. When needed, a 55 * {@link RavenwoodClassRule} can be used in addition to a {@link RavenwoodRule} to ignore tests 56 * before a test class is fully initialized. 57 */ 58 public class RavenwoodRule implements TestRule { 59 static final boolean IS_ON_RAVENWOOD = RavenwoodCommonUtils.isOnRavenwood(); 60 61 /** 62 * When probing is enabled, all tests will be unconditionally run on Ravenwood to detect 63 * cases where a test is able to pass despite being marked as {@code IgnoreUnderRavenwood}. 64 * 65 * This is typically helpful for internal maintainers discovering tests that had previously 66 * been ignored, but now have enough Ravenwood-supported functionality to be enabled. 67 */ 68 static final boolean ENABLE_PROBE_IGNORED = "1".equals( 69 System.getenv("RAVENWOOD_RUN_DISABLED_TESTS")); 70 71 /** 72 * When using ENABLE_PROBE_IGNORED, you may still want to skip certain tests, 73 * for example because the test would crash the JVM. 74 * 75 * This regex defines the tests that should still be disabled even if ENABLE_PROBE_IGNORED 76 * is set. 77 * 78 * Before running each test class and method, we check if this pattern can be found in 79 * the full test name (either [class full name], or [class full name] + "#" + [method name]), 80 * and if so, we skip it. 81 * 82 * For example, if you want to skip an entire test class, use: 83 * RAVENWOOD_REALLY_DISABLE='\.CustomTileDefaultsRepositoryTest$' 84 * 85 * For example, if you want to skip an entire test class, use: 86 * RAVENWOOD_REALLY_DISABLE='\.CustomTileDefaultsRepositoryTest#testSimple$' 87 * 88 * To ignore multiple classes, use (...|...), for example: 89 * RAVENWOOD_REALLY_DISABLE='\.(ClassA|ClassB)$' 90 * 91 * Because we use a regex-find, setting "." would disable all tests. 92 */ 93 private static final Pattern REALLY_DISABLE_PATTERN = Pattern.compile( 94 Objects.requireNonNullElse(System.getenv("RAVENWOOD_REALLY_DISABLE"), "")); 95 96 private static final boolean ENABLE_REALLY_DISABLE_PATTERN = 97 !REALLY_DISABLE_PATTERN.pattern().isEmpty(); 98 99 /** 100 * If true, enable optional validation on running tests. 101 */ 102 private static final boolean ENABLE_OPTIONAL_VALIDATION = "1".equals( 103 System.getenv("RAVENWOOD_OPTIONAL_VALIDATION")); 104 105 static { 106 if (ENABLE_PROBE_IGNORED) { 107 System.out.println("$RAVENWOOD_RUN_DISABLED_TESTS enabled: force running all tests"); 108 if (ENABLE_REALLY_DISABLE_PATTERN) { 109 System.out.println("$RAVENWOOD_REALLY_DISABLE=" + REALLY_DISABLE_PATTERN.pattern()); 110 } 111 } 112 } 113 114 private static final int NOBODY_UID = 9999; 115 116 private static final AtomicInteger sNextPid = new AtomicInteger(100); 117 118 int mCurrentUser = USER_SYSTEM; 119 120 /** 121 * Unless the test author requests differently, run as "nobody", and give each collection of 122 * tests its own unique PID. 123 */ 124 int mUid = NOBODY_UID; 125 int mPid = sNextPid.getAndIncrement(); 126 127 String mPackageName; 128 129 boolean mProvideMainThread = false; 130 131 final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties(); 132 133 final List<Class<?>> mServicesRequired = new ArrayList<>(); 134 135 volatile Context mContext; 136 volatile Instrumentation mInstrumentation; 137 RavenwoodRule()138 public RavenwoodRule() { 139 } 140 141 public static class Builder { 142 private RavenwoodRule mRule = new RavenwoodRule(); 143 Builder()144 public Builder() { 145 } 146 147 /** 148 * Configure the identity of this process to be the system UID for the duration of the 149 * test. Has no effect on non-Ravenwood environments. 150 */ setProcessSystem()151 public Builder setProcessSystem() { 152 mRule.mUid = SYSTEM_UID; 153 return this; 154 } 155 156 /** 157 * Configure the identity of this process to be an app UID for the duration of the 158 * test. Has no effect on non-Ravenwood environments. 159 */ setProcessApp()160 public Builder setProcessApp() { 161 mRule.mUid = FIRST_APPLICATION_UID; 162 return this; 163 } 164 165 /** 166 * Configure the identity of this process to be the given package name for the duration 167 * of the test. Has no effect on non-Ravenwood environments. 168 */ setPackageName( String packageName)169 public Builder setPackageName(/* @NonNull */ String packageName) { 170 mRule.mPackageName = Objects.requireNonNull(packageName); 171 return this; 172 } 173 174 /** 175 * Configure a "main" thread to be available for the duration of the test, as defined 176 * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments. 177 */ setProvideMainThread(boolean provideMainThread)178 public Builder setProvideMainThread(boolean provideMainThread) { 179 mRule.mProvideMainThread = provideMainThread; 180 return this; 181 } 182 183 /** 184 * Configure the given system property as immutable for the duration of the test. 185 * Read access to the key is allowed, and write access will fail. When {@code value} is 186 * {@code null}, the value is left as undefined. 187 * 188 * All properties in the {@code debug.*} namespace are automatically mutable, with no 189 * developer action required. 190 * 191 * Has no effect on non-Ravenwood environments. 192 */ setSystemPropertyImmutable( String key, Object value)193 public Builder setSystemPropertyImmutable(/* @NonNull */ String key, 194 /* @Nullable */ Object value) { 195 mRule.mSystemProperties.setValue(key, value); 196 mRule.mSystemProperties.setAccessReadOnly(key); 197 return this; 198 } 199 200 /** 201 * Configure the given system property as mutable for the duration of the test. 202 * Both read and write access to the key is allowed, and its value will be reset between 203 * each test. When {@code value} is {@code null}, the value is left as undefined. 204 * 205 * All properties in the {@code debug.*} namespace are automatically mutable, with no 206 * developer action required. 207 * 208 * Has no effect on non-Ravenwood environments. 209 */ setSystemPropertyMutable( String key, Object value)210 public Builder setSystemPropertyMutable(/* @NonNull */ String key, 211 /* @Nullable */ Object value) { 212 mRule.mSystemProperties.setValue(key, value); 213 mRule.mSystemProperties.setAccessReadWrite(key); 214 return this; 215 } 216 217 /** 218 * Configure the set of system services that are required for this test to operate. 219 * 220 * For example, passing {@code android.hardware.SerialManager.class} as an argument will 221 * ensure that the underlying service is created, initialized, and ready to use for the 222 * duration of the test. The {@code SerialManager} instance can be obtained via 223 * {@code RavenwoodRule.getContext()} and {@code Context.getSystemService()}, and 224 * {@code SerialManagerInternal} can be obtained via {@code LocalServices.getService()}. 225 */ setServicesRequired(Class<?>.... services)226 public Builder setServicesRequired(Class<?>... services) { 227 mRule.mServicesRequired.clear(); 228 for (Class<?> service : services) { 229 mRule.mServicesRequired.add(service); 230 } 231 return this; 232 } 233 build()234 public RavenwoodRule build() { 235 return mRule; 236 } 237 } 238 239 /** 240 * @deprecated replaced by {@link #isOnRavenwood()} 241 */ 242 @Deprecated isUnderRavenwood()243 public static boolean isUnderRavenwood() { 244 return IS_ON_RAVENWOOD; 245 } 246 247 /** 248 * Return if the current process is running on a Ravenwood test environment. 249 */ isOnRavenwood()250 public static boolean isOnRavenwood() { 251 return IS_ON_RAVENWOOD; 252 } 253 254 /** 255 * Return a {@code Context} available for usage during the currently running test case. 256 * 257 * Each test should obtain needed information or references via this method; 258 * references must not be stored beyond the scope of a test case. 259 */ getContext()260 public Context getContext() { 261 return Objects.requireNonNull(mContext, 262 "Context is only available during @Test execution"); 263 } 264 265 /** 266 * Return a {@code Instrumentation} available for usage during the currently running test case. 267 * 268 * Each test should obtain needed information or references via this method; 269 * references must not be stored beyond the scope of a test case. 270 */ getInstrumentation()271 public Instrumentation getInstrumentation() { 272 return Objects.requireNonNull(mInstrumentation, 273 "Instrumentation is only available during @Test execution"); 274 } 275 shouldEnableOnDevice(Description description)276 static boolean shouldEnableOnDevice(Description description) { 277 if (description.isTest()) { 278 if (description.getAnnotation(DisabledOnNonRavenwood.class) != null) { 279 return false; 280 } 281 } 282 final var clazz = description.getTestClass(); 283 if (clazz != null) { 284 if (clazz.getAnnotation(DisabledOnNonRavenwood.class) != null) { 285 return false; 286 } 287 } 288 return true; 289 } 290 291 /** 292 * Determine if the given {@link Description} should be enabled when running on the 293 * Ravenwood test environment. 294 * 295 * A more specific method-level annotation always takes precedence over any class-level 296 * annotation, and an {@link EnabledOnRavenwood} annotation always takes precedence over 297 * an {@link DisabledOnRavenwood} annotation. 298 */ shouldEnableOnRavenwood(Description description)299 static boolean shouldEnableOnRavenwood(Description description) { 300 // First, consult any method-level annotations 301 if (description.isTest()) { 302 // Stopgap for http://g/ravenwood/EPAD-N5ntxM 303 if (description.getMethodName().endsWith("$noRavenwood")) { 304 return false; 305 } 306 if (description.getAnnotation(EnabledOnRavenwood.class) != null) { 307 return true; 308 } 309 if (description.getAnnotation(DisabledOnRavenwood.class) != null) { 310 return false; 311 } 312 if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) { 313 return false; 314 } 315 } 316 317 // Otherwise, consult any class-level annotations 318 final var clazz = description.getTestClass(); 319 if (clazz != null) { 320 if (description.getTestClass().getAnnotation(EnabledOnRavenwood.class) != null) { 321 return true; 322 } 323 if (description.getTestClass().getAnnotation(DisabledOnRavenwood.class) != null) { 324 return false; 325 } 326 if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) { 327 return false; 328 } 329 } 330 331 // When no annotations have been requested, assume test should be included 332 return true; 333 } 334 shouldStillIgnoreInProbeIgnoreMode(Description description)335 static boolean shouldStillIgnoreInProbeIgnoreMode(Description description) { 336 if (!ENABLE_REALLY_DISABLE_PATTERN) { 337 return false; 338 } 339 340 final var fullname = description.getTestClass().getName() 341 + (description.isTest() ? "#" + description.getMethodName() : ""); 342 343 if (REALLY_DISABLE_PATTERN.matcher(fullname).find()) { 344 System.out.println("Still ignoring " + fullname); 345 return true; 346 } 347 return false; 348 } 349 350 @Override apply(Statement base, Description description)351 public Statement apply(Statement base, Description description) { 352 // No special treatment when running outside Ravenwood; run tests as-is 353 if (!IS_ON_RAVENWOOD) { 354 Assume.assumeTrue(shouldEnableOnDevice(description)); 355 return base; 356 } 357 358 if (ENABLE_PROBE_IGNORED) { 359 return applyProbeIgnored(base, description); 360 } else { 361 return applyDefault(base, description); 362 } 363 } 364 commonPrologue(Statement base, Description description)365 private void commonPrologue(Statement base, Description description) { 366 RavenwoodRuleImpl.logTestRunner("started", description); 367 RavenwoodRuleImpl.validate(base, description, ENABLE_OPTIONAL_VALIDATION); 368 RavenwoodRuleImpl.init(RavenwoodRule.this); 369 } 370 371 /** 372 * Run the given {@link Statement} with no special treatment. 373 */ applyDefault(Statement base, Description description)374 private Statement applyDefault(Statement base, Description description) { 375 return new Statement() { 376 @Override 377 public void evaluate() throws Throwable { 378 Assume.assumeTrue(shouldEnableOnRavenwood(description)); 379 380 commonPrologue(base, description); 381 try { 382 base.evaluate(); 383 RavenwoodRuleImpl.logTestRunner("finished", description); 384 } catch (Throwable t) { 385 RavenwoodRuleImpl.logTestRunner("failed", description); 386 throw t; 387 } finally { 388 RavenwoodRuleImpl.reset(RavenwoodRule.this); 389 } 390 } 391 }; 392 } 393 394 /** 395 * Run the given {@link Statement} with probing enabled. All tests will be unconditionally 396 * run on Ravenwood to detect cases where a test is able to pass despite being marked as 397 * {@code IgnoreUnderRavenwood}. 398 */ 399 private Statement applyProbeIgnored(Statement base, Description description) { 400 return new Statement() { 401 @Override 402 public void evaluate() throws Throwable { 403 Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description)); 404 405 commonPrologue(base, description); 406 try { 407 base.evaluate(); 408 } catch (Throwable t) { 409 // If the test isn't included, eat the exception and report the 410 // assumption failure that test authors expect; otherwise throw 411 Assume.assumeTrue(shouldEnableOnRavenwood(description)); 412 throw t; 413 } finally { 414 RavenwoodRuleImpl.logTestRunner("finished", description); 415 RavenwoodRuleImpl.reset(RavenwoodRule.this); 416 } 417 418 if (!shouldEnableOnRavenwood(description)) { 419 fail("Test wasn't included under Ravenwood, but it actually " 420 + "passed under Ravenwood; consider updating annotations"); 421 } 422 } 423 }; 424 } 425 426 public static class _$RavenwoodPrivate { 427 public static boolean isOptionalValidationEnabled() { 428 return ENABLE_OPTIONAL_VALIDATION; 429 } 430 } 431 432 /** 433 * Returns the "real" result from {@link System#currentTimeMillis()}. 434 * 435 * Currently, it's the same thing as calling {@link System#currentTimeMillis()}, 436 * but this one is guaranteeed to return the real value, even when Ravenwood supports 437 * injecting a time to{@link System#currentTimeMillis()}. 438 */ 439 public long realCurrentTimeMillis() { 440 return System.currentTimeMillis(); 441 } 442 } 443