1 /* 2 * Copyright (C) 2022 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 17 package com.android.adservices.service.customaudience; 18 19 import static android.adservices.common.AdServicesStatusUtils.STATUS_INTERNAL_ERROR; 20 import static android.adservices.common.AdServicesStatusUtils.STATUS_SUCCESS; 21 22 import static com.android.adservices.common.CommonFlagsValues.EXTENDED_FLEDGE_BACKGROUND_FETCH_NETWORK_CONNECT_TIMEOUT_MS; 23 import static com.android.adservices.common.CommonFlagsValues.EXTENDED_FLEDGE_BACKGROUND_FETCH_NETWORK_READ_TIMEOUT_MS; 24 25 import static com.google.common.truth.Truth.assertThat; 26 import static com.google.common.util.concurrent.Futures.immediateFuture; 27 import static com.google.common.util.concurrent.Futures.immediateVoidFuture; 28 29 import static org.junit.Assert.assertThrows; 30 import static org.junit.Assert.assertTrue; 31 import static org.mockito.ArgumentMatchers.anyInt; 32 import static org.mockito.ArgumentMatchers.eq; 33 import static org.mockito.Mockito.any; 34 import static org.mockito.Mockito.anyLong; 35 import static org.mockito.Mockito.doAnswer; 36 import static org.mockito.Mockito.doReturn; 37 import static org.mockito.Mockito.doThrow; 38 import static org.mockito.Mockito.never; 39 import static org.mockito.Mockito.times; 40 import static org.mockito.Mockito.verify; 41 import static org.mockito.Mockito.when; 42 43 import android.adservices.common.CommonFixture; 44 import android.annotation.NonNull; 45 import android.content.Context; 46 import android.content.pm.PackageManager; 47 48 import androidx.room.Room; 49 import androidx.test.core.app.ApplicationProvider; 50 51 import com.android.adservices.LoggerFactory; 52 import com.android.adservices.common.AdServicesDeviceSupportedRule; 53 import com.android.adservices.concurrency.AdServicesExecutors; 54 import com.android.adservices.customaudience.DBCustomAudienceBackgroundFetchDataFixture; 55 import com.android.adservices.data.adselection.AppInstallDao; 56 import com.android.adservices.data.adselection.SharedStorageDatabase; 57 import com.android.adservices.data.customaudience.CustomAudienceDao; 58 import com.android.adservices.data.customaudience.CustomAudienceDatabase; 59 import com.android.adservices.data.customaudience.DBCustomAudience; 60 import com.android.adservices.data.customaudience.DBCustomAudienceBackgroundFetchData; 61 import com.android.adservices.data.enrollment.EnrollmentDao; 62 import com.android.adservices.service.FakeFlagsFactory; 63 import com.android.adservices.service.Flags; 64 import com.android.adservices.service.customaudience.BackgroundFetchRunner.UpdateResultType; 65 import com.android.adservices.service.stats.AdServicesLoggerImpl; 66 import com.android.adservices.service.stats.BackgroundFetchExecutionLogger; 67 import com.android.adservices.service.stats.CustomAudienceLoggerFactory; 68 import com.android.adservices.service.stats.UpdateCustomAudienceExecutionLogger; 69 import com.android.adservices.shared.testing.AnswerSyncCallback; 70 import com.android.adservices.shared.testing.concurrency.SimpleSyncCallback; 71 72 import com.google.common.util.concurrent.FluentFuture; 73 import com.google.common.util.concurrent.ListenableFuture; 74 75 import org.junit.Before; 76 import org.junit.Rule; 77 import org.junit.Test; 78 import org.mockito.Mock; 79 import org.mockito.Mockito; 80 import org.mockito.junit.MockitoJUnit; 81 import org.mockito.junit.MockitoRule; 82 83 import java.time.Clock; 84 import java.time.Instant; 85 import java.util.ArrayList; 86 import java.util.Arrays; 87 import java.util.List; 88 import java.util.concurrent.CountDownLatch; 89 import java.util.concurrent.ExecutionException; 90 import java.util.concurrent.ExecutorService; 91 import java.util.concurrent.Executors; 92 import java.util.concurrent.TimeUnit; 93 import java.util.concurrent.TimeoutException; 94 import java.util.concurrent.atomic.AtomicInteger; 95 96 public class BackgroundFetchWorkerTest { 97 private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger(); 98 private static final Context CONTEXT = ApplicationProvider.getApplicationContext(); 99 100 private final Flags mFlags = new BackgroundFetchWorkerTestFlags(true); 101 private final ExecutorService mExecutorService = Executors.newFixedThreadPool(8); 102 103 @Rule(order = 1) 104 public final AdServicesDeviceSupportedRule deviceSupportRule = 105 new AdServicesDeviceSupportedRule(); 106 107 @Rule(order = 2) 108 public final MockitoRule mockitoRule = MockitoJUnit.rule(); 109 110 @Mock private PackageManager mPackageManagerMock; 111 @Mock private EnrollmentDao mEnrollmentDaoMock; 112 @Mock private Clock mClockMock; 113 @Mock private AdServicesLoggerImpl mAdServicesLoggerImplMock; 114 @Mock private UpdateCustomAudienceExecutionLogger mUpdateCustomAudienceExecutionLoggerMock; 115 private BackgroundFetchExecutionLogger mBackgroundFetchExecutionLoggerSpy; 116 @Mock private CustomAudienceLoggerFactory mCustomAudienceLoggerFactoryMock; 117 118 private CustomAudienceDao mCustomAudienceDaoSpy; 119 private AppInstallDao mAppInstallDaoSpy; 120 private BackgroundFetchRunner mBackgroundFetchRunnerSpy; 121 private BackgroundFetchWorker mBackgroundFetchWorker; 122 123 @Before setup()124 public void setup() { 125 when(mCustomAudienceLoggerFactoryMock.getUpdateCustomAudienceExecutionLogger()) 126 .thenReturn(mUpdateCustomAudienceExecutionLoggerMock); 127 mBackgroundFetchExecutionLoggerSpy = 128 Mockito.spy( 129 new BackgroundFetchExecutionLogger( 130 com.android.adservices.shared.util.Clock.getInstance(), 131 mAdServicesLoggerImplMock)); 132 when(mCustomAudienceLoggerFactoryMock.getBackgroundFetchExecutionLogger()) 133 .thenReturn(mBackgroundFetchExecutionLoggerSpy); 134 135 mCustomAudienceDaoSpy = 136 Mockito.spy( 137 Room.inMemoryDatabaseBuilder(CONTEXT, CustomAudienceDatabase.class) 138 .addTypeConverter(new DBCustomAudience.Converters(true, true, true)) 139 .build() 140 .customAudienceDao()); 141 mAppInstallDaoSpy = 142 Mockito.spy( 143 Room.inMemoryDatabaseBuilder(CONTEXT, SharedStorageDatabase.class) 144 .build() 145 .appInstallDao()); 146 147 mBackgroundFetchRunnerSpy = 148 Mockito.spy( 149 new BackgroundFetchRunner( 150 mCustomAudienceDaoSpy, 151 mAppInstallDaoSpy, 152 mPackageManagerMock, 153 mEnrollmentDaoMock, 154 mFlags, 155 mCustomAudienceLoggerFactoryMock)); 156 157 mBackgroundFetchWorker = 158 new BackgroundFetchWorker( 159 mCustomAudienceDaoSpy, 160 mFlags, 161 mBackgroundFetchRunnerSpy, 162 mClockMock, 163 mCustomAudienceLoggerFactoryMock); 164 } 165 166 @Test testBackgroundFetchWorkerNullInputsCauseFailure()167 public void testBackgroundFetchWorkerNullInputsCauseFailure() { 168 assertThrows( 169 NullPointerException.class, 170 () -> 171 new BackgroundFetchWorker( 172 null, 173 FakeFlagsFactory.getFlagsForTest(), 174 mBackgroundFetchRunnerSpy, 175 mClockMock, 176 mCustomAudienceLoggerFactoryMock)); 177 178 assertThrows( 179 NullPointerException.class, 180 () -> 181 new BackgroundFetchWorker( 182 mCustomAudienceDaoSpy, 183 null, 184 mBackgroundFetchRunnerSpy, 185 mClockMock, 186 mCustomAudienceLoggerFactoryMock)); 187 188 assertThrows( 189 NullPointerException.class, 190 () -> 191 new BackgroundFetchWorker( 192 mCustomAudienceDaoSpy, 193 FakeFlagsFactory.getFlagsForTest(), 194 null, 195 mClockMock, 196 mCustomAudienceLoggerFactoryMock)); 197 198 assertThrows( 199 NullPointerException.class, 200 () -> 201 new BackgroundFetchWorker( 202 mCustomAudienceDaoSpy, 203 FakeFlagsFactory.getFlagsForTest(), 204 mBackgroundFetchRunnerSpy, 205 null, 206 mCustomAudienceLoggerFactoryMock)); 207 assertThrows( 208 NullPointerException.class, 209 () -> 210 new BackgroundFetchWorker( 211 mCustomAudienceDaoSpy, 212 FakeFlagsFactory.getFlagsForTest(), 213 mBackgroundFetchRunnerSpy, 214 mClockMock, 215 null)); 216 } 217 218 @Test testRunBackgroundFetchThrowsTimeoutDuringUpdates()219 public void testRunBackgroundFetchThrowsTimeoutDuringUpdates() throws InterruptedException { 220 class FlagsWithSmallTimeout implements Flags { 221 @Override 222 public long getFledgeBackgroundFetchJobMaxRuntimeMs() { 223 return 100L; 224 } 225 } 226 227 class BackgroundFetchRunnerWithSleep extends BackgroundFetchRunner { 228 BackgroundFetchRunnerWithSleep( 229 @NonNull CustomAudienceDao customAudienceDao, @NonNull Flags flags) { 230 super( 231 customAudienceDao, 232 mAppInstallDaoSpy, 233 mPackageManagerMock, 234 mEnrollmentDaoMock, 235 flags, 236 mCustomAudienceLoggerFactoryMock); 237 } 238 239 @Override 240 public void deleteExpiredCustomAudiences(@NonNull Instant jobStartTime) { 241 // Do nothing 242 } 243 244 @Override 245 public FluentFuture<UpdateResultType> updateCustomAudience( 246 @NonNull Instant jobStartTime, 247 @NonNull DBCustomAudienceBackgroundFetchData fetchData) { 248 249 return FluentFuture.from( 250 AdServicesExecutors.getBlockingExecutor() 251 .submit( 252 () -> { 253 try { 254 Thread.sleep(500L); 255 } catch (InterruptedException e) { 256 e.printStackTrace(); 257 } 258 return null; 259 })); 260 } 261 } 262 263 Flags flagsWithSmallTimeout = new FlagsWithSmallTimeout(); 264 BackgroundFetchRunner backgroundFetchRunnerWithSleep = 265 new BackgroundFetchRunnerWithSleep(mCustomAudienceDaoSpy, flagsWithSmallTimeout); 266 BackgroundFetchWorker backgroundFetchWorkerThatTimesOut = 267 new BackgroundFetchWorker( 268 mCustomAudienceDaoSpy, 269 flagsWithSmallTimeout, 270 backgroundFetchRunnerWithSleep, 271 mClockMock, 272 mCustomAudienceLoggerFactoryMock); 273 274 // Mock a custom audience eligible for update 275 DBCustomAudienceBackgroundFetchData fetchData = 276 DBCustomAudienceBackgroundFetchDataFixture.getValidBuilderByBuyer( 277 CommonFixture.VALID_BUYER_1) 278 .setEligibleUpdateTime(CommonFixture.FIXED_NOW) 279 .build(); 280 List<DBCustomAudienceBackgroundFetchData> fetchDataList = Arrays.asList(fetchData); 281 doReturn(fetchDataList) 282 .when(mCustomAudienceDaoSpy) 283 .getActiveEligibleCustomAudienceBackgroundFetchData(any(), anyLong()); 284 285 when(mClockMock.instant()).thenReturn(Instant.now()); 286 287 // Ensure that log is closed after timeout. 288 CountDownLatch closeLoggerLatch = new CountDownLatch(1); 289 doAnswer( 290 unusedInovcation -> { 291 closeLoggerLatch.countDown(); 292 return null; 293 }) 294 .when(mBackgroundFetchExecutionLoggerSpy) 295 .close(anyInt(), anyInt()); 296 297 // Time out while updating custom audiences 298 ExecutionException expected = 299 assertThrows( 300 ExecutionException.class, 301 () -> backgroundFetchWorkerThatTimesOut.runBackgroundFetch().get()); 302 assertThat(expected.getCause()).isInstanceOf(TimeoutException.class); 303 // Wait for logger to close. 304 assertThat(closeLoggerLatch.await(200, TimeUnit.MILLISECONDS)).isTrue(); 305 // Test can timeout during cleanup also hence only verify internal error due to failure in 306 // runner. 307 verify(mBackgroundFetchExecutionLoggerSpy).close(anyInt(), eq(STATUS_INTERNAL_ERROR)); 308 } 309 310 @Test 311 public void testRunBackgroundFetchNothingToUpdate() 312 throws ExecutionException, InterruptedException { 313 assertTrue( 314 mCustomAudienceDaoSpy 315 .getActiveEligibleCustomAudienceBackgroundFetchData( 316 CommonFixture.FIXED_NOW, 1) 317 .isEmpty()); 318 319 CountDownLatch latchForExecutionLoggerClose = new CountDownLatch(1); 320 setLatchToCountdownOnLogClose(latchForExecutionLoggerClose); 321 when(mClockMock.instant()).thenReturn(CommonFixture.FIXED_NOW); 322 323 mBackgroundFetchWorker.runBackgroundFetch().get(); 324 325 // ensures that we verify only after BackgroundExecuterLoggerClose#close is executed. 326 latchForExecutionLoggerClose.await(); 327 verify(mBackgroundFetchRunnerSpy).deleteExpiredCustomAudiences(any()); 328 verify(mCustomAudienceDaoSpy).deleteAllExpiredCustomAudienceData(any()); 329 verify(mBackgroundFetchRunnerSpy).deleteDisallowedOwnerCustomAudiences(); 330 verify(mCustomAudienceDaoSpy).deleteAllDisallowedOwnerCustomAudienceData(any(), any()); 331 verify(mBackgroundFetchRunnerSpy).deleteDisallowedBuyerCustomAudiences(); 332 verify(mBackgroundFetchRunnerSpy).deleteDisallowedPackageAppInstallEntries(); 333 verify(mCustomAudienceDaoSpy).deleteAllDisallowedBuyerCustomAudienceData(any(), any()); 334 verify(mBackgroundFetchRunnerSpy, never()).updateCustomAudience(any(), any()); 335 verify(mBackgroundFetchExecutionLoggerSpy).close(0, STATUS_SUCCESS); 336 } 337 338 @Test 339 public void testRunBackgroundFetchNothingToUpdateNoFilters() 340 throws ExecutionException, InterruptedException { 341 Flags flagsFilteringDisabled = new BackgroundFetchWorkerTestFlags(false); 342 mBackgroundFetchWorker = 343 new BackgroundFetchWorker( 344 mCustomAudienceDaoSpy, 345 flagsFilteringDisabled, 346 mBackgroundFetchRunnerSpy, 347 mClockMock, 348 mCustomAudienceLoggerFactoryMock); 349 assertTrue( 350 mCustomAudienceDaoSpy 351 .getActiveEligibleCustomAudienceBackgroundFetchData( 352 CommonFixture.FIXED_NOW, 1) 353 .isEmpty()); 354 355 CountDownLatch latchForExecutionLoggerClose = new CountDownLatch(1); 356 setLatchToCountdownOnLogClose(latchForExecutionLoggerClose); 357 when(mClockMock.instant()).thenReturn(CommonFixture.FIXED_NOW); 358 359 mBackgroundFetchWorker.runBackgroundFetch().get(); 360 361 // ensures that we verify only after BackgroundExecuterLoggerClose#close is executed. 362 latchForExecutionLoggerClose.await(); 363 verify(mBackgroundFetchRunnerSpy).deleteExpiredCustomAudiences(any()); 364 verify(mCustomAudienceDaoSpy).deleteAllExpiredCustomAudienceData(any()); 365 verify(mBackgroundFetchRunnerSpy).deleteDisallowedOwnerCustomAudiences(); 366 verify(mCustomAudienceDaoSpy).deleteAllDisallowedOwnerCustomAudienceData(any(), any()); 367 verify(mBackgroundFetchRunnerSpy).deleteDisallowedBuyerCustomAudiences(); 368 verify(mBackgroundFetchRunnerSpy, times(0)).deleteDisallowedPackageAppInstallEntries(); 369 verify(mCustomAudienceDaoSpy).deleteAllDisallowedBuyerCustomAudienceData(any(), any()); 370 verify(mBackgroundFetchRunnerSpy, never()).updateCustomAudience(any(), any()); 371 verify(mBackgroundFetchExecutionLoggerSpy).close(0, STATUS_SUCCESS); 372 } 373 374 @Test 375 public void testRunBackgroundFetchUpdateOneCustomAudience() 376 throws ExecutionException, InterruptedException { 377 // Mock a single custom audience eligible for update 378 DBCustomAudienceBackgroundFetchData fetchData = 379 DBCustomAudienceBackgroundFetchDataFixture.getValidBuilderByBuyer( 380 CommonFixture.VALID_BUYER_1) 381 .setEligibleUpdateTime(CommonFixture.FIXED_NOW) 382 .build(); 383 List<DBCustomAudienceBackgroundFetchData> fetchDataList = Arrays.asList(fetchData); 384 doReturn(fetchDataList) 385 .when(mCustomAudienceDaoSpy) 386 .getActiveEligibleCustomAudienceBackgroundFetchData(any(), anyLong()); 387 doReturn(FluentFuture.from(immediateFuture(null))) 388 .when(mBackgroundFetchRunnerSpy) 389 .updateCustomAudience(any(), any()); 390 391 CountDownLatch latchForExecutionLoggerClose = new CountDownLatch(1); 392 setLatchToCountdownOnLogClose(latchForExecutionLoggerClose); 393 when(mClockMock.instant()).thenReturn(CommonFixture.FIXED_NOW); 394 mBackgroundFetchWorker.runBackgroundFetch().get(); 395 396 // ensures that we verify only after BackgroundExecuterLoggerClose#close is executed. 397 latchForExecutionLoggerClose.await(); 398 verify(mBackgroundFetchRunnerSpy).deleteExpiredCustomAudiences(any()); 399 verify(mCustomAudienceDaoSpy).deleteAllExpiredCustomAudienceData(any()); 400 verify(mBackgroundFetchRunnerSpy).deleteDisallowedOwnerCustomAudiences(); 401 verify(mCustomAudienceDaoSpy).deleteAllDisallowedOwnerCustomAudienceData(any(), any()); 402 verify(mBackgroundFetchRunnerSpy).deleteDisallowedBuyerCustomAudiences(); 403 verify(mBackgroundFetchRunnerSpy).deleteDisallowedPackageAppInstallEntries(); 404 verify(mCustomAudienceDaoSpy).deleteAllDisallowedBuyerCustomAudienceData(any(), any()); 405 verify(mBackgroundFetchRunnerSpy).updateCustomAudience(any(), any()); 406 verify(mBackgroundFetchExecutionLoggerSpy).close(fetchDataList.size(), STATUS_SUCCESS); 407 } 408 409 @Test 410 public void testRunBackgroundFetchUpdateCustomAudiences() 411 throws ExecutionException, InterruptedException { 412 int numEligibleCustomAudiences = 12; 413 414 // Mock a list of custom audiences eligible for update 415 DBCustomAudienceBackgroundFetchData.Builder fetchDataBuilder = 416 DBCustomAudienceBackgroundFetchDataFixture.getValidBuilderByBuyer( 417 CommonFixture.VALID_BUYER_1) 418 .setEligibleUpdateTime(CommonFixture.FIXED_NOW); 419 List<DBCustomAudienceBackgroundFetchData> fetchDataList = new ArrayList<>(); 420 for (int i = 0; i < numEligibleCustomAudiences; i++) { 421 fetchDataList.add(fetchDataBuilder.setName("ca" + i).build()); 422 } 423 424 doReturn(fetchDataList) 425 .when(mCustomAudienceDaoSpy) 426 .getActiveEligibleCustomAudienceBackgroundFetchData(any(), anyLong()); 427 doReturn(FluentFuture.from(immediateFuture(null))) 428 .when(mBackgroundFetchRunnerSpy) 429 .updateCustomAudience(any(), any()); 430 431 when(mClockMock.instant()).thenReturn(CommonFixture.FIXED_NOW); 432 CountDownLatch latchForExecutionLoggerClose = new CountDownLatch(1); 433 setLatchToCountdownOnLogClose(latchForExecutionLoggerClose); 434 mBackgroundFetchWorker.runBackgroundFetch().get(); 435 436 // ensures that we verify only after BackgroundExecuterLoggerClose#close is executed. 437 latchForExecutionLoggerClose.await(); 438 verify(mBackgroundFetchRunnerSpy).deleteExpiredCustomAudiences(any()); 439 verify(mCustomAudienceDaoSpy).deleteAllExpiredCustomAudienceData(any()); 440 verify(mBackgroundFetchRunnerSpy).deleteDisallowedOwnerCustomAudiences(); 441 verify(mCustomAudienceDaoSpy).deleteAllDisallowedOwnerCustomAudienceData(any(), any()); 442 verify(mBackgroundFetchRunnerSpy).deleteDisallowedBuyerCustomAudiences(); 443 verify(mBackgroundFetchRunnerSpy).deleteDisallowedPackageAppInstallEntries(); 444 verify(mCustomAudienceDaoSpy).deleteAllDisallowedBuyerCustomAudienceData(any(), any()); 445 verify(mBackgroundFetchRunnerSpy, times(numEligibleCustomAudiences)) 446 .updateCustomAudience(any(), any()); 447 verify(mBackgroundFetchExecutionLoggerSpy).close(fetchDataList.size(), STATUS_SUCCESS); 448 } 449 450 @Test 451 public void testRunBackgroundFetchChecksWorkInProgress() 452 throws InterruptedException, ExecutionException { 453 int numEligibleCustomAudiences = 16; 454 CountDownLatch partialCompletionLatch = new CountDownLatch(numEligibleCustomAudiences / 4); 455 456 // Mock a list of custom audiences eligible for update 457 DBCustomAudienceBackgroundFetchData.Builder fetchDataBuilder = 458 DBCustomAudienceBackgroundFetchDataFixture.getValidBuilderByBuyer( 459 CommonFixture.VALID_BUYER_1) 460 .setEligibleUpdateTime(CommonFixture.FIXED_NOW); 461 List<DBCustomAudienceBackgroundFetchData> fetchDataList = new ArrayList<>(); 462 for (int i = 0; i < numEligibleCustomAudiences; i++) { 463 fetchDataList.add(fetchDataBuilder.setName("ca" + i).build()); 464 } 465 466 doReturn(fetchDataList) 467 .when(mCustomAudienceDaoSpy) 468 .getActiveEligibleCustomAudienceBackgroundFetchData(any(), anyLong()); 469 doAnswer( 470 unusedInvocation -> { 471 Thread.sleep(100); 472 partialCompletionLatch.countDown(); 473 return FluentFuture.from(immediateFuture(null)); 474 }) 475 .when(mBackgroundFetchRunnerSpy) 476 .updateCustomAudience(any(), any()); 477 478 // ensures that we verify only after BackgroundExecuterLoggerClose#close is executed. 479 CountDownLatch latchForExecutionLoggerClose = new CountDownLatch(1); 480 setLatchToCountdownOnLogClose(latchForExecutionLoggerClose); 481 when(mClockMock.instant()).thenReturn(CommonFixture.FIXED_NOW); 482 483 CountDownLatch bgfWorkStoppedLatch = new CountDownLatch(1); 484 mExecutorService.execute( 485 () -> { 486 try { 487 mBackgroundFetchWorker.runBackgroundFetch().get(); 488 } catch (Exception exception) { 489 sLogger.e( 490 exception, "Exception encountered while running background fetch"); 491 } finally { 492 bgfWorkStoppedLatch.countDown(); 493 } 494 }); 495 496 // Wait til updates are partially complete, then try running background fetch again and 497 // verify nothing is done 498 partialCompletionLatch.await(); 499 when(mClockMock.instant()).thenReturn(CommonFixture.FIXED_NOW.plusSeconds(1)); 500 mBackgroundFetchWorker.runBackgroundFetch().get(); 501 502 bgfWorkStoppedLatch.await(); 503 latchForExecutionLoggerClose.await(); 504 verify(mBackgroundFetchRunnerSpy).deleteExpiredCustomAudiences(any()); 505 verify(mCustomAudienceDaoSpy).deleteAllExpiredCustomAudienceData(any()); 506 verify(mBackgroundFetchRunnerSpy).deleteDisallowedOwnerCustomAudiences(); 507 verify(mCustomAudienceDaoSpy).deleteAllDisallowedOwnerCustomAudienceData(any(), any()); 508 verify(mBackgroundFetchRunnerSpy).deleteDisallowedBuyerCustomAudiences(); 509 verify(mBackgroundFetchRunnerSpy).deleteDisallowedPackageAppInstallEntries(); 510 verify(mCustomAudienceDaoSpy).deleteAllDisallowedBuyerCustomAudienceData(any(), any()); 511 verify(mBackgroundFetchRunnerSpy, times(numEligibleCustomAudiences)) 512 .updateCustomAudience(any(), any()); 513 verify(mBackgroundFetchExecutionLoggerSpy).close(fetchDataList.size(), STATUS_SUCCESS); 514 } 515 516 @Test 517 public void testStopWorkWithoutRunningFetchDoesNothing() { 518 // Verify no errors/exceptions thrown when no work in progress 519 mBackgroundFetchWorker.stopWork(); 520 } 521 522 @Test 523 public void testStopWorkGracefullyStopsBackgroundFetch() throws Exception { 524 int numEligibleCustomAudiences = 16; 525 CountDownLatch partialCompletionLatch = new CountDownLatch(numEligibleCustomAudiences / 4); 526 527 // Mock a list of custom audiences eligible for update 528 DBCustomAudienceBackgroundFetchData.Builder fetchDataBuilder = 529 DBCustomAudienceBackgroundFetchDataFixture.getValidBuilderByBuyer( 530 CommonFixture.VALID_BUYER_1) 531 .setEligibleUpdateTime(CommonFixture.FIXED_NOW); 532 List<DBCustomAudienceBackgroundFetchData> fetchDataList = new ArrayList<>(); 533 for (int i = 0; i < numEligibleCustomAudiences; i++) { 534 fetchDataList.add(fetchDataBuilder.setName("ca" + i).build()); 535 } 536 537 doReturn(fetchDataList) 538 .when(mCustomAudienceDaoSpy) 539 .getActiveEligibleCustomAudienceBackgroundFetchData(any(), anyLong()); 540 doAnswer( 541 unusedInvocation -> { 542 Thread.sleep(100); 543 partialCompletionLatch.countDown(); 544 return FluentFuture.from(immediateVoidFuture()); 545 }) 546 .when(mBackgroundFetchRunnerSpy) 547 .updateCustomAudience(any(), any()); 548 549 // Ensure that logger is closed after stopping work. 550 CountDownLatch closeLoggerLatch = new CountDownLatch(1); 551 doAnswer( 552 unusedInovcation -> { 553 closeLoggerLatch.countDown(); 554 return null; 555 }) 556 .when(mBackgroundFetchExecutionLoggerSpy) 557 .close(anyInt(), anyInt()); 558 559 when(mClockMock.instant()).thenReturn(CommonFixture.FIXED_NOW); 560 561 ListenableFuture<Void> backgroundFetchResult = mBackgroundFetchWorker.runBackgroundFetch(); 562 563 // Wait til updates are partially complete, then try stopping background fetch 564 partialCompletionLatch.await(); 565 mBackgroundFetchWorker.stopWork(); 566 // stopWork() should notify to the worker that the work should end so the future 567 // should complete within the time required to update the custom audiences 568 backgroundFetchResult.get( 569 100 * (numEligibleCustomAudiences * 3 / 4) + 100, TimeUnit.SECONDS); 570 // Wait for logger to close. 571 assertThat(closeLoggerLatch.await(200, TimeUnit.MILLISECONDS)).isTrue(); 572 verify(mBackgroundFetchExecutionLoggerSpy) 573 .close(numEligibleCustomAudiences, STATUS_SUCCESS); 574 } 575 576 @Test 577 public void testStopWorkPreemptsDataUpdates() throws Exception { 578 int numEligibleCustomAudiences = 16; 579 CountDownLatch beforeUpdatingCasLatch = new CountDownLatch(numEligibleCustomAudiences / 4); 580 581 // Mock a list of custom audiences eligible for update 582 DBCustomAudienceBackgroundFetchData.Builder fetchDataBuilder = 583 DBCustomAudienceBackgroundFetchDataFixture.getValidBuilderByBuyer( 584 CommonFixture.VALID_BUYER_1) 585 .setEligibleUpdateTime(CommonFixture.FIXED_NOW); 586 List<DBCustomAudienceBackgroundFetchData> fetchDataList = new ArrayList<>(); 587 for (int i = 0; i < numEligibleCustomAudiences; i++) { 588 fetchDataList.add(fetchDataBuilder.setName("ca" + i).build()); 589 } 590 591 // Ensuring that stopWork is called before the data update process 592 doAnswer( 593 unusedInvocation -> { 594 beforeUpdatingCasLatch.await(); 595 return fetchDataList; 596 }) 597 .when(mCustomAudienceDaoSpy) 598 .getActiveEligibleCustomAudienceBackgroundFetchData(any(), anyLong()); 599 doAnswer( 600 unusedInvocation -> { 601 Thread.sleep(100); 602 return null; 603 }) 604 .when(mBackgroundFetchRunnerSpy) 605 .updateCustomAudience(any(), any()); 606 607 // Ensure that logger is closed after stopping work. 608 CountDownLatch closeLoggerLatch = new CountDownLatch(1); 609 doAnswer( 610 unusedInovcation -> { 611 closeLoggerLatch.countDown(); 612 return null; 613 }) 614 .when(mBackgroundFetchExecutionLoggerSpy) 615 .close(anyInt(), anyInt()); 616 617 when(mClockMock.instant()).thenReturn(CommonFixture.FIXED_NOW); 618 619 ListenableFuture<Void> backgroundFetchResult = mBackgroundFetchWorker.runBackgroundFetch(); 620 621 // Wait til updates are partially complete, then try stopping background fetch 622 mBackgroundFetchWorker.stopWork(); 623 beforeUpdatingCasLatch.countDown(); 624 // stopWork() called before updating the data should cause immediate termination 625 // waiting for 200ms to handle thread scheduling delays. 626 // The important check is that the time is less than the time of updating all CAs 627 backgroundFetchResult.get(200, TimeUnit.MILLISECONDS); 628 // Wait for logger to close. 629 assertThat(closeLoggerLatch.await(200, TimeUnit.MILLISECONDS)).isTrue(); 630 verify(mBackgroundFetchExecutionLoggerSpy).close(0, STATUS_SUCCESS); 631 } 632 633 @Test 634 public void testRunBackgroundFetchInSequence() throws InterruptedException, ExecutionException { 635 int numEligibleCustomAudiences = 16; 636 int expectedUpdateCustomAudienceCalls = numEligibleCustomAudiences / 2; 637 638 // Mock two lists of custom audiences eligible for update 639 DBCustomAudienceBackgroundFetchData.Builder fetchDataBuilder = 640 DBCustomAudienceBackgroundFetchDataFixture.getValidBuilderByBuyer( 641 CommonFixture.VALID_BUYER_1) 642 .setEligibleUpdateTime(CommonFixture.FIXED_NOW); 643 List<DBCustomAudienceBackgroundFetchData> fetchDataList1 = new ArrayList<>(); 644 List<DBCustomAudienceBackgroundFetchData> fetchDataList2 = new ArrayList<>(); 645 for (int i = 0; i < numEligibleCustomAudiences; i++) { 646 DBCustomAudienceBackgroundFetchData fetchData = 647 fetchDataBuilder.setName("ca" + i).build(); 648 if (i < expectedUpdateCustomAudienceCalls) { 649 fetchDataList1.add(fetchData); 650 } else { 651 fetchDataList2.add(fetchData); 652 } 653 } 654 655 // Count the number of times updateCustomAudience is run 656 AtomicInteger completionCount = new AtomicInteger(0); 657 658 // Return the first list the first time, and the second list in the second call 659 AnswerSyncCallback<FluentFuture<UpdateResultType>> updateCustomAudienceCallback = 660 AnswerSyncCallback.forMultipleAnswers( 661 FluentFuture.from(immediateFuture(null)), 662 expectedUpdateCustomAudienceCalls); 663 664 doReturn(fetchDataList1) 665 .doReturn(fetchDataList2) 666 .when(mCustomAudienceDaoSpy) 667 .getActiveEligibleCustomAudienceBackgroundFetchData(any(), anyLong()); 668 doAnswer(updateCustomAudienceCallback) 669 .when(mBackgroundFetchRunnerSpy) 670 .updateCustomAudience(any(), any()); 671 672 when(mClockMock.instant()).thenReturn(CommonFixture.FIXED_NOW); 673 674 // TODO(b/321743128): Clarify the production behavior for the logger and use existed mocking 675 // method setLatchToCountdownOnLogClose(). 676 // 677 // Verify the invocations of the logging events. 678 AnswerSyncCallback<Void> loggerCloseCallback = 679 AnswerSyncCallback.forMultipleVoidAnswers(/* numberOfExpectedCalls= */ 2); 680 doAnswer(loggerCloseCallback) 681 .when(mBackgroundFetchExecutionLoggerSpy) 682 .close(anyInt(), anyInt()); 683 684 SimpleSyncCallback bgfWorkStoppedCallback = new SimpleSyncCallback(); 685 mExecutorService.execute( 686 () -> { 687 try { 688 mBackgroundFetchWorker.runBackgroundFetch().get(); 689 } catch (Exception exception) { 690 sLogger.e( 691 exception, "Exception encountered while running background fetch"); 692 } finally { 693 bgfWorkStoppedCallback.setCalled(); 694 } 695 }); 696 697 // Wait til updates are complete, then try running background fetch again and 698 // verify the second run updates more custom audiences successfully 699 updateCustomAudienceCallback.assertCalled(); 700 bgfWorkStoppedCallback.assertCalled(); 701 when(mClockMock.instant()).thenReturn(CommonFixture.FIXED_NOW.plusSeconds(1)); 702 mBackgroundFetchWorker.runBackgroundFetch().get(); 703 704 verify(mBackgroundFetchRunnerSpy, times(2)).deleteExpiredCustomAudiences(any()); 705 verify(mCustomAudienceDaoSpy, times(2)).deleteAllExpiredCustomAudienceData(any()); 706 verify(mBackgroundFetchRunnerSpy, times(2)).deleteDisallowedOwnerCustomAudiences(); 707 verify(mCustomAudienceDaoSpy, times(2)) 708 .deleteAllDisallowedOwnerCustomAudienceData(any(), any()); 709 verify(mBackgroundFetchRunnerSpy, times(2)).deleteDisallowedBuyerCustomAudiences(); 710 verify(mBackgroundFetchRunnerSpy, times(2)).deleteDisallowedPackageAppInstallEntries(); 711 verify(mCustomAudienceDaoSpy, times(2)) 712 .deleteAllDisallowedBuyerCustomAudienceData(any(), any()); 713 verify(mBackgroundFetchRunnerSpy, times(numEligibleCustomAudiences)) 714 .updateCustomAudience(any(), any()); 715 assertThat(updateCustomAudienceCallback.getNumberActualCalls()) 716 .isEqualTo(numEligibleCustomAudiences); 717 loggerCloseCallback.assertCalled(); 718 } 719 720 @Test 721 public void testRunBackgroundFetchHandlesLoggerCloseError() 722 throws ExecutionException, InterruptedException { 723 // Mock a single custom audience eligible for update 724 DBCustomAudienceBackgroundFetchData fetchData = 725 DBCustomAudienceBackgroundFetchDataFixture.getValidBuilderByBuyer( 726 CommonFixture.VALID_BUYER_1) 727 .setEligibleUpdateTime(CommonFixture.FIXED_NOW) 728 .build(); 729 List<DBCustomAudienceBackgroundFetchData> fetchDataList = Arrays.asList(fetchData); 730 doReturn(fetchDataList) 731 .when(mCustomAudienceDaoSpy) 732 .getActiveEligibleCustomAudienceBackgroundFetchData(any(), anyLong()); 733 doReturn(FluentFuture.from(immediateFuture(null))) 734 .when(mBackgroundFetchRunnerSpy) 735 .updateCustomAudience(any(), any()); 736 737 when(mClockMock.instant()).thenReturn(CommonFixture.FIXED_NOW); 738 739 doThrow(new IllegalStateException()) 740 .when(mBackgroundFetchExecutionLoggerSpy) 741 .close(anyInt(), anyInt()); 742 743 // Background fetch should complete without error 744 mBackgroundFetchWorker.runBackgroundFetch().get(); 745 } 746 747 private void setLatchToCountdownOnLogClose(CountDownLatch latch) { 748 doAnswer( 749 unusedInvocation -> { 750 latch.countDown(); 751 return null; 752 }) 753 .when(mAdServicesLoggerImplMock) 754 .logBackgroundFetchProcessReportedStats(any()); 755 } 756 757 private static class BackgroundFetchWorkerTestFlags implements Flags { 758 private final boolean mFledgeAppInstallFilteringEnabled; 759 760 BackgroundFetchWorkerTestFlags(boolean fledgeAppInstallFilteringEnabled) { 761 mFledgeAppInstallFilteringEnabled = fledgeAppInstallFilteringEnabled; 762 } 763 764 @Override 765 public int getFledgeBackgroundFetchThreadPoolSize() { 766 return 4; 767 } 768 769 @Override 770 public boolean getFledgeAppInstallFilteringEnabled() { 771 return mFledgeAppInstallFilteringEnabled; 772 } 773 774 @Override 775 public int getFledgeBackgroundFetchNetworkConnectTimeoutMs() { 776 return EXTENDED_FLEDGE_BACKGROUND_FETCH_NETWORK_CONNECT_TIMEOUT_MS; 777 } 778 779 @Override 780 public int getFledgeBackgroundFetchNetworkReadTimeoutMs() { 781 return EXTENDED_FLEDGE_BACKGROUND_FETCH_NETWORK_READ_TIMEOUT_MS; 782 } 783 } 784 } 785