1 /* 2 * Copyright (C) 2024 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.adservices.shared.testing.concurrency; 17 18 import static com.android.adservices.shared.testing.concurrency.SyncCallback.LOG_TAG; 19 20 import static org.junit.Assert.assertThrows; 21 import static org.junit.Assume.assumeFalse; 22 import static org.junit.Assume.assumeTrue; 23 24 import static java.lang.Thread.currentThread; 25 import static java.util.concurrent.TimeUnit.MILLISECONDS; 26 27 import com.android.adservices.shared.meta_testing.FakeLogger; 28 import com.android.adservices.shared.testing.LogEntry; 29 import com.android.adservices.shared.testing.Logger.LogLevel; 30 import com.android.adservices.shared.testing.Logger.RealLogger; 31 import com.android.adservices.shared.testing.Nullable; 32 import com.android.adservices.shared.testing.SharedSidelessTestCase; 33 import com.android.adservices.shared.testing.StandardStreamsLogger; 34 35 import com.google.common.collect.ImmutableList; 36 37 import org.junit.Test; 38 39 import java.lang.reflect.Constructor; 40 import java.util.concurrent.ArrayBlockingQueue; 41 import java.util.concurrent.TimeUnit; 42 43 /** Base class for all {@code SyncCallback} implementations. */ 44 public abstract class SyncCallbackTestCase<CB extends SyncCallback & FreezableToString> 45 extends SharedSidelessTestCase { 46 47 protected static final long INJECTION_TIMEOUT_MS = 200; 48 protected static final long CALLBACK_TIMEOUT_MS = INJECTION_TIMEOUT_MS + 400; 49 50 protected final FakeLogger mFakeLogger = new FakeLogger(); 51 52 private final SyncCallbackSettings.Builder mFakeLoggerSettingsBuilder = 53 new SyncCallbackSettings.Builder(mFakeLogger); 54 private final SyncCallbackSettings.Builder mDefaultSettingsBuilder = 55 mFakeLoggerSettingsBuilder 56 .setFailIfCalledOnMainThread(supportsFailIfCalledOnMainThread()) 57 .setMaxTimeoutMs(CALLBACK_TIMEOUT_MS); 58 59 // Used to set the name of the method returned by call() - cannot AtomicRefecence because 60 // call() might be called it might be called AFTER assertCalled() 61 private final ArrayBlockingQueue<String> mSetCalledMethodQueue = new ArrayBlockingQueue<>(1); 62 63 protected final SyncCallbackSettings mDefaultSettings = mDefaultSettingsBuilder.build(); 64 65 protected final ConcurrencyHelper mConcurrencyHelper; 66 67 // TODO(b/342448771): ideally should remove it, but the class hierarchy is messed up (as some 68 // classes are defined on side-less but the test on device-side) SyncCallbackTestCase()69 protected SyncCallbackTestCase() { 70 this(StandardStreamsLogger.getInstance()); 71 } 72 SyncCallbackTestCase(RealLogger realLogger)73 protected SyncCallbackTestCase(RealLogger realLogger) { 74 mConcurrencyHelper = new ConcurrencyHelper(realLogger); 75 } 76 77 /** 78 * Gets a new callback to be used in the test. 79 * 80 * <p>Each call should return a different object. 81 */ newCallback(SyncCallbackSettings settings)82 protected CB newCallback(SyncCallbackSettings settings) { 83 SyncCallback rawCallback = newRawCallback(settings); 84 if (rawCallback == null) { 85 throw new UnsupportedOperationException( 86 "Must override this method or return non-null on newRawCallback()"); 87 } 88 @SuppressWarnings("unchecked") 89 CB castCallback = (CB) (rawCallback); 90 return castCallback; 91 } 92 93 /** 94 * Similar to {@link #newCallback(SyncCallbackSettings)}, but should be used by tests whose 95 * callback type is not available on earlier platform releases (like {@code 96 * android.os.OutcomeReceiver}). 97 */ 98 @Nullable newRawCallback(SyncCallbackSettings settings)99 protected SyncCallback newRawCallback(SyncCallbackSettings settings) { 100 return null; 101 } 102 newFrozenCallback(SyncCallbackSettings settings)103 private CB newFrozenCallback(SyncCallbackSettings settings) { 104 CB callback = newCallback(settings); 105 callback.freezeToString(); 106 return callback; 107 } 108 109 /** Calls the callback and return the name of the called method. */ call(CB callback)110 protected final String call(CB callback) { 111 String methodName = callCallback(callback); 112 if (methodName == null) { 113 throw new IllegalStateException( 114 "Callback " + callback + " returned null to callCallback()"); 115 } 116 mSetCalledMethodQueue.offer(methodName); 117 return methodName; 118 } 119 120 // NOTE: currently it just need one method, so we're always returning poll() 121 122 /** Gets the name of the method returned by {@link #call(SyncCallback)}. */ getSetCalledMethodName()123 private String getSetCalledMethodName() throws InterruptedException { 124 String methodName = mSetCalledMethodQueue.poll(CALLBACK_TIMEOUT_MS, MILLISECONDS); 125 if (methodName == null) { 126 // Shouldn't happen... 127 throw new IllegalStateException( 128 "Could not infer name of setCalled() method after " 129 + CALLBACK_TIMEOUT_MS 130 + " ms"); 131 } 132 return methodName; 133 } 134 135 /** 136 * {@code SyncCallback}s are expected to have 2 constructors (1 that doesn't take any parameter 137 * and 1 that takes a {@link SyncCallbackSettings}), so this methods return {@code true} by 138 * default, but should be overridden to return {@code false} if that's not the case (for 139 * example, if the {@code SyncCallback} uses a factory method or if it needs extra parameters 140 * like a {@code Context}. 141 */ providesExpectedConstructors()142 protected boolean providesExpectedConstructors() { 143 return true; 144 } 145 146 /** 147 * Abstraction to "call" the callback. 148 * 149 * <p><b>Note:</b> must just call the callback right away, not in a background thread. 150 * 151 * @return representation of the method called (like "setCalled()" or "inject(foo)"). 152 */ callCallback(CB callback)153 protected abstract String callCallback(CB callback); 154 155 /** 156 * Checks whether the callback supports being constructor with a {@link SyncCallbackSettings 157 * settings} object that supports {@link SyncCallbackSettings#isFailIfCalledOnMainThread() 158 * failing if called in the main thread}. 159 * 160 * @return {@code true} by default. 161 */ supportsFailIfCalledOnMainThread()162 protected boolean supportsFailIfCalledOnMainThread() { 163 return true; 164 } 165 166 /** Makes sure subclasses provide distinct callbacks, as some tests rely on that. */ 167 @Test testNewCallback()168 public final void testNewCallback() { 169 CB callback1 = newFrozenCallback(mDefaultSettings); 170 expect.withMessage("1st callback").that(callback1).isNotNull(); 171 172 CB callback2 = newFrozenCallback(mDefaultSettings); 173 expect.withMessage("2nd callback").that(callback2).isNotNull(); 174 expect.withMessage("2nd callback").that(callback2).isNotSameInstanceAs(callback1); 175 } 176 177 @Test testHasExpectedConstructors()178 public final void testHasExpectedConstructors() throws Exception { 179 assumeTrue( 180 "callback doesn't provide expected constructors", providesExpectedConstructors()); 181 182 CB callback = newFrozenCallback(mDefaultSettings); 183 @SuppressWarnings("unchecked") 184 Class<CB> callbackClass = (Class<CB>) callback.getClass(); 185 186 Constructor<CB> defaultConstructor = getConstructor(callbackClass); 187 expect.withMessage("Default constructor (%s)", callbackClass) 188 .that(defaultConstructor) 189 .isNotNull(); 190 191 Constructor<CB> settingsConstructor = 192 getConstructor(callbackClass, SyncCallbackSettings.class); 193 expect.withMessage("%s(SyncCallbackSettings) constructor", callbackClass) 194 .that(settingsConstructor) 195 .isNotNull(); 196 } 197 198 @Test testConstructor_cannotFailOnMainThread()199 public final void testConstructor_cannotFailOnMainThread() throws Exception { 200 assumeCannotFailIfCalledOnMainThread(); 201 SyncCallbackSettings settings = 202 mFakeLoggerSettingsBuilder.setFailIfCalledOnMainThread(true).build(); 203 204 assertThrows(IllegalArgumentException.class, () -> newFrozenCallback(settings)); 205 } 206 207 @Nullable getConstructor(Class<CB> callbackClass, Class<?>... parameterTypes)208 private Constructor<CB> getConstructor(Class<CB> callbackClass, Class<?>... parameterTypes) { 209 try { 210 return callbackClass.getDeclaredConstructor(parameterTypes); 211 } catch (Exception e) { 212 mLog.e("Failed to get constructor for class %s: %s", callbackClass, e); 213 return null; 214 } 215 } 216 217 @Test testGetSettings()218 public final void testGetSettings() { 219 CB callback = newFrozenCallback(mDefaultSettings); 220 221 expect.withMessage("getSettings()") 222 .that(callback.getSettings()) 223 .isSameInstanceAs(mDefaultSettings); 224 } 225 226 @Test testGetId()227 public final void testGetId() { 228 CB callback1 = newFrozenCallback(mDefaultSettings); 229 String id1 = callback1.getId(); 230 expect.withMessage("id").that(id1).isNotNull(); 231 232 CB callback2 = newFrozenCallback(mDefaultSettings); 233 String id2 = callback2.getId(); 234 expect.withMessage("id2").that(id2).isNotNull(); 235 expect.withMessage("id2").that(id2).isNotEqualTo(id1); 236 } 237 238 @Test testAssertCalled()239 public final void testAssertCalled() throws Exception { 240 var callback = newFrozenCallback(mDefaultSettings); 241 var log = new LogChecker(callback); 242 243 // Check state before 244 expectIsCalledAndNumberCalls(callback, "before setCalled()", false, 0); 245 246 Thread t = runAsync(INJECTION_TIMEOUT_MS, () -> call(callback)); 247 callback.assertCalled(); 248 249 // Check state after 250 expectIsCalledAndNumberCalls(callback, "after setCalled()", true, 1); 251 String setCalled = getSetCalledMethodName(); 252 expectLoggedCalls( 253 log.d(setCalled + " called on " + t.getName()), 254 log.v(setCalled + " returning"), 255 log.d("assertCalled() called on " + currentThread().getName()), 256 log.v("assertCalled() returning")); 257 258 // Further calls - make sure number actual calls keeps increasing 259 // (don't need to call on bg because it's already called) 260 call(callback); 261 expect.withMessage("%s.getNumberActualCalls() after 2nd call", callback) 262 .that(callback.getNumberActualCalls()) 263 .isEqualTo(2); 264 } 265 266 @Test testAssertCalled_neverCalled()267 public final void testAssertCalled_neverCalled() throws Exception { 268 var callback = newFrozenCallback(mDefaultSettings); 269 var log = new LogChecker(callback); 270 271 var thrown = 272 assertThrows(SyncCallbackTimeoutException.class, () -> callback.assertCalled()); 273 274 expect.withMessage("e.getTimeout()") 275 .that(thrown.getTimeout()) 276 .isEqualTo(mDefaultSettings.getMaxTimeoutMs()); 277 expect.withMessage("e.getUnit()()").that(thrown.getUnit()).isEqualTo(TimeUnit.MILLISECONDS); 278 279 expectIsCalledAndNumberCalls(callback, "after setCalled()", false, 0); 280 expectLoggedCalls( 281 log.d("assertCalled() called on " + currentThread().getName()), 282 log.e("assertCalled() failed: " + thrown)); 283 } 284 285 @Test testAssertCalled_interrupted()286 public final void testAssertCalled_interrupted() throws Exception { 287 var callback = newFrozenCallback(mDefaultSettings); 288 var log = new LogChecker(callback); 289 ArrayBlockingQueue<Throwable> actualFailureQueue = new ArrayBlockingQueue<>(1); 290 291 // Must run it in another thread so it can be interrupted 292 Thread thread = 293 startNewThread( 294 () -> { 295 try { 296 callback.assertCalled(); 297 } catch (Throwable t) { 298 actualFailureQueue.offer(t); 299 } 300 }); 301 thread.interrupt(); 302 303 Throwable thrown = actualFailureQueue.poll(CALLBACK_TIMEOUT_MS, MILLISECONDS); 304 expect.withMessage("thrown exception") 305 .that(thrown) 306 .isInstanceOf(InterruptedException.class); 307 308 expectIsCalledAndNumberCalls(callback, "after interrupted", false, 0); 309 expectLoggedCalls( 310 log.d("assertCalled() called on " + thread.getName()), 311 log.e("assertCalled() failed: " + thrown)); 312 } 313 314 @Test testAssertCalled_multipleCalls()315 public final void testAssertCalled_multipleCalls() throws Exception { 316 SyncCallbackSettings settings = mDefaultSettingsBuilder.setExpectedNumberCalls(2).build(); 317 CB callback = newFrozenCallback(settings); 318 319 // 1st call 320 runAsync(INJECTION_TIMEOUT_MS, () -> call(callback)); 321 assertThrows(SyncCallbackTimeoutException.class, () -> callback.assertCalled()); 322 323 expectIsCalledAndNumberCalls(callback, "after 1st setCalled()", false, 1); 324 325 // 2nd call 326 runAsync(INJECTION_TIMEOUT_MS, () -> call(callback)); 327 callback.assertCalled(); 328 329 expectIsCalledAndNumberCalls(callback, "after 2nd setCalled()", true, 2); 330 331 // Further calls - make sure number actual calls keeps increasing 332 // (don't need to call on bg because it's already called) 333 call(callback); 334 expectIsCalledAndNumberCalls(callback, "after 3rd setCalled()", true, 3); 335 } 336 337 @Test testAssertCalled_multipleCallsFromMultipleCallbacks_firstFinishFirst()338 public final void testAssertCalled_multipleCallsFromMultipleCallbacks_firstFinishFirst() 339 throws Exception { 340 // Set a smaller timeout, as it's expect to fail multiple times 341 long injectionTimeoutMs = 20; 342 SyncCallbackSettings settings = 343 mDefaultSettingsBuilder 344 .setExpectedNumberCalls(4) 345 .setMaxTimeoutMs(injectionTimeoutMs + 40) 346 .build(); 347 CB callback1 = newFrozenCallback(settings); 348 CB callback2 = newFrozenCallback(settings); 349 350 // 1st call on 1st callback 351 runAsync(injectionTimeoutMs, () -> call(callback1)); 352 assertThrows(SyncCallbackTimeoutException.class, () -> callback1.assertCalled()); 353 assertThrows(SyncCallbackTimeoutException.class, () -> callback2.assertCalled()); 354 expectIsCalled(callback1, "after 1st call on 1st callback", false); 355 expectIsCalled(callback2, "after 1st call on 1st callback", false); 356 357 // 1st call on 2nd callback 358 runAsync(injectionTimeoutMs, () -> call(callback2)); 359 assertThrows(SyncCallbackTimeoutException.class, () -> callback1.assertCalled()); 360 assertThrows(SyncCallbackTimeoutException.class, () -> callback2.assertCalled()); 361 expectIsCalled(callback1, "after 1st call on 2nd callback", false); 362 expectIsCalled(callback2, "after 1st call on 2nd callback", false); 363 364 // 2nd call on 2nd callback 365 runAsync(injectionTimeoutMs, () -> call(callback2)); 366 assertThrows(SyncCallbackTimeoutException.class, () -> callback1.assertCalled()); 367 assertThrows(SyncCallbackTimeoutException.class, () -> callback2.assertCalled()); 368 expectIsCalled(callback1, "after 2nd call on 2nd callback", false); 369 expectIsCalled(callback2, "after 2nd call on 2nd callback", false); 370 371 // 2nd call on 1st callback 372 runAsync(injectionTimeoutMs, () -> call(callback1)); 373 callback1.assertCalled(); 374 callback2.assertCalled(); 375 expectIsCalledAndNumberCalls(callback1, "after 2nd call on 1st callback", true, 2); 376 expectIsCalledAndNumberCalls(callback2, "after 2nd call on 1st callback", true, 2); 377 378 // Further calls - make sure number actual calls keeps increasing 379 // (don't need to call on bg because it's already called) 380 call(callback1); 381 expectIsCalledAndNumberCalls(callback1, "after 3rd call on 1st callback", true, 3); 382 expectIsCalledAndNumberCalls(callback2, "after 3rd call on 1st callback", true, 2); 383 call(callback2); 384 expectIsCalledAndNumberCalls(callback1, "after 3rd call on 2nd callback", true, 3); 385 expectIsCalledAndNumberCalls(callback2, "after 3rd call on 2nd callback", true, 3); 386 } 387 388 @Test testAssertCalled_multipleCallsFromMultipleCallbacks_secondFinishFirst()389 public final void testAssertCalled_multipleCallsFromMultipleCallbacks_secondFinishFirst() 390 throws Exception { 391 // Set a smaller timeout, as it's expect to fail multiple times 392 long injectionTimeoutMs = 20; 393 SyncCallbackSettings settings = 394 mDefaultSettingsBuilder 395 .setExpectedNumberCalls(4) 396 .setMaxTimeoutMs(injectionTimeoutMs + 40) 397 .build(); 398 CB callback1 = newFrozenCallback(settings); 399 CB callback2 = newFrozenCallback(settings); 400 401 // 1st call on 1st callback 402 runAsync(injectionTimeoutMs, () -> call(callback1)); 403 assertThrows(SyncCallbackTimeoutException.class, () -> callback1.assertCalled()); 404 assertThrows(SyncCallbackTimeoutException.class, () -> callback2.assertCalled()); 405 expectIsCalled(callback1, "after 1st call on 1st callback", false); 406 expectIsCalled(callback2, "after 1st call on 1st callback", false); 407 408 // 1st call on 2nd callback 409 runAsync(injectionTimeoutMs, () -> call(callback2)); 410 assertThrows(SyncCallbackTimeoutException.class, () -> callback1.assertCalled()); 411 assertThrows(SyncCallbackTimeoutException.class, () -> callback2.assertCalled()); 412 expectIsCalled(callback1, "after 1st call on 2nd callback", false); 413 expectIsCalled(callback2, "after 1st call on 2nd callback", false); 414 415 // 2nd call on 1st callback 416 runAsync(injectionTimeoutMs, () -> call(callback1)); 417 assertThrows(SyncCallbackTimeoutException.class, () -> callback1.assertCalled()); 418 assertThrows(SyncCallbackTimeoutException.class, () -> callback2.assertCalled()); 419 expectIsCalled(callback1, "after 2nd call on 1st callback", false); 420 expectIsCalled(callback2, "after 2nd call on 1st callback", false); 421 422 // 2nd call on 2nd callback 423 runAsync(injectionTimeoutMs, () -> call(callback2)); 424 callback1.assertCalled(); 425 callback2.assertCalled(); 426 expectIsCalledAndNumberCalls(callback1, "after 2nd call on 2nd callback", true, 2); 427 expectIsCalledAndNumberCalls(callback2, "after 2nd call on 2nd callback", true, 2); 428 429 // Further calls - make sure number actual calls keeps increasing 430 // (don't need to call on bg because it's already called) 431 call(callback1); 432 expectIsCalledAndNumberCalls(callback1, "after 3rd call on 1st callback", true, 3); 433 expectIsCalledAndNumberCalls(callback2, "after 3rd call on 1st callback", true, 2); 434 call(callback2); 435 expectIsCalledAndNumberCalls(callback1, "after 3rd call on 2nd callback", true, 3); 436 expectIsCalledAndNumberCalls(callback2, "after 3rd call on 2nd callback", true, 3); 437 } 438 439 @Test testAssertCalled_failsWhenCalledOnMainThread()440 public final void testAssertCalled_failsWhenCalledOnMainThread() throws Exception { 441 assumeCanFailIfCalledOnMainThread(); 442 SyncCallbackSettings settings = 443 new SyncCallbackSettings.Builder(mFakeLogger, () -> Boolean.TRUE) 444 .setMaxTimeoutMs(CALLBACK_TIMEOUT_MS) 445 .setFailIfCalledOnMainThread(true) 446 .build(); 447 448 var callback = newFrozenCallback(settings); 449 var log = new LogChecker(callback); 450 451 // setCalled() passes... 452 // NOTE: not really the main thread, as it's emulated 453 Thread mainThread = runAsync(INJECTION_TIMEOUT_MS, () -> call(callback)); 454 455 String setCalled = getSetCalledMethodName(); 456 var thrown = assertThrows(CalledOnMainThreadException.class, () -> callback.assertCalled()); 457 expect.withMessage("thrown") 458 .that(thrown) 459 .hasMessageThat() 460 .contains(setCalled + " called on main thread (" + mainThread.getName() + ")"); 461 462 expectIsCalledAndNumberCalls(callback, "after setCalled()", true, 1); 463 464 expectLoggedCalls( 465 log.d(setCalled + " called on " + mainThread.getName()), 466 log.v(setCalled + " returning"), 467 log.d("assertCalled() called on " + currentThread().getName()), 468 log.e("assertCalled() failed: " + thrown)); 469 } 470 471 /** Helper method to assert the value of {@code isCalled()}. */ expectIsCalled( SyncCallback callback, String when, boolean expectedIsCalled)472 protected final void expectIsCalled( 473 SyncCallback callback, String when, boolean expectedIsCalled) { 474 expect.withMessage("%s.isCalled() %s", callback, when) 475 .that(callback.isCalled()) 476 .isEqualTo(expectedIsCalled); 477 } 478 479 /** 480 * Helper method to assert the value of {@code isCalled()} and {@code getNumberActualCalls()}. 481 */ expectIsCalledAndNumberCalls( SyncCallback callback, String when, boolean expectedIsCalled, int expectedNumberCalls)482 protected final void expectIsCalledAndNumberCalls( 483 SyncCallback callback, String when, boolean expectedIsCalled, int expectedNumberCalls) { 484 expectIsCalled(callback, when, expectedIsCalled); 485 expect.withMessage("%s.getNumberActualCalls() %s", callback, when) 486 .that(callback.getNumberActualCalls()) 487 .isEqualTo(expectedNumberCalls); 488 } 489 490 /** Helper methods to assert calls to the callback {@code logX()} methods. */ expectLoggedCalls(@ullable LogEntry... expectedEntries)491 public final void expectLoggedCalls(@Nullable LogEntry... expectedEntries) { 492 ImmutableList<LogEntry> entries = mFakeLogger.getEntries(); 493 expect.withMessage("log entries").that(entries).containsExactlyElementsIn(expectedEntries); 494 } 495 runAsync(long timeoutMs, Runnable r)496 protected final Thread runAsync(long timeoutMs, Runnable r) { 497 return mConcurrencyHelper.runAsync(timeoutMs, r); 498 } 499 startNewThread(Runnable r)500 protected final Thread startNewThread(Runnable r) { 501 return mConcurrencyHelper.startNewThread(r); 502 } 503 assumeCannotFailIfCalledOnMainThread()504 private void assumeCannotFailIfCalledOnMainThread() { 505 assumeFalse( 506 "callback can fail if called on main thread", supportsFailIfCalledOnMainThread()); 507 } 508 assumeCanFailIfCalledOnMainThread()509 private void assumeCanFailIfCalledOnMainThread() { 510 assumeTrue( 511 "callback cannot fail if called on main thread", 512 supportsFailIfCalledOnMainThread()); 513 } 514 515 /** Helper class used to to assert calls to the callback {@code logX()} methods. */ 516 protected static final class LogChecker { 517 518 private final AbstractSyncCallback mCallback; 519 LogChecker(SyncCallback callback)520 public LogChecker(SyncCallback callback) { 521 if (!(callback instanceof AbstractSyncCallback)) { 522 throw new IllegalArgumentException( 523 "Not an instance of AbstractSyncCallback: " + callback); 524 } 525 this.mCallback = AbstractSyncCallback.class.cast(callback); 526 } 527 e(String expectedMessage)528 public LogEntry e(String expectedMessage) { 529 return new LogEntry(LogLevel.ERROR, LOG_TAG, mCallback + ": " + expectedMessage); 530 } 531 d(String expectedMessage)532 public LogEntry d(String expectedMessage) { 533 return new LogEntry( 534 LogLevel.DEBUG, LOG_TAG, mCallback.toStringLite() + ": " + expectedMessage); 535 } 536 v(String expectedMessage)537 public LogEntry v(String expectedMessage) { 538 return new LogEntry(LogLevel.VERBOSE, LOG_TAG, mCallback + ": " + expectedMessage); 539 } 540 } 541 } 542