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 * Configure the given system property as immutable for the duration of the test. 104 * Read access to the key is allowed, and write access will fail. When {@code value} is 105 * {@code null}, the value is left as undefined. 106 * 107 * All properties in the {@code debug.*} namespace are automatically mutable, with no 108 * developer action required. 109 * 110 * Has no effect on non-Ravenwood environments. 111 */ setSystemPropertyImmutable(@onNull String key, @Nullable Object value)112 public Builder setSystemPropertyImmutable(@NonNull String key, @Nullable Object value) { 113 mRule.mProperties.setValue(key, value); 114 mRule.mProperties.setAccessReadOnly(key); 115 return this; 116 } 117 118 /** 119 * Configure the given system property as mutable for the duration of the test. 120 * Both read and write access to the key is allowed, and its value will be reset between 121 * each test. When {@code value} is {@code null}, the value is left as undefined. 122 * 123 * All properties in the {@code debug.*} namespace are automatically mutable, with no 124 * developer action required. 125 * 126 * Has no effect on non-Ravenwood environments. 127 */ setSystemPropertyMutable(@onNull String key, @Nullable Object value)128 public Builder setSystemPropertyMutable(@NonNull String key, @Nullable Object value) { 129 mRule.mProperties.setValue(key, value); 130 mRule.mProperties.setAccessReadWrite(key); 131 return this; 132 } 133 build()134 public RavenwoodRule build() { 135 return mRule; 136 } 137 } 138 139 /** 140 * Return if the current process is running on a Ravenwood test environment. 141 */ isOnRavenwood()142 public static boolean isOnRavenwood() { 143 return IS_ON_RAVENWOOD; 144 } 145 ensureOnRavenwood(String featureName)146 private static void ensureOnRavenwood(String featureName) { 147 if (!IS_ON_RAVENWOOD) { 148 throw new RuntimeException(featureName + " is only supported on Ravenwood."); 149 } 150 } 151 152 @Override apply(Statement base, Description description)153 public Statement apply(Statement base, Description description) { 154 if (!IS_ON_RAVENWOOD) { 155 return base; 156 } 157 return new Statement() { 158 @Override 159 public void evaluate() throws Throwable { 160 RavenwoodAwareTestRunner.onRavenwoodRuleEnter(description, RavenwoodRule.this); 161 try { 162 base.evaluate(); 163 } finally { 164 RavenwoodAwareTestRunner.onRavenwoodRuleExit(description, RavenwoodRule.this); 165 } 166 } 167 }; 168 } 169 170 /** 171 * Returns the "real" result from {@link System#currentTimeMillis()}. 172 * 173 * Currently, it's the same thing as calling {@link System#currentTimeMillis()}, 174 * but this one is guaranteeed to return the real value, even when Ravenwood supports 175 * injecting a time to{@link System#currentTimeMillis()}. 176 */ 177 public long realCurrentTimeMillis() { 178 return System.currentTimeMillis(); 179 } 180 181 /** 182 * Equivalent to setting the ANDROID_LOG_TAGS environmental variable. 183 * 184 * See https://developer.android.com/tools/logcat#filteringOutput for the string format. 185 * 186 * NOTE: this works only on Ravenwood. 187 */ 188 public static void setAndroidLogTags(@Nullable String androidLogTags) { 189 ensureOnRavenwood("RavenwoodRule.setAndroidLogTags()"); 190 try { 191 Class<?> logRavenwoodClazz = Class.forName("android.util.Log_ravenwood"); 192 var setter = logRavenwoodClazz.getMethod("setLogLevels", String.class); 193 setter.invoke(null, androidLogTags); 194 } catch (ReflectiveOperationException e) { 195 throw new RuntimeException(e); 196 } 197 } 198 199 /** 200 * Set a log level for a given tag. Pass NULL to {@code tag} to change the default level. 201 * 202 * NOTE: this works only on Ravenwood. 203 */ 204 public static void setLogLevel(@Nullable String tag, int level) { 205 ensureOnRavenwood("RavenwoodRule.setLogLevel()"); 206 try { 207 Class<?> logRavenwoodClazz = Class.forName("android.util.Log_ravenwood"); 208 var setter = logRavenwoodClazz.getMethod("setLogLevel", String.class, int.class); 209 setter.invoke(null, tag, level); 210 } catch (ReflectiveOperationException e) { 211 throw new RuntimeException(e); 212 } 213 } 214 215 // Below are internal to ravenwood. Don't use them from normal tests... 216 217 public static class RavenwoodPrivate { 218 private RavenwoodPrivate() { 219 } 220 221 private volatile Boolean mRunDisabledTestsOverride = null; 222 223 private volatile Pattern mReallyDisabledPattern = null; 224 225 public boolean isRunningDisabledTests() { 226 if (mRunDisabledTestsOverride != null) { 227 return mRunDisabledTestsOverride; 228 } 229 return RUN_DISABLED_TESTS; 230 } 231 232 public Pattern getReallyDisabledPattern() { 233 if (mReallyDisabledPattern != null) { 234 return mReallyDisabledPattern; 235 } 236 return REALLY_DISABLED_PATTERN; 237 } 238 239 public void overrideRunDisabledTest(boolean runDisabledTests, 240 @Nullable String reallyDisabledPattern) { 241 mRunDisabledTestsOverride = runDisabledTests; 242 mReallyDisabledPattern = 243 reallyDisabledPattern == null ? null : Pattern.compile(reallyDisabledPattern); 244 } 245 246 public void resetRunDisabledTest() { 247 mRunDisabledTestsOverride = null; 248 mReallyDisabledPattern = null; 249 } 250 } 251 252 private static final RavenwoodPrivate sRavenwoodPrivate = new RavenwoodPrivate(); 253 254 public static RavenwoodPrivate private$ravenwood() { 255 return sRavenwoodPrivate; 256 } 257 } 258