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