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(long timeoutMs, Supplier<String> caller)113 void assertCalled(long timeoutMs, Supplier<String> caller) throws InterruptedException { 114 TimeUnit unit = TimeUnit.MILLISECONDS; 115 if (!mLatch.await(timeoutMs, unit)) { 116 throw new SyncCallbackTimeoutException(caller.get(), timeoutMs, unit); 117 } 118 } 119 isCalled()120 boolean isCalled() { 121 return mLatch.getCount() == 0; 122 } 123 124 /** 125 * Checks that the given settings is not configured to {@link 126 * SyncCallbackSettings#isFailIfCalledOnMainThread() fail if called in the main thread}. 127 * 128 * @return same settings 129 * @throws IllegalArgumentException if configured to {@link 130 * SyncCallbackSettings#isFailIfCalledOnMainThread() fail if called in the main thread}. 131 */ checkCanFailOnMainThread(SyncCallbackSettings settings)132 public static SyncCallbackSettings checkCanFailOnMainThread(SyncCallbackSettings settings) { 133 if (settings.isFailIfCalledOnMainThread()) { 134 throw new IllegalArgumentException( 135 "Cannot use a SyncCallbackSettings (" 136 + settings 137 + ") that fails if called on main thread"); 138 } 139 return settings; 140 } 141 142 /** Bob the Builder! */ 143 public static final class Builder { 144 145 private int mExpectedNumberCalls = 1; 146 private long mMaxTimeoutMs = DEFAULT_TIMEOUT_MS; 147 private boolean mFailIfCalledOnMainThread = true; 148 private final Supplier<Boolean> mIsMainThreadSupplier; 149 private final RealLogger mRealLogger; 150 151 // Package protected so it's only called by SyncCallbackFactory and unit tests Builder(RealLogger realLogger, Supplier<Boolean> isMainThreadSupplier)152 Builder(RealLogger realLogger, Supplier<Boolean> isMainThreadSupplier) { 153 mRealLogger = Objects.requireNonNull(realLogger, "realLogger cannot be null"); 154 mIsMainThreadSupplier = 155 Objects.requireNonNull( 156 isMainThreadSupplier, "isMainThreadSupplier cannot be null"); 157 } 158 159 @VisibleForTesting Builder(RealLogger realLogger)160 Builder(RealLogger realLogger) { 161 this(realLogger, () -> Boolean.FALSE); 162 } 163 164 /** See {@link SyncCallbackSettings#getExpectedNumberCalls()}. */ setExpectedNumberCalls(int value)165 public Builder setExpectedNumberCalls(int value) { 166 assertNonNegative(value); 167 mExpectedNumberCalls = value; 168 return this; 169 } 170 171 /** See {@link SyncCallbackSettings#getMaxTimeoutMs(long)}. */ setMaxTimeoutMs(long value)172 public Builder setMaxTimeoutMs(long value) { 173 assertNonNegative(value); 174 mMaxTimeoutMs = value; 175 return this; 176 } 177 178 /** See {@link SyncCallbackSettings#isFailIfCalledOnMainThread()}. */ setFailIfCalledOnMainThread(boolean value)179 public Builder setFailIfCalledOnMainThread(boolean value) { 180 mFailIfCalledOnMainThread = value; 181 return this; 182 } 183 184 /** Can we build it? Yes we can! */ build()185 public SyncCallbackSettings build() { 186 return new SyncCallbackSettings(this); 187 } 188 } 189 assertNonNegative(long value)190 private static void assertNonNegative(long value) { 191 if (value < 0) { 192 throw new IllegalArgumentException("value must be >= 0"); 193 } 194 } 195 } 196