blob: 3f3e699ac68c4d83ef4fc8ffa28a388a8bd31221 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.adservices.service.customaudience;
import static com.android.adservices.service.PhFlagsFixture.EXTENDED_FLEDGE_BACKGROUND_FETCH_NETWORK_CONNECT_TIMEOUT_MS;
import static com.android.adservices.service.PhFlagsFixture.EXTENDED_FLEDGE_BACKGROUND_FETCH_NETWORK_READ_TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.Futures.immediateFuture;
import static com.google.common.util.concurrent.Futures.immediateVoidFuture;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.adservices.common.CommonFixture;
import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.PackageManager;
import androidx.room.Room;
import androidx.test.core.app.ApplicationProvider;
import com.android.adservices.LoggerFactory;
import com.android.adservices.concurrency.AdServicesExecutors;
import com.android.adservices.customaudience.DBCustomAudienceBackgroundFetchDataFixture;
import com.android.adservices.data.adselection.AppInstallDao;
import com.android.adservices.data.adselection.SharedStorageDatabase;
import com.android.adservices.data.customaudience.CustomAudienceDao;
import com.android.adservices.data.customaudience.CustomAudienceDatabase;
import com.android.adservices.data.customaudience.DBCustomAudience;
import com.android.adservices.data.customaudience.DBCustomAudienceBackgroundFetchData;
import com.android.adservices.data.enrollment.EnrollmentDao;
import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
import com.google.common.util.concurrent.FluentFuture;
import com.google.common.util.concurrent.ListenableFuture;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.time.Clock;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
public class BackgroundFetchWorkerTest {
private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger();
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
private final Flags mFlags = new BackgroundFetchWorkerTestFlags(true);
private final ExecutorService mExecutorService = Executors.newFixedThreadPool(8);
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock private PackageManager mPackageManagerMock;
@Mock private EnrollmentDao mEnrollmentDaoMock;
@Mock private Clock mClock;
private CustomAudienceDao mCustomAudienceDaoSpy;
private AppInstallDao mAppInstallDaoSpy;
private BackgroundFetchRunner mBackgroundFetchRunnerSpy;
private BackgroundFetchWorker mBackgroundFetchWorker;
@Before
public void setup() {
mCustomAudienceDaoSpy =
Mockito.spy(
Room.inMemoryDatabaseBuilder(CONTEXT, CustomAudienceDatabase.class)
.addTypeConverter(new DBCustomAudience.Converters(true))
.build()
.customAudienceDao());
mAppInstallDaoSpy =
Mockito.spy(
Room.inMemoryDatabaseBuilder(CONTEXT, SharedStorageDatabase.class)
.build()
.appInstallDao());
mBackgroundFetchRunnerSpy =
Mockito.spy(
new BackgroundFetchRunner(
mCustomAudienceDaoSpy,
mAppInstallDaoSpy,
mPackageManagerMock,
mEnrollmentDaoMock,
mFlags));
mBackgroundFetchWorker =
new BackgroundFetchWorker(
mCustomAudienceDaoSpy, mFlags, mBackgroundFetchRunnerSpy, mClock);
}
@Test
public void testBackgroundFetchWorkerNullInputsCauseFailure() {
assertThrows(
NullPointerException.class,
() ->
new BackgroundFetchWorker(
null,
FlagsFactory.getFlagsForTest(),
mBackgroundFetchRunnerSpy,
mClock));
assertThrows(
NullPointerException.class,
() ->
new BackgroundFetchWorker(
mCustomAudienceDaoSpy, null, mBackgroundFetchRunnerSpy, mClock));
assertThrows(
NullPointerException.class,
() ->
new BackgroundFetchWorker(
mCustomAudienceDaoSpy,
FlagsFactory.getFlagsForTest(),
null,
mClock));
assertThrows(
NullPointerException.class,
() ->
new BackgroundFetchWorker(
mCustomAudienceDaoSpy,
FlagsFactory.getFlagsForTest(),
mBackgroundFetchRunnerSpy,
null));
}
@Test
public void testRunBackgroundFetchThrowsTimeoutDuringUpdates() {
class FlagsWithSmallTimeout implements Flags {
@Override
public long getFledgeBackgroundFetchJobMaxRuntimeMs() {
return 100L;
}
}
class BackgroundFetchRunnerWithSleep extends BackgroundFetchRunner {
BackgroundFetchRunnerWithSleep(
@NonNull CustomAudienceDao customAudienceDao, @NonNull Flags flags) {
super(
customAudienceDao,
mAppInstallDaoSpy,
mPackageManagerMock,
mEnrollmentDaoMock,
flags);
}
@Override
public void deleteExpiredCustomAudiences(@NonNull Instant jobStartTime) {
// Do nothing
}
@Override
public FluentFuture<?> updateCustomAudience(
@NonNull Instant jobStartTime,
@NonNull DBCustomAudienceBackgroundFetchData fetchData) {
return FluentFuture.from(
AdServicesExecutors.getBlockingExecutor()
.submit(
() -> {
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}));
}
}
Flags flagsWithSmallTimeout = new FlagsWithSmallTimeout();
BackgroundFetchRunner backgroundFetchRunnerWithSleep =
new BackgroundFetchRunnerWithSleep(mCustomAudienceDaoSpy, flagsWithSmallTimeout);
BackgroundFetchWorker backgroundFetchWorkerThatTimesOut =
new BackgroundFetchWorker(
mCustomAudienceDaoSpy,
flagsWithSmallTimeout,
backgroundFetchRunnerWithSleep,
mClock);
// Mock a custom audience eligible for update
DBCustomAudienceBackgroundFetchData fetchData =
DBCustomAudienceBackgroundFetchDataFixture.getValidBuilderByBuyer(
CommonFixture.VALID_BUYER_1)
.setEligibleUpdateTime(CommonFixture.FIXED_NOW)
.build();
doReturn(Arrays.asList(fetchData))
.when(mCustomAudienceDaoSpy)
.getActiveEligibleCustomAudienceBackgroundFetchData(any(), anyLong());
when(mClock.instant()).thenReturn(Instant.now());
// Time out while updating custom audiences
ExecutionException expected =
assertThrows(
ExecutionException.class,
() -> backgroundFetchWorkerThatTimesOut.runBackgroundFetch().get());
assertThat(expected.getCause()).isInstanceOf(TimeoutException.class);
}
@Test
public void testRunBackgroundFetchNothingToUpdate()
throws ExecutionException, InterruptedException {
assertTrue(
mCustomAudienceDaoSpy
.getActiveEligibleCustomAudienceBackgroundFetchData(
CommonFixture.FIXED_NOW, 1)
.isEmpty());
when(mClock.instant()).thenReturn(CommonFixture.FIXED_NOW);
mBackgroundFetchWorker.runBackgroundFetch().get();
verify(mBackgroundFetchRunnerSpy).deleteExpiredCustomAudiences(any());
verify(mCustomAudienceDaoSpy).deleteAllExpiredCustomAudienceData(any());
verify(mBackgroundFetchRunnerSpy).deleteDisallowedOwnerCustomAudiences();
verify(mCustomAudienceDaoSpy).deleteAllDisallowedOwnerCustomAudienceData(any(), any());
verify(mBackgroundFetchRunnerSpy).deleteDisallowedBuyerCustomAudiences();
verify(mBackgroundFetchRunnerSpy).deleteDisallowedPackageAppInstallEntries();
verify(mCustomAudienceDaoSpy).deleteAllDisallowedBuyerCustomAudienceData(any(), any());
verify(mBackgroundFetchRunnerSpy, never()).updateCustomAudience(any(), any());
}
@Test
public void testRunBackgroundFetchNothingToUpdateNoFilters()
throws ExecutionException, InterruptedException {
Flags flagsFilteringDisabled = new BackgroundFetchWorkerTestFlags(false);
mBackgroundFetchWorker =
new BackgroundFetchWorker(
mCustomAudienceDaoSpy,
flagsFilteringDisabled,
mBackgroundFetchRunnerSpy,
mClock);
assertTrue(
mCustomAudienceDaoSpy
.getActiveEligibleCustomAudienceBackgroundFetchData(
CommonFixture.FIXED_NOW, 1)
.isEmpty());
when(mClock.instant()).thenReturn(CommonFixture.FIXED_NOW);
mBackgroundFetchWorker.runBackgroundFetch().get();
verify(mBackgroundFetchRunnerSpy).deleteExpiredCustomAudiences(any());
verify(mCustomAudienceDaoSpy).deleteAllExpiredCustomAudienceData(any());
verify(mBackgroundFetchRunnerSpy).deleteDisallowedOwnerCustomAudiences();
verify(mCustomAudienceDaoSpy).deleteAllDisallowedOwnerCustomAudienceData(any(), any());
verify(mBackgroundFetchRunnerSpy).deleteDisallowedBuyerCustomAudiences();
verify(mBackgroundFetchRunnerSpy, times(0)).deleteDisallowedPackageAppInstallEntries();
verify(mCustomAudienceDaoSpy).deleteAllDisallowedBuyerCustomAudienceData(any(), any());
verify(mBackgroundFetchRunnerSpy, never()).updateCustomAudience(any(), any());
}
@Test
public void testRunBackgroundFetchUpdateOneCustomAudience()
throws ExecutionException, InterruptedException {
// Mock a single custom audience eligible for update
DBCustomAudienceBackgroundFetchData fetchData =
DBCustomAudienceBackgroundFetchDataFixture.getValidBuilderByBuyer(
CommonFixture.VALID_BUYER_1)
.setEligibleUpdateTime(CommonFixture.FIXED_NOW)
.build();
doReturn(Arrays.asList(fetchData))
.when(mCustomAudienceDaoSpy)
.getActiveEligibleCustomAudienceBackgroundFetchData(any(), anyLong());
doReturn(FluentFuture.from(immediateFuture(null)))
.when(mBackgroundFetchRunnerSpy)
.updateCustomAudience(any(), any());
when(mClock.instant()).thenReturn(CommonFixture.FIXED_NOW);
mBackgroundFetchWorker.runBackgroundFetch().get();
verify(mBackgroundFetchRunnerSpy).deleteExpiredCustomAudiences(any());
verify(mCustomAudienceDaoSpy).deleteAllExpiredCustomAudienceData(any());
verify(mBackgroundFetchRunnerSpy).deleteDisallowedOwnerCustomAudiences();
verify(mCustomAudienceDaoSpy).deleteAllDisallowedOwnerCustomAudienceData(any(), any());
verify(mBackgroundFetchRunnerSpy).deleteDisallowedBuyerCustomAudiences();
verify(mBackgroundFetchRunnerSpy).deleteDisallowedPackageAppInstallEntries();
verify(mCustomAudienceDaoSpy).deleteAllDisallowedBuyerCustomAudienceData(any(), any());
verify(mBackgroundFetchRunnerSpy).updateCustomAudience(any(), any());
}
@Test
public void testRunBackgroundFetchUpdateCustomAudiences()
throws ExecutionException, InterruptedException {
int numEligibleCustomAudiences = 12;
// Mock a list of custom audiences eligible for update
DBCustomAudienceBackgroundFetchData.Builder fetchDataBuilder =
DBCustomAudienceBackgroundFetchDataFixture.getValidBuilderByBuyer(
CommonFixture.VALID_BUYER_1)
.setEligibleUpdateTime(CommonFixture.FIXED_NOW);
List<DBCustomAudienceBackgroundFetchData> fetchDataList = new ArrayList<>();
for (int i = 0; i < numEligibleCustomAudiences; i++) {
fetchDataList.add(fetchDataBuilder.setName("ca" + i).build());
}
doReturn(fetchDataList)
.when(mCustomAudienceDaoSpy)
.getActiveEligibleCustomAudienceBackgroundFetchData(any(), anyLong());
doReturn(FluentFuture.from(immediateFuture(null)))
.when(mBackgroundFetchRunnerSpy)
.updateCustomAudience(any(), any());
when(mClock.instant()).thenReturn(CommonFixture.FIXED_NOW);
mBackgroundFetchWorker.runBackgroundFetch().get();
verify(mBackgroundFetchRunnerSpy).deleteExpiredCustomAudiences(any());
verify(mCustomAudienceDaoSpy).deleteAllExpiredCustomAudienceData(any());
verify(mBackgroundFetchRunnerSpy).deleteDisallowedOwnerCustomAudiences();
verify(mCustomAudienceDaoSpy).deleteAllDisallowedOwnerCustomAudienceData(any(), any());
verify(mBackgroundFetchRunnerSpy).deleteDisallowedBuyerCustomAudiences();
verify(mBackgroundFetchRunnerSpy).deleteDisallowedPackageAppInstallEntries();
verify(mCustomAudienceDaoSpy).deleteAllDisallowedBuyerCustomAudienceData(any(), any());
verify(mBackgroundFetchRunnerSpy, times(numEligibleCustomAudiences))
.updateCustomAudience(any(), any());
}
@Test
public void testRunBackgroundFetchChecksWorkInProgress()
throws InterruptedException, ExecutionException {
int numEligibleCustomAudiences = 16;
CountDownLatch partialCompletionLatch = new CountDownLatch(numEligibleCustomAudiences / 4);
// Mock a list of custom audiences eligible for update
DBCustomAudienceBackgroundFetchData.Builder fetchDataBuilder =
DBCustomAudienceBackgroundFetchDataFixture.getValidBuilderByBuyer(
CommonFixture.VALID_BUYER_1)
.setEligibleUpdateTime(CommonFixture.FIXED_NOW);
List<DBCustomAudienceBackgroundFetchData> fetchDataList = new ArrayList<>();
for (int i = 0; i < numEligibleCustomAudiences; i++) {
fetchDataList.add(fetchDataBuilder.setName("ca" + i).build());
}
doReturn(fetchDataList)
.when(mCustomAudienceDaoSpy)
.getActiveEligibleCustomAudienceBackgroundFetchData(any(), anyLong());
doAnswer(
unusedInvocation -> {
Thread.sleep(100);
partialCompletionLatch.countDown();
return FluentFuture.from(immediateFuture(null));
})
.when(mBackgroundFetchRunnerSpy)
.updateCustomAudience(any(), any());
when(mClock.instant()).thenReturn(CommonFixture.FIXED_NOW);
CountDownLatch bgfWorkStoppedLatch = new CountDownLatch(1);
mExecutorService.execute(
() -> {
try {
mBackgroundFetchWorker.runBackgroundFetch().get();
} catch (Exception exception) {
sLogger.e(
exception, "Exception encountered while running background fetch");
} finally {
bgfWorkStoppedLatch.countDown();
}
});
// Wait til updates are partially complete, then try running background fetch again and
// verify nothing is done
partialCompletionLatch.await();
when(mClock.instant()).thenReturn(CommonFixture.FIXED_NOW.plusSeconds(1));
mBackgroundFetchWorker.runBackgroundFetch().get();
bgfWorkStoppedLatch.await();
verify(mBackgroundFetchRunnerSpy).deleteExpiredCustomAudiences(any());
verify(mCustomAudienceDaoSpy).deleteAllExpiredCustomAudienceData(any());
verify(mBackgroundFetchRunnerSpy).deleteDisallowedOwnerCustomAudiences();
verify(mCustomAudienceDaoSpy).deleteAllDisallowedOwnerCustomAudienceData(any(), any());
verify(mBackgroundFetchRunnerSpy).deleteDisallowedBuyerCustomAudiences();
verify(mBackgroundFetchRunnerSpy).deleteDisallowedPackageAppInstallEntries();
verify(mCustomAudienceDaoSpy).deleteAllDisallowedBuyerCustomAudienceData(any(), any());
verify(mBackgroundFetchRunnerSpy, times(numEligibleCustomAudiences))
.updateCustomAudience(any(), any());
}
@Test
public void testStopWorkWithoutRunningFetchDoesNothing() {
// Verify no errors/exceptions thrown when no work in progress
mBackgroundFetchWorker.stopWork();
}
@Test
public void testStopWorkGracefullyStopsBackgroundFetch() throws Exception {
int numEligibleCustomAudiences = 16;
CountDownLatch partialCompletionLatch = new CountDownLatch(numEligibleCustomAudiences / 4);
// Mock a list of custom audiences eligible for update
DBCustomAudienceBackgroundFetchData.Builder fetchDataBuilder =
DBCustomAudienceBackgroundFetchDataFixture.getValidBuilderByBuyer(
CommonFixture.VALID_BUYER_1)
.setEligibleUpdateTime(CommonFixture.FIXED_NOW);
List<DBCustomAudienceBackgroundFetchData> fetchDataList = new ArrayList<>();
for (int i = 0; i < numEligibleCustomAudiences; i++) {
fetchDataList.add(fetchDataBuilder.setName("ca" + i).build());
}
doReturn(fetchDataList)
.when(mCustomAudienceDaoSpy)
.getActiveEligibleCustomAudienceBackgroundFetchData(any(), anyLong());
doAnswer(
unusedInvocation -> {
Thread.sleep(100);
partialCompletionLatch.countDown();
return FluentFuture.from(immediateVoidFuture());
})
.when(mBackgroundFetchRunnerSpy)
.updateCustomAudience(any(), any());
when(mClock.instant()).thenReturn(CommonFixture.FIXED_NOW);
ListenableFuture<Void> backgrounFetchResult = mBackgroundFetchWorker.runBackgroundFetch();
// Wait til updates are partially complete, then try stopping background fetch
partialCompletionLatch.await();
mBackgroundFetchWorker.stopWork();
// stopWork() should notify to the worker that the work should end so the future
// should complete within the time required to update the custom audiences
backgrounFetchResult.get(
100 * (numEligibleCustomAudiences * 3 / 4) + 100, TimeUnit.SECONDS);
}
@Test
public void testStopWorkPreemptsDataUpdates() throws Exception {
int numEligibleCustomAudiences = 16;
CountDownLatch beforeUpdatingCasLatch = new CountDownLatch(numEligibleCustomAudiences / 4);
// Mock a list of custom audiences eligible for update
DBCustomAudienceBackgroundFetchData.Builder fetchDataBuilder =
DBCustomAudienceBackgroundFetchDataFixture.getValidBuilderByBuyer(
CommonFixture.VALID_BUYER_1)
.setEligibleUpdateTime(CommonFixture.FIXED_NOW);
List<DBCustomAudienceBackgroundFetchData> fetchDataList = new ArrayList<>();
for (int i = 0; i < numEligibleCustomAudiences; i++) {
fetchDataList.add(fetchDataBuilder.setName("ca" + i).build());
}
// Ensuring that stopWork is called before the data update process
doAnswer(
unusedInvocation -> {
beforeUpdatingCasLatch.await();
return fetchDataList;
})
.when(mCustomAudienceDaoSpy)
.getActiveEligibleCustomAudienceBackgroundFetchData(any(), anyLong());
doAnswer(
unusedInvocation -> {
Thread.sleep(100);
return null;
})
.when(mBackgroundFetchRunnerSpy)
.updateCustomAudience(any(), any());
when(mClock.instant()).thenReturn(CommonFixture.FIXED_NOW);
ListenableFuture<Void> backgrounFetchResult = mBackgroundFetchWorker.runBackgroundFetch();
// Wait til updates are partially complete, then try stopping background fetch
mBackgroundFetchWorker.stopWork();
beforeUpdatingCasLatch.countDown();
// stopWork() called before updating the data should cause immediate termination
// waiting for 200ms to handle thread scheduling delays.
// The important check is that the time is less than the time of updating all CAs
backgrounFetchResult.get(200, TimeUnit.MILLISECONDS);
}
@Test
public void testRunBackgroundFetchInSequence() throws InterruptedException, ExecutionException {
int numEligibleCustomAudiences = 16;
CountDownLatch completionLatch = new CountDownLatch(numEligibleCustomAudiences / 2);
// Mock two lists of custom audiences eligible for update
DBCustomAudienceBackgroundFetchData.Builder fetchDataBuilder =
DBCustomAudienceBackgroundFetchDataFixture.getValidBuilderByBuyer(
CommonFixture.VALID_BUYER_1)
.setEligibleUpdateTime(CommonFixture.FIXED_NOW);
List<DBCustomAudienceBackgroundFetchData> fetchDataList1 = new ArrayList<>();
List<DBCustomAudienceBackgroundFetchData> fetchDataList2 = new ArrayList<>();
for (int i = 0; i < numEligibleCustomAudiences; i++) {
DBCustomAudienceBackgroundFetchData fetchData =
fetchDataBuilder.setName("ca" + i).build();
if (i < numEligibleCustomAudiences / 2) {
fetchDataList1.add(fetchData);
} else {
fetchDataList2.add(fetchData);
}
}
// Count the number of times updateCustomAudience is run
AtomicInteger completionCount = new AtomicInteger(0);
// Return the first list the first time, and the second list in the second call
doReturn(fetchDataList1)
.doReturn(fetchDataList2)
.when(mCustomAudienceDaoSpy)
.getActiveEligibleCustomAudienceBackgroundFetchData(any(), anyLong());
doAnswer(
unusedInvocation -> {
completionLatch.countDown();
completionCount.getAndIncrement();
return FluentFuture.from(immediateFuture(null));
})
.when(mBackgroundFetchRunnerSpy)
.updateCustomAudience(any(), any());
when(mClock.instant()).thenReturn(CommonFixture.FIXED_NOW);
CountDownLatch bgfWorkStoppedLatch = new CountDownLatch(1);
mExecutorService.execute(
() -> {
try {
mBackgroundFetchWorker.runBackgroundFetch().get();
} catch (Exception exception) {
sLogger.e(
exception, "Exception encountered while running background fetch");
} finally {
bgfWorkStoppedLatch.countDown();
}
});
// Wait til updates are complete, then try running background fetch again and
// verify the second run updates more custom audiences successfully
completionLatch.await();
bgfWorkStoppedLatch.await();
when(mClock.instant()).thenReturn(CommonFixture.FIXED_NOW.plusSeconds(1));
mBackgroundFetchWorker.runBackgroundFetch().get();
verify(mBackgroundFetchRunnerSpy, times(2)).deleteExpiredCustomAudiences(any());
verify(mCustomAudienceDaoSpy, times(2)).deleteAllExpiredCustomAudienceData(any());
verify(mBackgroundFetchRunnerSpy, times(2)).deleteDisallowedOwnerCustomAudiences();
verify(mCustomAudienceDaoSpy, times(2))
.deleteAllDisallowedOwnerCustomAudienceData(any(), any());
verify(mBackgroundFetchRunnerSpy, times(2)).deleteDisallowedBuyerCustomAudiences();
verify(mBackgroundFetchRunnerSpy, times(2)).deleteDisallowedPackageAppInstallEntries();
verify(mCustomAudienceDaoSpy, times(2))
.deleteAllDisallowedBuyerCustomAudienceData(any(), any());
verify(mBackgroundFetchRunnerSpy, times(numEligibleCustomAudiences))
.updateCustomAudience(any(), any());
assertThat(completionCount.get()).isEqualTo(numEligibleCustomAudiences);
}
private static class BackgroundFetchWorkerTestFlags implements Flags {
private final boolean mFledgeAdSelectionFilteringEnabled;
BackgroundFetchWorkerTestFlags(boolean fledgeAdSelectionFilteringEnabled) {
mFledgeAdSelectionFilteringEnabled = fledgeAdSelectionFilteringEnabled;
}
@Override
public int getFledgeBackgroundFetchThreadPoolSize() {
return 4;
}
@Override
public boolean getFledgeAdSelectionFilteringEnabled() {
return mFledgeAdSelectionFilteringEnabled;
}
@Override
public int getFledgeBackgroundFetchNetworkConnectTimeoutMs() {
return EXTENDED_FLEDGE_BACKGROUND_FETCH_NETWORK_CONNECT_TIMEOUT_MS;
}
@Override
public int getFledgeBackgroundFetchNetworkReadTimeoutMs() {
return EXTENDED_FLEDGE_BACKGROUND_FETCH_NETWORK_READ_TIMEOUT_MS;
}
}
}