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 17 package com.android.adservices.mockito; 18 19 import static com.android.adservices.shared.testing.concurrency.SyncCallbackSettings.DEFAULT_TIMEOUT_MS; 20 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod; 21 22 import static org.mockito.ArgumentMatchers.any; 23 import static org.mockito.ArgumentMatchers.anyBoolean; 24 import static org.mockito.ArgumentMatchers.anyInt; 25 import static org.mockito.ArgumentMatchers.anyLong; 26 import static org.mockito.Mockito.doAnswer; 27 import static org.mockito.Mockito.mock; 28 import static org.mockito.Mockito.never; 29 import static org.mockito.Mockito.spy; 30 import static org.mockito.Mockito.verify; 31 import static org.mockito.Mockito.when; 32 33 import android.app.job.JobParameters; 34 import android.app.job.JobService; 35 import android.content.Context; 36 import android.util.Log; 37 38 import com.android.adservices.service.Flags; 39 import com.android.adservices.service.stats.AdServicesLogger; 40 import com.android.adservices.service.stats.ApiCallStats; 41 import com.android.adservices.shared.errorlogging.AdServicesErrorLogger; 42 import com.android.adservices.shared.testing.JobServiceLoggingCallback; 43 import com.android.adservices.shared.testing.concurrency.ResultSyncCallback; 44 import com.android.adservices.shared.testing.concurrency.SyncCallbackFactory; 45 import com.android.adservices.shared.util.Clock; 46 import com.android.adservices.spe.AdServicesJobInfo; 47 import com.android.adservices.spe.AdServicesJobServiceLogger; 48 import com.android.adservices.spe.AdServicesStatsdJobServiceLogger; 49 50 import java.util.concurrent.Executors; 51 52 /** Provides Mockito expectation for common calls. */ 53 public final class MockitoExpectations { 54 55 private static final String TAG = MockitoExpectations.class.getSimpleName(); 56 57 /** 58 * Mocks a call to {@link AdServicesLogger#logApiCallStats(ApiCallStats)} and returns a callback 59 * object that blocks until that call is made. 60 */ mockLogApiCallStats( AdServicesLogger adServicesLogger)61 public static ResultSyncCallback<ApiCallStats> mockLogApiCallStats( 62 AdServicesLogger adServicesLogger) { 63 return mockLogApiCallStats(adServicesLogger, DEFAULT_TIMEOUT_MS); 64 } 65 66 /** 67 * Mocks a call to {@link AdServicesLogger#logApiCallStats(ApiCallStats)} and returns a callback 68 * object that blocks until that call is made. This method allows to pass in a customized 69 * timeout. 70 */ mockLogApiCallStats( AdServicesLogger adServicesLogger, long timeoutMs)71 public static ResultSyncCallback<ApiCallStats> mockLogApiCallStats( 72 AdServicesLogger adServicesLogger, long timeoutMs) { 73 ResultSyncCallback<ApiCallStats> callback = 74 new ResultSyncCallback<>( 75 SyncCallbackFactory.newSettingsBuilder() 76 .setMaxTimeoutMs(timeoutMs) 77 .build()); 78 79 doAnswer( 80 inv -> { 81 Log.v(TAG, "mockLogApiCallStats(): inv=" + inv); 82 ApiCallStats apiCallStats = inv.getArgument(0); 83 callback.injectResult(apiCallStats); 84 return null; 85 }) 86 .when(adServicesLogger) 87 .logApiCallStats(any()); 88 89 return callback; 90 } 91 92 /** Verifies methods in {@link AdServicesJobServiceLogger} were never called. */ verifyLoggingNotHappened(AdServicesJobServiceLogger logger)93 public static void verifyLoggingNotHappened(AdServicesJobServiceLogger logger) { 94 // Mock logger to call actual public logging methods. Because when the feature flag of 95 // logging is on, these methods are actually called, but the internal logging methods will 96 // not be invoked. 97 callRealPublicMethodsForBackgroundJobLogging(logger); 98 99 verify(logger, never()).persistJobExecutionData(anyInt(), anyLong()); 100 verify(logger, never()).logExecutionStats(anyInt(), anyLong(), anyInt(), anyInt()); 101 } 102 103 /** Verifies {@link AdServicesJobServiceLogger#recordJobSkipped(int, int)} is called once. */ verifyBackgroundJobsSkipLogged( AdServicesJobServiceLogger logger, JobServiceLoggingCallback callback)104 public static void verifyBackgroundJobsSkipLogged( 105 AdServicesJobServiceLogger logger, JobServiceLoggingCallback callback) 106 throws InterruptedException { 107 callback.assertLoggingFinished(); 108 109 verify(logger).recordJobSkipped(anyInt(), anyInt()); 110 } 111 112 /** Verifies the logging flow of a successful {@link JobService}'s execution is called once. */ verifyJobFinishedLogged( AdServicesJobServiceLogger logger, JobServiceLoggingCallback onStartJobCallback, JobServiceLoggingCallback onJobDoneCallback)113 public static void verifyJobFinishedLogged( 114 AdServicesJobServiceLogger logger, 115 JobServiceLoggingCallback onStartJobCallback, 116 JobServiceLoggingCallback onJobDoneCallback) 117 throws InterruptedException { 118 verifyOnStartJobLogged(logger, onStartJobCallback); 119 verifyOnJobFinishedLogged(logger, onJobDoneCallback); 120 } 121 122 /** 123 * Verifies {@link AdServicesJobServiceLogger#recordOnStopJob(JobParameters, int, boolean)} is 124 * called once. 125 */ verifyOnStopJobLogged( AdServicesJobServiceLogger logger, JobServiceLoggingCallback callback)126 public static void verifyOnStopJobLogged( 127 AdServicesJobServiceLogger logger, JobServiceLoggingCallback callback) 128 throws InterruptedException { 129 callback.assertLoggingFinished(); 130 131 verify(logger).recordOnStopJob(any(), anyInt(), anyBoolean()); 132 } 133 134 /** 135 * Mocks a call to {@link Flags#getBackgroundJobsLoggingKillSwitch()}, returning overrideValue. 136 */ mockBackgroundJobsLoggingKillSwitch(Flags flag, boolean overrideValue)137 public static void mockBackgroundJobsLoggingKillSwitch(Flags flag, boolean overrideValue) { 138 when(flag.getBackgroundJobsLoggingKillSwitch()).thenReturn(overrideValue); 139 } 140 141 /** Mocks a call to {@link Flags#getCobaltLoggingEnabled()}, returning overrideValue. */ mockCobaltLoggingEnabled(Flags flags, boolean enabled)142 public static void mockCobaltLoggingEnabled(Flags flags, boolean enabled) { 143 when(flags.getCobaltLoggingEnabled()).thenReturn(enabled); 144 } 145 146 /** 147 * Mocks a call to {@link Flags#getAppNameApiErrorCobaltLoggingEnabled()}, returning 148 * overrideValue. 149 */ mockAppNameApiErrorCobaltLoggingEnabled(Flags flags, boolean enabled)150 public static void mockAppNameApiErrorCobaltLoggingEnabled(Flags flags, boolean enabled) { 151 when(flags.getAppNameApiErrorCobaltLoggingEnabled()).thenReturn(enabled); 152 } 153 154 /** 155 * Mocks a call to {@link Flags#getMsmtRegistrationCobaltLoggingEnabled()} ()}, returning 156 * overrideValue. 157 */ mockMsmtRegistrationCobaltLoggingEnabled(Flags flags, boolean enabled)158 public static void mockMsmtRegistrationCobaltLoggingEnabled(Flags flags, boolean enabled) { 159 when(flags.getMsmtRegistrationCobaltLoggingEnabled()).thenReturn(enabled); 160 } 161 162 /** 163 * Mocks a call to {@link Flags#getAdservicesReleaseStageForCobalt()}, returning {@code DEBUG} 164 * as the testing release stage. 165 */ mockAdservicesReleaseStageForCobalt(Flags flags)166 public static void mockAdservicesReleaseStageForCobalt(Flags flags) { 167 when(flags.getAdservicesReleaseStageForCobalt()).thenReturn("DEBUG"); 168 } 169 170 /** Mocks calls to override Cobalt app name api error logging related flags. */ mockCobaltLoggingFlags(Flags flags, boolean override)171 public static void mockCobaltLoggingFlags(Flags flags, boolean override) { 172 mockCobaltLoggingEnabled(flags, override); 173 mockAppNameApiErrorCobaltLoggingEnabled(flags, override); 174 mockAdservicesReleaseStageForCobalt(flags); 175 } 176 177 /** 178 * Mock {@link AdServicesJobServiceLogger#persistJobExecutionData(int, long)} to wait for it to 179 * complete. 180 */ syncPersistJobExecutionData( AdServicesJobServiceLogger logger)181 public static JobServiceLoggingCallback syncPersistJobExecutionData( 182 AdServicesJobServiceLogger logger) { 183 JobServiceLoggingCallback callback = new JobServiceLoggingCallback(); 184 doAnswer( 185 invocation -> { 186 invocation.callRealMethod(); 187 callback.onLoggingMethodCalled(); 188 return null; 189 }) 190 .when(logger) 191 .recordOnStartJob(anyInt()); 192 193 return callback; 194 } 195 196 /** 197 * Mock one of below 3 endpoints for a {@link JobService}'s execution to wait for it to 198 * complete. 199 * 200 * <ul> 201 * <li>{@link AdServicesJobServiceLogger#recordOnStopJob(JobParameters, int, boolean)} 202 * <li>{@link AdServicesJobServiceLogger#recordJobSkipped(int, int)} 203 * <li>{@link AdServicesJobServiceLogger#recordJobFinished(int, boolean, boolean)} 204 * </ul> 205 * 206 * @throws IllegalStateException if there is more than one method is called. 207 */ syncLogExecutionStats( AdServicesJobServiceLogger logger)208 public static JobServiceLoggingCallback syncLogExecutionStats( 209 AdServicesJobServiceLogger logger) { 210 JobServiceLoggingCallback callback = new JobServiceLoggingCallback(); 211 212 doAnswer( 213 invocation -> { 214 callback.onLoggingMethodCalled(); 215 return null; 216 }) 217 .when(logger) 218 .recordOnStopJob(any(), anyInt(), anyBoolean()); 219 220 doAnswer( 221 invocation -> { 222 callback.onLoggingMethodCalled(); 223 return null; 224 }) 225 .when(logger) 226 .recordJobSkipped(anyInt(), anyInt()); 227 228 doAnswer( 229 invocation -> { 230 callback.onLoggingMethodCalled(); 231 return null; 232 }) 233 .when(logger) 234 .recordJobFinished(anyInt(), anyBoolean(), anyBoolean()); 235 236 return callback; 237 } 238 239 /** 240 * Verify the logging methods in {@link JobService#onStartJob(JobParameters)} has been invoked. 241 */ verifyOnStartJobLogged( AdServicesJobServiceLogger logger, JobServiceLoggingCallback callback)242 public static void verifyOnStartJobLogged( 243 AdServicesJobServiceLogger logger, JobServiceLoggingCallback callback) 244 throws InterruptedException { 245 callback.assertLoggingFinished(); 246 247 verify(logger).recordOnStartJob(anyInt()); 248 } 249 250 /** 251 * Verify the logging methods in {@link JobService#jobFinished(JobParameters, boolean)} has been 252 * invoked. 253 */ verifyOnJobFinishedLogged( AdServicesJobServiceLogger logger, JobServiceLoggingCallback callback)254 public static void verifyOnJobFinishedLogged( 255 AdServicesJobServiceLogger logger, JobServiceLoggingCallback callback) 256 throws InterruptedException { 257 callback.assertLoggingFinished(); 258 259 verify(logger).recordJobFinished(anyInt(), anyBoolean(), anyBoolean()); 260 } 261 262 /** Get a spied instance of {@link AdServicesJobServiceLogger}. */ getSpiedAdServicesJobServiceLogger( Context context, Flags flags)263 public static AdServicesJobServiceLogger getSpiedAdServicesJobServiceLogger( 264 Context context, Flags flags) { 265 return spy( 266 new AdServicesJobServiceLogger( 267 context, 268 Clock.getInstance(), 269 mock(AdServicesStatsdJobServiceLogger.class), 270 mock(AdServicesErrorLogger.class), 271 Executors.newCachedThreadPool(), 272 AdServicesJobInfo.getJobIdToJobNameMap(), 273 flags)); 274 } 275 MockitoExpectations()276 private MockitoExpectations() { 277 throw new UnsupportedOperationException("Provides only static methods"); 278 } 279 callRealPublicMethodsForBackgroundJobLogging( AdServicesJobServiceLogger logger)280 private static void callRealPublicMethodsForBackgroundJobLogging( 281 AdServicesJobServiceLogger logger) { 282 doCallRealMethod().when(logger).recordOnStartJob(anyInt()); 283 doCallRealMethod().when(logger).recordOnStopJob(any(), anyInt(), anyBoolean()); 284 doCallRealMethod().when(logger).recordJobSkipped(anyInt(), anyInt()); 285 doCallRealMethod().when(logger).recordJobFinished(anyInt(), anyBoolean(), anyBoolean()); 286 } 287 } 288