From eefd8153d93107c04aeb7a0fcd5542ebaced8b26 Mon Sep 17 00:00:00 2001 From: Brett Chabot Date: Fri, 6 Dec 2024 15:28:36 -0800 Subject: [PATCH] Only drain main looper when it is paused. ControlledLooper.drainMainThreadUntilIdle was created so Robolectric can manually execute tasks when main looper is paused. Now with the introduction of INSTRUMENTATION_TEST looper mode with a free running looper, it doesn't make sense to always idle the main looper, and could lead to deviation of behavior between a test run with Robolectric INSTRUMENTATION_TEST mode and real android. This commit changes drainMainThreadUntilIdle so its a no-op if main looper is not paused. PiperOrigin-RevId: 703634485 --- espresso/CHANGELOG.md | 2 ++ .../test/espresso/base/ThreadPoolExecutorExtractor.java | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/espresso/CHANGELOG.md b/espresso/CHANGELOG.md index a1888dc4e..a0993d9c1 100644 --- a/espresso/CHANGELOG.md +++ b/espresso/CHANGELOG.md @@ -16,6 +16,8 @@ The following artifacts were released: **Bug Fixes** +* Fix deadlock in espresso in Robolectric INSTRUMENTATION_TEST + paused looper. + **New Features** **Breaking Changes** diff --git a/espresso/core/java/androidx/test/espresso/base/ThreadPoolExecutorExtractor.java b/espresso/core/java/androidx/test/espresso/base/ThreadPoolExecutorExtractor.java index 0cd67686e..b016f7d44 100644 --- a/espresso/core/java/androidx/test/espresso/base/ThreadPoolExecutorExtractor.java +++ b/espresso/core/java/androidx/test/espresso/base/ThreadPoolExecutorExtractor.java @@ -19,6 +19,8 @@ import android.os.Handler; import android.os.Looper; import androidx.annotation.Nullable; +import androidx.test.internal.platform.ServiceLoaderWrapper; +import androidx.test.internal.platform.os.ControlledLooper; import java.lang.reflect.Field; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; @@ -42,6 +44,10 @@ final class ThreadPoolExecutorExtractor { private static final String MODERN_ASYNC_TASK_FIELD_NAME = "THREAD_POOL_EXECUTOR"; private final Handler mainHandler; + private final ControlledLooper controlledLooper = + ServiceLoaderWrapper.loadSingleService( + ControlledLooper.class, () -> ControlledLooper.NO_OP_CONTROLLED_LOOPER); + @Inject ThreadPoolExecutorExtractor(Looper looper) { mainHandler = new Handler(looper); @@ -85,6 +91,7 @@ public void run() { } }); try { + controlledLooper.drainMainThreadUntilIdle(); latch.await(); } catch (InterruptedException ie) { if (!futureToRun.isDone()) {