• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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