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 org.junit.Assert.assertThrows;
19 
20 import org.junit.Before;
21 import org.junit.Test;
22 
23 import java.util.List;
24 import java.util.concurrent.atomic.AtomicInteger;
25 
26 /** Base test for classes that extend ResultTestSyncCallback. */
27 public abstract class IResultSyncCallbackTestCase<
28                 R, CB extends IResultSyncCallback<R> & FreezableToString>
29         extends IBinderSyncCallbackTestCase<CB> {
30 
31     private static final AtomicInteger sNextId = new AtomicInteger();
32 
33     // Must be set on @Before otherwise OutcomeReceiverForTestsTest would fail on R
34     protected CB mCallback;
35 
36     /** Gets a new, unique result object, preferably with a user-friendly string representation. */
newResult()37     protected abstract R newResult();
38 
39     @Before
setFixtures()40     public final void setFixtures() {
41         mCallback = newCallback(mDefaultSettings);
42         mLog.v("setFixtures(): mCallback=%s", mCallback);
43     }
44 
45     /**
46      * Gets a unique id.
47      *
48      * <p>Useful to make sure {@link #newResult()} return unique objects.
49      */
getNextUniqueId()50     protected final int getNextUniqueId() {
51         return sNextId.incrementAndGet();
52     }
53 
54     @Override
callCallback(CB callback)55     protected String callCallback(CB callback) {
56         R result = newResult();
57         callback.injectResult(result);
58         return "injectResult(" + result + ")";
59     }
60 
61     @Test
testNewResult()62     public final void testNewResult() {
63         R result1 = newResult();
64         expect.withMessage("1st result").that(result1).isNotNull();
65 
66         R result2 = newResult();
67         expect.withMessage("2nd result").that(result2).isNotNull();
68         expect.withMessage("2nd result").that(result2).isNotSameInstanceAs(result1);
69     }
70 
71     @Test
testInjectResult_assertResultReceived()72     public final void testInjectResult_assertResultReceived() throws Exception {
73         assertInitialState(mCallback);
74         R injectedResult = newResult();
75 
76         runAsync(INJECTION_TIMEOUT_MS, () -> mCallback.injectResult(injectedResult));
77         R receivedResult = mCallback.assertResultReceived();
78 
79         expect.withMessage("%s.assertResultReceived()", mCallback)
80                 .that(receivedResult)
81                 .isSameInstanceAs(injectedResult);
82         expect.withMessage("%s.isCalled() after injectResult()", mCallback)
83                 .that(mCallback.isCalled())
84                 .isTrue();
85         R gottenResult = mCallback.getResult();
86         expect.withMessage("%s.getResult()", mCallback)
87                 .that(gottenResult)
88                 .isSameInstanceAs(injectedResult);
89         expect.withMessage("%s.getResults() after injectResult()", mCallback)
90                 .that(mCallback.getResults())
91                 .containsExactly(injectedResult);
92         expect.withMessage("toString() after injectResult()")
93                 .that(mCallback.toString())
94                 .contains("result=" + injectedResult);
95     }
96 
97     @Test
testInjectNullResult_assertResultReceived()98     public final void testInjectNullResult_assertResultReceived() throws Exception {
99         assertInitialState(mCallback);
100         R injectedResult = null;
101 
102         runAsync(INJECTION_TIMEOUT_MS, () -> mCallback.injectResult(injectedResult));
103         R receivedResult = mCallback.assertResultReceived();
104 
105         expect.withMessage("%s.assertResultReceived()", mCallback).that(receivedResult).isNull();
106         expect.withMessage("%s.isCalled() after injectResult()", mCallback)
107                 .that(mCallback.isCalled())
108                 .isTrue();
109 
110         assertGetResultMethods(mCallback, "after injectResult()", injectedResult);
111     }
112 
113     @Test
testInjectResult_assertCalled()114     public final void testInjectResult_assertCalled() throws Exception {
115         assertInitialState(mCallback);
116         R injectedResult = newResult();
117 
118         runAsync(INJECTION_TIMEOUT_MS, () -> mCallback.injectResult(injectedResult));
119         mCallback.assertCalled();
120 
121         assertGetResultMethods(mCallback, "after injectResult()", injectedResult);
122     }
123 
124     @Test
testInjectNullResult_assertCalled()125     public final void testInjectNullResult_assertCalled() throws Exception {
126         assertInitialState(mCallback);
127         R injectedResult = null;
128 
129         runAsync(INJECTION_TIMEOUT_MS, () -> mCallback.injectResult(injectedResult));
130         mCallback.assertCalled();
131 
132         assertGetResultMethods(mCallback, "after injectResult()", injectedResult);
133     }
134 
135     @Test
testInjectResult_calledTwice()136     public final void testInjectResult_calledTwice() throws Exception {
137         R firstResult = newResult();
138         mCallback.injectResult(firstResult);
139         R secondResult = newResult();
140         mCallback.injectResult(secondResult);
141 
142         R assertReceivedResult = mCallback.assertResultReceived();
143 
144         expect.withMessage("%s.assertResultReceived()", mCallback)
145                 .that(assertReceivedResult)
146                 .isSameInstanceAs(firstResult);
147 
148         assertGetResultMethods(
149                 mCallback, "after 2 injectResult() calls", firstResult, secondResult);
150     }
151 
152     @Test
testInjectResult_calledTwice_firstWasNull()153     public final void testInjectResult_calledTwice_firstWasNull() throws Exception {
154         R firstResult = null;
155         mCallback.injectResult(firstResult);
156         R secondResult = newResult();
157         mCallback.injectResult(secondResult);
158 
159         R assertReceivedResult = mCallback.assertResultReceived();
160 
161         expect.withMessage("%s.assertResultReceived()", mCallback)
162                 .that(assertReceivedResult)
163                 .isSameInstanceAs(firstResult);
164 
165         assertGetResultMethods(
166                 mCallback, "after 2 injectResult() calls", firstResult, secondResult);
167     }
168 
169     @Test
testGetResults_immutable()170     public final void testGetResults_immutable() throws Exception {
171         List<R> results = mCallback.getResults();
172         expect.withMessage("%s.getResults() initially", mCallback).that(results).isEmpty();
173 
174         assertThrows(UnsupportedOperationException.class, () -> results.add(newResult()));
175 
176         expect.withMessage("%s.getResults() after", mCallback)
177                 .that(mCallback.getResults())
178                 .isEmpty();
179     }
180 
181     @Test
testToString_containsResults()182     public final void testToString_containsResults() throws Exception {
183         // Initial state
184         String toString = mCallback.toString();
185         expect.withMessage("toString()").that(toString).contains("(no result yet)");
186 
187         // 1st call
188         R firstResult = newResult();
189         mCallback.injectResult(firstResult);
190         toString = mCallback.toString();
191         expect.withMessage("toString()").that(toString).contains("result=" + firstResult);
192         expect.withMessage("toString()").that(toString).contains("results=[" + firstResult + "]");
193 
194         // 2nd call
195         R secondResult = newResult();
196         mCallback.injectResult(secondResult);
197         toString = mCallback.toString();
198         expect.withMessage("toString()").that(toString).contains("result=" + firstResult);
199         expect.withMessage("toString()")
200                 .that(toString)
201                 .contains("results=[" + firstResult + ", " + secondResult + "]");
202     }
203 
assertGetResultMethodsWhenNoResult(CB callback, String when)204     protected final void assertGetResultMethodsWhenNoResult(CB callback, String when) {
205         expect.withMessage("%s.getResult() %s", callback, when).that(callback.getResult()).isNull();
206         expect.withMessage("%s.getResults() %s", callback, when)
207                 .that(callback.getResults())
208                 .isEmpty();
209     }
210 
211     @SafeVarargs
assertGetResultMethods(CB callback, String when, R... expectedResults)212     protected final void assertGetResultMethods(CB callback, String when, R... expectedResults) {
213         expect.withMessage("%s.getResult() %s", callback, when)
214                 .that(callback.getResult())
215                 .isSameInstanceAs(expectedResults[0]);
216         expect.withMessage("%s.getResults() %s", callback, when)
217                 .that(callback.getResults())
218                 .containsExactly(expectedResults)
219                 .inOrder();
220     }
221 
assertInitialState(CB callback)222     private void assertInitialState(CB callback) {
223         assertGetResultMethodsWhenNoResult(callback, "initially");
224     }
225 }
226