• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.car.internal;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 import static com.google.common.truth.Truth.assertWithMessage;
20 
21 import android.os.HandlerThread;
22 import android.os.Looper;
23 import android.os.SystemClock;
24 
25 import com.android.internal.annotations.GuardedBy;
26 
27 import org.junit.After;
28 import org.junit.Before;
29 import org.junit.Test;
30 
31 import java.util.ArrayList;
32 import java.util.List;
33 
34 /**
35  * Unit tests for {@link LongPendingRequestPool}.
36  */
37 public final class PendingRequestPoolUnitTest {
38 
39     private static final class LongTestRequest implements LongRequestIdWithTimeout {
40         private final long mRequestId;
41         private final long mTimeoutUptimeMs;
42 
LongTestRequest(long requestId, long timeoutUptimeMs)43         LongTestRequest(long requestId, long timeoutUptimeMs) {
44             mRequestId = requestId;
45             mTimeoutUptimeMs = timeoutUptimeMs;
46         }
47 
48         @Override
getRequestId()49         public long getRequestId() {
50             return mRequestId;
51         }
52 
53         @Override
getTimeoutUptimeMs()54         public long getTimeoutUptimeMs() {
55             return mTimeoutUptimeMs;
56         }
57     }
58 
59     private static final class LongTestTimeoutCallback implements
60             LongPendingRequestPool.TimeoutCallback {
61         private final Object mLock = new Object();
62         @GuardedBy("mLock")
63         private final List<Long> mTimeoutRequestIds = new ArrayList<>();
64 
65         @Override
onRequestsTimeout(List<Long> requestIds)66         public void onRequestsTimeout(List<Long> requestIds) {
67             synchronized (mLock) {
68                 for (int i = 0; i < requestIds.size(); i++) {
69                     mTimeoutRequestIds.add(requestIds.get(i));
70                 }
71                 mLock.notify();
72             }
73         }
74 
waitForTimeoutRequestIds(int count, long waitTimeoutMs)75         List<Long> waitForTimeoutRequestIds(int count, long waitTimeoutMs) throws Exception {
76             synchronized (mLock) {
77                 long endTime = SystemClock.uptimeMillis() + waitTimeoutMs;
78                 while (mTimeoutRequestIds.size() < count) {
79                     long now = SystemClock.uptimeMillis();
80                     if (now >= endTime) {
81                         break;
82                     }
83                     mLock.wait(endTime - now);
84                 }
85                 return new ArrayList<>(mTimeoutRequestIds);
86             }
87         }
88 
countTimeoutRequestIds()89         int countTimeoutRequestIds() {
90             synchronized (mLock) {
91                 return mTimeoutRequestIds.size();
92             }
93         }
94     }
95 
96     private final HandlerThread mHandlerThread = new HandlerThread(
97             PendingRequestPoolUnitTest.class.getSimpleName());
98     private LongTestTimeoutCallback mLongTestTimeoutCallback;
99     private LongPendingRequestPool<LongTestRequest> mLongPendingRequestPool;
100 
101     @Before
setUp()102     public void setUp() {
103         mHandlerThread.start();
104         Looper looper = mHandlerThread.getLooper();
105         mLongTestTimeoutCallback = new LongTestTimeoutCallback();
106         mLongPendingRequestPool = new LongPendingRequestPool<>(looper, mLongTestTimeoutCallback);
107     }
108 
109     @After
tearDown()110     public void tearDown() {
111         mHandlerThread.quitSafely();
112     }
113 
114     @Test
testAddRemoveRequests()115     public void testAddRemoveRequests() {
116         long testRequestId1 = 123;
117         long testRequestId2 = 234;
118         long timeoutUptimeMs = SystemClock.uptimeMillis() + 1000;
119         LongTestRequest request1 = new LongTestRequest(testRequestId1, timeoutUptimeMs);
120         LongTestRequest request2 = new LongTestRequest(testRequestId2, timeoutUptimeMs);
121 
122         mLongPendingRequestPool.addPendingRequests(List.of(request1, request2));
123 
124         assertThat(mLongPendingRequestPool.getRequestIfFound(0)).isNull();
125         assertThat(mLongPendingRequestPool.getRequestIfFound(testRequestId1)).isEqualTo(request1);
126         assertThat(mLongPendingRequestPool.getRequestIfFound(testRequestId2)).isEqualTo(request2);
127 
128         mLongPendingRequestPool.removeRequest(testRequestId1);
129         mLongPendingRequestPool.removeRequest(testRequestId2);
130 
131         assertThat(mLongTestTimeoutCallback.countTimeoutRequestIds()).isEqualTo(0);
132         assertWithMessage("Expect request pool to be empty after the test").that(
133                 mLongPendingRequestPool.isEmpty()).isTrue();
134     }
135 
136     @Test
testRequestTimeout()137     public void testRequestTimeout() throws Exception {
138         long testRequestId1 = 123;
139         long testRequestId2 = 234;
140         long testRequestId3 = 345;
141         LongTestRequest request1 = new LongTestRequest(testRequestId1,
142                 SystemClock.uptimeMillis() + 100);
143         LongTestRequest request2 = new LongTestRequest(testRequestId2,
144                 SystemClock.uptimeMillis() + 200);
145         LongTestRequest request3 = new LongTestRequest(testRequestId3,
146                 SystemClock.uptimeMillis() + 1000);
147 
148         mLongPendingRequestPool.addPendingRequests(List.of(request1, request2, request3));
149 
150         // No requests should timeout yet.
151         assertThat(mLongTestTimeoutCallback.countTimeoutRequestIds()).isEqualTo(0);
152 
153         // Mark request 3 as finished.
154         mLongPendingRequestPool.removeRequest(testRequestId3);
155 
156         List<Long> timeoutRequestIds = mLongTestTimeoutCallback.waitForTimeoutRequestIds(
157                 /* count= */ 2, /* waitTimeoutMs= */ 1000);
158 
159         assertThat(timeoutRequestIds).hasSize(2);
160         assertThat(timeoutRequestIds).containsExactlyElementsIn(
161                 new Long[]{testRequestId1, testRequestId2});
162 
163         // Must remove the timeout request explicitly.
164         mLongPendingRequestPool.removeRequest(testRequestId1, /* alreadyTimedOut= */ true);
165         mLongPendingRequestPool.removeRequest(testRequestId2, /* alreadyTimedOut= */ true);
166         assertWithMessage("Expect request pool to be empty after the test").that(
167                 mLongPendingRequestPool.isEmpty()).isTrue();
168     }
169 
170     @Test
testRequestsAlreadyTimedOutNoDuplicateCallbacksCalled()171     public void testRequestsAlreadyTimedOutNoDuplicateCallbacksCalled() throws Exception {
172         long testRequestId1 = 123;
173         long testRequestId2 = 234;
174         long testRequestId3 = 345;
175 
176         long now = SystemClock.uptimeMillis();
177         // All the requests already timeout.
178         LongTestRequest request1 = new LongTestRequest(testRequestId1, now);
179         LongTestRequest request2 = new LongTestRequest(testRequestId2, now);
180         LongTestRequest request3 = new LongTestRequest(testRequestId3, now);
181 
182         mLongPendingRequestPool.addPendingRequests(List.of(request1));
183 
184         // The callback is invoked in a message handler, so it is not guaranteed to be invoked
185         // after the addPendingRequests returns.
186         List<Long> timeoutRequestIds = mLongTestTimeoutCallback.waitForTimeoutRequestIds(
187                 /* count= */ 1, /* waitTimeoutMs= */ 1000);
188         assertWithMessage("A request that already timeout must invoke the callback")
189                 .that(timeoutRequestIds).hasSize(1);
190         assertThat(timeoutRequestIds).containsExactlyElementsIn(new Long[]{testRequestId1});
191 
192         mLongPendingRequestPool.addPendingRequests(List.of(request2, request3));
193 
194         timeoutRequestIds = mLongTestTimeoutCallback.waitForTimeoutRequestIds(
195                 /* count= */ 3, /* waitTimeoutMs= */ 1000);
196         assertWithMessage("Requests that already timeout must invoke the callback")
197                 .that(timeoutRequestIds).hasSize(3);
198         assertThat(timeoutRequestIds).containsExactlyElementsIn(
199                 new Long[]{testRequestId1, testRequestId2, testRequestId3});
200 
201         // Clean up the requests.
202         mLongPendingRequestPool.removeRequest(testRequestId1, /* alreadyTimedOut= */ true);
203         mLongPendingRequestPool.removeRequest(testRequestId2, /* alreadyTimedOut= */ true);
204         mLongPendingRequestPool.removeRequest(testRequestId3, /* alreadyTimedOut= */ true);
205         assertWithMessage("Expect request pool to be empty after the test").that(
206                 mLongPendingRequestPool.isEmpty()).isTrue();
207     }
208 }
209