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 package com.android.adservices.common; 17 18 import android.os.SystemClock; 19 import android.os.SystemProperties; 20 import android.util.Log; 21 22 import androidx.annotation.Nullable; 23 24 import com.android.adservices.shared.testing.DeviceSideTestCase; 25 26 import com.google.errorprone.annotations.FormatMethod; 27 import com.google.errorprone.annotations.FormatString; 28 29 import org.junit.After; 30 import org.junit.Rule; 31 32 // TODO(b/285014040): need to add unit tests for this class itself, as it's now providing logic. 33 34 /** Superclass for all other "base classes" on {@code AdServices} projects. */ 35 abstract class AdServicesTestCase extends DeviceSideTestCase { 36 37 private static final String TAG = AdServicesTestCase.class.getSimpleName(); 38 39 // NOTE: properties below are meant to be used to debug / reproduce test failures, they should 40 // NOT be used programmatically in the tests themselves. 41 42 /** When set, defines duration (in milliseconds) to sleep after each test. */ 43 private static final String PROP_DELAY_AFTER_TEST = "debug.adservices.test.postTestDelay"; 44 45 /** 46 * When set to a (non-zero) number, test will throw a RuntimeException in a background time at 47 * the value defined by it, either once (if the number is positive) or repeatedly. 48 * 49 * <p>For example, if the value is {@code 3}, the 3rd test - and only the 3rd test - will throw 50 * the exception after it's done. But if it's {@code -3}, then the exception will be thrown 51 * after the 3rd test, 6th test, etc... . 52 */ 53 private static final String PROP_EXCEPTION_THROWN_FREQUENCY = 54 "debug.adservices.test.postTestThrownFrequency"; 55 56 @Rule(order = 1) 57 public final AdServicesDeviceSupportedRule adServicesDeviceSupportedRule = 58 new AdServicesDeviceSupportedRule(); 59 60 @After postTestOptionalActions()61 public final void postTestOptionalActions() { 62 throwExceptionInBgAfterTest(); 63 sleepAfterTest(); 64 } 65 66 @Override getTestName()67 public final String getTestName() { 68 return processLifeguard.getTestName(); 69 } 70 71 /** Sleeps for the given amount of time. */ 72 @FormatMethod sleep( int timeMs, @FormatString String reasonFmt, @Nullable Object... reasonArgs)73 protected final void sleep( 74 int timeMs, @FormatString String reasonFmt, @Nullable Object... reasonArgs) { 75 String reason = String.format(reasonFmt, reasonArgs); 76 Log.i( 77 TAG, 78 getTestName() 79 + ": napping " 80 + timeMs 81 + "ms on thread " 82 + Thread.currentThread() 83 + ". Reason: " 84 + reason); 85 SystemClock.sleep(timeMs); 86 Log.i(TAG, "Little Suzie woke up!"); 87 } 88 89 /** 90 * Throws a runtime exception (with the given {@code message}) in the background. 91 * 92 * <p>By default, it starts a thread (with the given {@code threadName}) that throws right away, 93 * but subclasses could override it to change the behavior (for example, to use an existing 94 * executor). 95 */ throwExceptionInBg(String threadName, String message)96 protected void throwExceptionInBg(String threadName, String message) { 97 RuntimeException exception = new RuntimeException(message); 98 Log.i(TAG, "Starting thread " + threadName + " (which will throw " + exception + ")"); 99 new Thread( 100 () -> { 101 throw exception; 102 }, 103 threadName) 104 .start(); 105 } 106 sleepAfterTest()107 private void sleepAfterTest() { 108 int napTimeMs = SystemProperties.getInt(PROP_DELAY_AFTER_TEST, 0); 109 if (napTimeMs <= 0) { 110 return; 111 } 112 sleep( 113 napTimeMs, 114 "forcing sleep after test as requested by system property %s", 115 PROP_DELAY_AFTER_TEST); 116 } 117 throwExceptionInBgAfterTest()118 private void throwExceptionInBgAfterTest() { 119 int frequency = SystemProperties.getInt(PROP_EXCEPTION_THROWN_FREQUENCY, 0); 120 if (frequency == 0) { 121 return; 122 } 123 124 int testNumber = getTestInvocationId(); 125 boolean throwException = 126 (frequency < 0 && (testNumber % frequency != 0)) 127 || (frequency > 0 && testNumber == frequency); 128 129 if (!throwException) { 130 Log.i( 131 TAG, 132 "Not throwing exception after test #" 133 + testNumber 134 + " (frequency=" 135 + frequency 136 + ")"); 137 return; 138 } 139 140 Log.e( 141 TAG, 142 "Throwing exception after test #" + testNumber + " (frequency=" + frequency + ")"); 143 144 String threadName = getTestName() + "-postTest-#" + testNumber; 145 String message = 146 getTestName() 147 + " failing @After " 148 + testNumber 149 + " invocation(s) of " 150 + "test methods from classes that extend " 151 + AdServicesTestCase.class.getSimpleName() 152 + " (as requested by property " 153 + PROP_EXCEPTION_THROWN_FREQUENCY 154 + ")"; 155 throwExceptionInBg(threadName, message); 156 } 157 } 158