1 /* 2 * Copyright (C) 2024 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.shared.testing.concurrency; 17 18 import static com.android.adservices.shared.testing.concurrency.SyncCallback.LOG_TAG; 19 20 import com.android.adservices.shared.testing.Identifiable; 21 import com.android.adservices.shared.testing.Logger; 22 import com.android.adservices.shared.testing.Logger.RealLogger; 23 24 import com.google.common.annotations.VisibleForTesting; 25 26 import java.util.Objects; 27 import java.util.concurrent.CountDownLatch; 28 import java.util.concurrent.TimeUnit; 29 import java.util.concurrent.atomic.AtomicInteger; 30 import java.util.function.Supplier; 31 32 /** 33 * Defines the settings and some other internal state of a {@code SyncCallback}. 34 * 35 * <p><b>Note: </b>the internal state includes the current invocations, so normally each callback 36 * should have its own settings (for example, it should not be used as a static variable with some 37 * default settings), although it could be shared when a test need to block after distinct callbacks 38 * are called. 39 */ 40 public final class SyncCallbackSettings implements Identifiable { 41 42 /** Timeout set by default constructor */ 43 public static final long DEFAULT_TIMEOUT_MS = 5_000; 44 45 private static final AtomicInteger sIdGenerator = new AtomicInteger(); 46 47 private final String mId = String.valueOf(sIdGenerator.incrementAndGet()); 48 private final int mExpectedNumberCalls; 49 private final CountDownLatch mLatch; 50 private final long mMaxTimeoutMs; 51 private final boolean mFailIfCalledOnMainThread; 52 private final Supplier<Boolean> mIsMainThreadSupplier; 53 private final Logger mLogger; 54 SyncCallbackSettings(Builder builder)55 private SyncCallbackSettings(Builder builder) { 56 mExpectedNumberCalls = builder.mExpectedNumberCalls; 57 mLatch = new CountDownLatch(mExpectedNumberCalls); 58 mMaxTimeoutMs = builder.mMaxTimeoutMs; 59 mFailIfCalledOnMainThread = builder.mFailIfCalledOnMainThread; 60 mIsMainThreadSupplier = builder.mIsMainThreadSupplier; 61 mLogger = new Logger(builder.mRealLogger, LOG_TAG); 62 } 63 64 /** Gets the expected number of calls this callback should receive before it's done. */ getExpectedNumberCalls()65 public int getExpectedNumberCalls() { 66 return mExpectedNumberCalls; 67 } 68 69 /** Gets the maximum time the callback could wait before failing. */ getMaxTimeoutMs()70 public long getMaxTimeoutMs() { 71 return mMaxTimeoutMs; 72 } 73 74 /** Checks whether the callback should fail if called from the main thread. */ isFailIfCalledOnMainThread()75 public boolean isFailIfCalledOnMainThread() { 76 return mFailIfCalledOnMainThread; 77 } 78 isMainThread()79 boolean isMainThread() { 80 return mIsMainThreadSupplier.get(); 81 } 82 83 @Override getId()84 public String getId() { 85 return mId; 86 } 87 88 @Override toString()89 public String toString() { 90 return "settingsId=" 91 + mId 92 + ", expectedNumberCalls=" 93 + mExpectedNumberCalls 94 + ", maxTimeoutMs=" 95 + mMaxTimeoutMs 96 + ", failIfCalledOnMainThread=" 97 + mFailIfCalledOnMainThread 98 + ", missingCalls=" 99 + mLatch.getCount(); 100 } 101 getLogger()102 Logger getLogger() { 103 return mLogger; 104 } 105 106 // NOTE: log of methods below are indirectly unit tested by the callback test - testing it again 107 // on SyncCallbackSettings would be an overkill (they're not public anyways) 108 countDown()109 void countDown() { 110 mLatch.countDown(); 111 } 112 assertCalled(Supplier<String> caller)113 void assertCalled(Supplier<String> caller) throws InterruptedException { 114 long timeoutMs = getMaxTimeoutMs(); 115 TimeUnit unit = TimeUnit.MILLISECONDS; 116 if (!mLatch.await(timeoutMs, unit)) { 117 throw new SyncCallbackTimeoutException(caller.get(), timeoutMs, unit); 118 } 119 } 120 isCalled()121 boolean isCalled() { 122 return mLatch.getCount() == 0; 123 } 124 125 /** 126 * Checks that the given settings is not configured to {@link 127 * SyncCallbackSettings#isFailIfCalledOnMainThread() fail if called in the main thread}. 128 * 129 * @return same settings 130 * @throws IllegalArgumentException if configured to {@link 131 * SyncCallbackSettings#isFailIfCalledOnMainThread() fail if called in the main thread}. 132 */ checkCanFailOnMainThread(SyncCallbackSettings settings)133 public static SyncCallbackSettings checkCanFailOnMainThread(SyncCallbackSettings settings) { 134 if (settings.isFailIfCalledOnMainThread()) { 135 throw new IllegalArgumentException( 136 "Cannot use a SyncCallbackSettings (" 137 + settings 138 + ") that fails if called on main thread"); 139 } 140 return settings; 141 } 142 143 /** Bob the Builder! */ 144 public static final class Builder { 145 146 private int mExpectedNumberCalls = 1; 147 private long mMaxTimeoutMs = DEFAULT_TIMEOUT_MS; 148 private boolean mFailIfCalledOnMainThread = true; 149 private final Supplier<Boolean> mIsMainThreadSupplier; 150 private final RealLogger mRealLogger; 151 152 // Package protected so it's only called by SyncCallbackFactory and unit tests Builder(RealLogger realLogger, Supplier<Boolean> isMainThreadSupplier)153 Builder(RealLogger realLogger, Supplier<Boolean> isMainThreadSupplier) { 154 mRealLogger = Objects.requireNonNull(realLogger, "realLogger cannot be null"); 155 mIsMainThreadSupplier = 156 Objects.requireNonNull( 157 isMainThreadSupplier, "isMainThreadSupplier cannot be null"); 158 } 159 160 @VisibleForTesting Builder(RealLogger realLogger)161 Builder(RealLogger realLogger) { 162 this(realLogger, () -> Boolean.FALSE); 163 } 164 165 /** See {@link SyncCallbackSettings#getExpectedNumberCalls()}. */ setExpectedNumberCalls(int value)166 public Builder setExpectedNumberCalls(int value) { 167 assertIsPositive(value); 168 mExpectedNumberCalls = value; 169 return this; 170 } 171 172 /** See {@link SyncCallbackSettings#getMaxTimeoutMs(long)}. */ setMaxTimeoutMs(long value)173 public Builder setMaxTimeoutMs(long value) { 174 assertIsPositive(value); 175 mMaxTimeoutMs = value; 176 return this; 177 } 178 179 /** See {@link SyncCallbackSettings#isFailIfCalledOnMainThread()}. */ setFailIfCalledOnMainThread(boolean value)180 public Builder setFailIfCalledOnMainThread(boolean value) { 181 mFailIfCalledOnMainThread = value; 182 return this; 183 } 184 185 /** Can we build it? Yes we can! */ build()186 public SyncCallbackSettings build() { 187 return new SyncCallbackSettings(this); 188 } 189 } 190 assertIsPositive(long value)191 private static void assertIsPositive(long value) { 192 if (value <= 0) { 193 throw new IllegalArgumentException("must be a positive value"); 194 } 195 } 196 } 197