diff options
Diffstat (limited to 'adservices/tests/unittest/service-core/src/com/android/adservices/service/MaintenanceJobServiceTest.java')
-rw-r--r-- | adservices/tests/unittest/service-core/src/com/android/adservices/service/MaintenanceJobServiceTest.java | 277 |
1 files changed, 259 insertions, 18 deletions
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/MaintenanceJobServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/MaintenanceJobServiceTest.java index d625836a6a..4513beb233 100644 --- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/MaintenanceJobServiceTest.java +++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/MaintenanceJobServiceTest.java @@ -17,19 +17,22 @@ package com.android.adservices.service; import static com.android.adservices.service.AdServicesConfig.MAINTENANCE_JOB_ID; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; import android.app.job.JobInfo; import android.app.job.JobParameters; @@ -37,8 +40,12 @@ import android.app.job.JobScheduler; import android.content.ComponentName; import android.content.Context; +import androidx.room.Room; import androidx.test.core.app.ApplicationProvider; +import com.android.adservices.data.adselection.AdSelectionDatabase; +import com.android.adservices.data.adselection.AdSelectionEntryDao; +import com.android.adservices.service.common.FledgeMaintenanceTasksWorker; import com.android.adservices.service.topics.AppUpdateManager; import com.android.adservices.service.topics.BlockedTopicsManager; import com.android.adservices.service.topics.CacheManager; @@ -50,15 +57,19 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; import org.mockito.Spy; +import org.mockito.quality.Strictness; /** Unit tests for {@link com.android.adservices.service.MaintenanceJobService} */ @SuppressWarnings("ConstantConditions") public class MaintenanceJobServiceTest { private static final int BACKGROUND_THREAD_TIMEOUT_MS = 5_000; + private static final long MAINTENANCE_JOB_PERIOD_MS = 10_000L; + private static final long MAINTENANCE_JOB_FLEX_MS = 1_000L; + private static final long CURRENT_EPOCH_ID = 1L; private static final Context CONTEXT = ApplicationProvider.getApplicationContext(); private static final JobScheduler JOB_SCHEDULER = CONTEXT.getSystemService(JobScheduler.class); private static final Flags TEST_FLAGS = FlagsFactory.getFlagsForTest(); @@ -74,10 +85,20 @@ public class MaintenanceJobServiceTest { @Mock AppUpdateManager mMockAppUpdateManager; @Mock JobParameters mMockJobParameters; @Mock Flags mMockFlags; + @Mock JobScheduler mMockJobScheduler; + private AdSelectionEntryDao mAdSelectionEntryDao; + @Spy private FledgeMaintenanceTasksWorker mFledgeMaintenanceTasksWorkerSpy; @Before public void setup() { - MockitoAnnotations.initMocks(this); + mAdSelectionEntryDao = + Room.inMemoryDatabaseBuilder( + ApplicationProvider.getApplicationContext(), + AdSelectionDatabase.class) + .build() + .adSelectionEntryDao(); + + mFledgeMaintenanceTasksWorkerSpy = new FledgeMaintenanceTasksWorker(mAdSelectionEntryDao); // Start a mockitoSession to mock static method mStaticMockSession = @@ -85,6 +106,8 @@ public class MaintenanceJobServiceTest { .spyStatic(MaintenanceJobService.class) .spyStatic(TopicsWorker.class) .spyStatic(FlagsFactory.class) + .initMocks(this) + .strictness(Strictness.LENIENT) .startMocking(); // Mock JobScheduler invocation in EpochJobService @@ -97,7 +120,9 @@ public class MaintenanceJobServiceTest { @After public void teardown() { JOB_SCHEDULER.cancelAll(); - mStaticMockSession.finishMocking(); + if (mStaticMockSession != null) { + mStaticMockSession.finishMocking(); + } } @Test @@ -109,9 +134,86 @@ public class MaintenanceJobServiceTest { mBlockedTopicsManager, mMockAppUpdateManager, TEST_FLAGS); + // Killswitch is off. + doReturn(false).when(mMockFlags).getTopicsKillSwitch(); + doReturn(false).when(mMockFlags).getFledgeSelectAdsKillSwitch(); + doReturn(CURRENT_EPOCH_ID).when(mMockEpochManager).getCurrentEpochId(); + doReturn(TEST_FLAGS.getAdSelectionExpirationWindowS()) + .when(mMockFlags) + .getAdSelectionExpirationWindowS(); + // Mock static method FlagsFactory.getFlags() to return Mock Flags. + ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags); + + // Mock static method AppUpdateWorker.getInstance, let it return the local + // appUpdateWorker in order to get a test instance. + ExtendedMockito.doReturn(topicsWorker) + .when(() -> TopicsWorker.getInstance(any(Context.class))); + + mSpyMaintenanceJobService.injectFledgeMaintenanceTasksWorker( + mFledgeMaintenanceTasksWorkerSpy); + + mSpyMaintenanceJobService.onStartJob(mMockJobParameters); + + // Grant some time to allow background thread to execute + Thread.sleep(BACKGROUND_THREAD_TIMEOUT_MS); + + ExtendedMockito.verify(() -> TopicsWorker.getInstance(any(Context.class))); + verify(mMockAppUpdateManager) + .reconcileUninstalledApps(any(Context.class), eq(CURRENT_EPOCH_ID)); + verify(mMockAppUpdateManager) + .reconcileInstalledApps(any(Context.class), /* currentEpochId */ anyLong()); + + // Ensure Fledge job was done + verify(mFledgeMaintenanceTasksWorkerSpy).clearExpiredAdSelectionData(); + } + + @Test + public void testOnStartJob_TopicsKillSwitchOn() throws InterruptedException { + // Killswitch is off. + doReturn(true).when(mMockFlags).getTopicsKillSwitch(); + doReturn(false).when(mMockFlags).getFledgeSelectAdsKillSwitch(); + doReturn(CURRENT_EPOCH_ID).when(mMockEpochManager).getCurrentEpochId(); + + // Mock static method FlagsFactory.getFlags() to return Mock Flags. + ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags); + doReturn(TEST_FLAGS.getAdSelectionExpirationWindowS()) + .when(mMockFlags) + .getAdSelectionExpirationWindowS(); + + // Inject FledgeMaintenanceTasksWorker since the test can't get it the standard way + mSpyMaintenanceJobService.injectFledgeMaintenanceTasksWorker( + mFledgeMaintenanceTasksWorkerSpy); + + mSpyMaintenanceJobService.onStartJob(mMockJobParameters); + + // Grant some time to allow background thread to execute + Thread.sleep(BACKGROUND_THREAD_TIMEOUT_MS); + + // Verify that topics job is not done + ExtendedMockito.verify(() -> TopicsWorker.getInstance(any(Context.class)), never()); + verify(mMockAppUpdateManager, never()) + .reconcileUninstalledApps(any(Context.class), eq(CURRENT_EPOCH_ID)); + verify(mMockAppUpdateManager, never()) + .reconcileInstalledApps(any(Context.class), /* currentEpochId */ anyLong()); + + // Verifying that FLEDGE job was done + verify(mFledgeMaintenanceTasksWorkerSpy).clearExpiredAdSelectionData(); + } + + @Test + public void testOnStartJob_SelectAdsKillSwitchOn() throws InterruptedException { + final TopicsWorker topicsWorker = + new TopicsWorker( + mMockEpochManager, + mMockCacheManager, + mBlockedTopicsManager, + mMockAppUpdateManager, + TEST_FLAGS); // Killswitch is off. doReturn(false).when(mMockFlags).getTopicsKillSwitch(); + doReturn(true).when(mMockFlags).getFledgeSelectAdsKillSwitch(); + doReturn(CURRENT_EPOCH_ID).when(mMockEpochManager).getCurrentEpochId(); // Mock static method FlagsFactory.getFlags() to return Mock Flags. ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags); @@ -127,29 +229,36 @@ public class MaintenanceJobServiceTest { Thread.sleep(BACKGROUND_THREAD_TIMEOUT_MS); ExtendedMockito.verify(() -> TopicsWorker.getInstance(any(Context.class))); - verify(mMockAppUpdateManager).reconcileUninstalledApps(any(Context.class)); + verify(mMockAppUpdateManager) + .reconcileUninstalledApps(any(Context.class), eq(CURRENT_EPOCH_ID)); verify(mMockAppUpdateManager) .reconcileInstalledApps(any(Context.class), /* currentEpochId */ anyLong()); + verify(mFledgeMaintenanceTasksWorkerSpy, never()).clearExpiredAdSelectionData(); } @Test - public void testOnStartJob_killSwitchOn() throws InterruptedException { + public void testOnStartJob_killSwitchOn() { // Killswitch on. doReturn(true).when(mMockFlags).getTopicsKillSwitch(); + doReturn(true).when(mMockFlags).getFledgeSelectAdsKillSwitch(); // Mock static method FlagsFactory.getFlags() to return Mock Flags. ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags); doNothing().when(mSpyMaintenanceJobService).jobFinished(mMockJobParameters, false); + // Inject FledgeMaintenanceTasksWorker since the test can't get it the standard way + mSpyMaintenanceJobService.injectFledgeMaintenanceTasksWorker( + mFledgeMaintenanceTasksWorkerSpy); + // Schedule the job to assert after starting that the scheduled job has been cancelled JobInfo existingJobInfo = new JobInfo.Builder( MAINTENANCE_JOB_ID, new ComponentName(CONTEXT, EpochJobService.class)) .setRequiresCharging(true) - .setPeriodic( - /* maintenanceJobPeriodMs */ 10000, /* maintenanceJobFlexMs */ 1000) + .setPeriodic(MAINTENANCE_JOB_PERIOD_MS, MAINTENANCE_JOB_FLEX_MS) + .setPersisted(true) .build(); JOB_SCHEDULER.schedule(existingJobInfo); assertNotNull(JOB_SCHEDULER.getPendingJob(MAINTENANCE_JOB_ID)); @@ -161,25 +270,145 @@ public class MaintenanceJobServiceTest { verify(mSpyMaintenanceJobService).jobFinished(mMockJobParameters, false); verifyNoMoreInteractions(staticMockMarker(TopicsWorker.class)); + verify(mFledgeMaintenanceTasksWorkerSpy, never()).clearExpiredAdSelectionData(); } @Test - public void testOnStartJob_globalKillswitchOverridesAll() throws InterruptedException { - // Global Killswitch is on. - doReturn(true).when(mMockFlags).getGlobalKillSwitch(); + public void testOnStartJob_killSwitchOnDoesFledgeJobWhenTopicsJobThrowsException() + throws Exception { + final TopicsWorker topicsWorker = + new TopicsWorker( + mMockEpochManager, + mMockCacheManager, + mBlockedTopicsManager, + mMockAppUpdateManager, + TEST_FLAGS); + // Killswitch is off. + doReturn(false).when(mMockFlags).getTopicsKillSwitch(); + doReturn(false).when(mMockFlags).getFledgeSelectAdsKillSwitch(); + doReturn(CURRENT_EPOCH_ID).when(mMockEpochManager).getCurrentEpochId(); + doReturn(TEST_FLAGS.getAdSelectionExpirationWindowS()) + .when(mMockFlags) + .getAdSelectionExpirationWindowS(); + + // Mock static method FlagsFactory.getFlags() to return Mock Flags. + ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags); + + // Mock static method AppUpdateWorker.getInstance, let it return the local + // appUpdateWorker in order to get a test instance. + ExtendedMockito.doReturn(topicsWorker) + .when(() -> TopicsWorker.getInstance(any(Context.class))); + + // Inject FledgeMaintenanceTasksWorker since the test can't get it the standard way + mSpyMaintenanceJobService.injectFledgeMaintenanceTasksWorker( + mFledgeMaintenanceTasksWorkerSpy); + + // Simulating a failure in Topics job + ExtendedMockito.doThrow(new IllegalStateException()) + .when(mMockAppUpdateManager) + .reconcileUninstalledApps(any(Context.class), eq(CURRENT_EPOCH_ID)); - // Killswitch off. + mSpyMaintenanceJobService.onStartJob(mMockJobParameters); + + // Grant some time to allow background thread to execute + Thread.sleep(BACKGROUND_THREAD_TIMEOUT_MS); + + ExtendedMockito.verify(() -> TopicsWorker.getInstance(any(Context.class))); + verify(mMockAppUpdateManager) + .reconcileUninstalledApps(any(Context.class), eq(CURRENT_EPOCH_ID)); + // Verify that this is not called because we threw an exception + verify(mMockAppUpdateManager, never()) + .reconcileInstalledApps(any(Context.class), /* currentEpochId */ anyLong()); + + // Verifying that FLEDGE job was done + verify(mFledgeMaintenanceTasksWorkerSpy).clearExpiredAdSelectionData(); + } + + @Test + public void testOnStartJob_killSwitchOnDoesTopicsJobWhenFledgeThrowsException() + throws Exception { + final TopicsWorker topicsWorker = + new TopicsWorker( + mMockEpochManager, + mMockCacheManager, + mBlockedTopicsManager, + mMockAppUpdateManager, + TEST_FLAGS); + // Killswitch is off. doReturn(false).when(mMockFlags).getTopicsKillSwitch(); + doReturn(false).when(mMockFlags).getFledgeSelectAdsKillSwitch(); + doReturn(CURRENT_EPOCH_ID).when(mMockEpochManager).getCurrentEpochId(); // Mock static method FlagsFactory.getFlags() to return Mock Flags. ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags); + // Mock static method AppUpdateWorker.getInstance, let it return the local + // appUpdateWorker in order to get a test instance. + ExtendedMockito.doReturn(topicsWorker) + .when(() -> TopicsWorker.getInstance(any(Context.class))); + + // Inject FledgeMaintenanceTasksWorker since the test can't get it the standard way + mSpyMaintenanceJobService.injectFledgeMaintenanceTasksWorker( + mFledgeMaintenanceTasksWorkerSpy); + + // Simulating a failure in Fledge job + ExtendedMockito.doThrow(new IllegalStateException()) + .when(mFledgeMaintenanceTasksWorkerSpy) + .clearExpiredAdSelectionData(); + + mSpyMaintenanceJobService.onStartJob(mMockJobParameters); + + // Grant some time to allow background thread to execute + Thread.sleep(BACKGROUND_THREAD_TIMEOUT_MS); + + ExtendedMockito.verify(() -> TopicsWorker.getInstance(any(Context.class))); + verify(mMockAppUpdateManager) + .reconcileUninstalledApps(any(Context.class), eq(CURRENT_EPOCH_ID)); + verify(mMockAppUpdateManager) + .reconcileInstalledApps(any(Context.class), /* currentEpochId */ anyLong()); + + verify(mFledgeMaintenanceTasksWorkerSpy).clearExpiredAdSelectionData(); + } + + @Test + public void testOnStartJob_globalKillswitchOverridesAll() throws InterruptedException { + Flags flagsGlobalKillSwitchOn = + new Flags() { + @Override + public boolean getGlobalKillSwitch() { + return true; + } + }; + + // Setting mock flags to use flags with global switch overridden + doReturn(flagsGlobalKillSwitchOn.getTopicsKillSwitch()) + .when(mMockFlags) + .getTopicsKillSwitch(); + doReturn(flagsGlobalKillSwitchOn.getTopicsKillSwitch()) + .when(mMockFlags) + .getFledgeSelectAdsKillSwitch(); + doReturn(CURRENT_EPOCH_ID).when(mMockEpochManager).getCurrentEpochId(); + + // Mock static method FlagsFactory.getFlags() to return Mock Flags. + ExtendedMockito.doReturn(mMockFlags).when(FlagsFactory::getFlags); + + ExtendedMockito.doNothing() + .when(mSpyMaintenanceJobService) + .jobFinished(mMockJobParameters, false); + mSpyMaintenanceJobService.onStartJob(mMockJobParameters); // Grant some time to allow background thread to execute Thread.sleep(BACKGROUND_THREAD_TIMEOUT_MS); - // When the kill switch is on, the MaintenanceJobService exits early and do nothing. + verify(() -> TopicsWorker.getInstance(any(Context.class)), never()); + verify(mMockAppUpdateManager, never()) + .reconcileUninstalledApps(any(Context.class), eq(CURRENT_EPOCH_ID)); + verify(mMockAppUpdateManager, never()) + .reconcileInstalledApps(any(Context.class), /* currentEpochId */ anyLong()); + + // Ensure Fledge job not was done + verify(mFledgeMaintenanceTasksWorkerSpy, never()).clearExpiredAdSelectionData(); } @Test @@ -256,7 +485,7 @@ public class MaintenanceJobServiceTest { } @Test - public void testScheduleIfNeeded_scheduledWithKillSwichOn() { + public void testScheduleIfNeeded_scheduledWithKillSwitchOn() { // Killswitch is on. doReturn(true).when(mMockFlags).getTopicsKillSwitch(); @@ -267,4 +496,16 @@ public class MaintenanceJobServiceTest { assertThat(EpochJobService.scheduleIfNeeded(CONTEXT, /* forceSchedule */ false)).isFalse(); assertThat(JOB_SCHEDULER.getPendingJob(MAINTENANCE_JOB_ID)).isNull(); } + + @Test + public void testSchedule_jobInfoIsPersisted() { + final ArgumentCaptor<JobInfo> argumentCaptor = ArgumentCaptor.forClass(JobInfo.class); + + MaintenanceJobService.schedule( + CONTEXT, mMockJobScheduler, MAINTENANCE_JOB_PERIOD_MS, MAINTENANCE_JOB_FLEX_MS); + + verify(mMockJobScheduler, times(1)).schedule(argumentCaptor.capture()); + assertThat(argumentCaptor.getValue()).isNotNull(); + assertThat(argumentCaptor.getValue().isPersisted()).isTrue(); + } } |