Commit 7a9d7b86 authored by Michael Thiessen's avatar Michael Thiessen Committed by Commit Bot

Reland "Introduce CriteriaHelper#pollUiThreadNested"

Introduce CriteriaHelper#pollUiThreadNested
This change adds an assertion that pollUiThread is not called from the
UI thread, which is equivalent to calling sleep(), and won't actually
run any UI thread tasks.

In the few cases where polling the UI Thread from the UI thread is
necessary, I introduce CriteriaHelper#pollUiThreadNested, which nests
the looper in order to continue running UI tasks.

Bug: 1115220
Change-Id: I9338a607590e8ee0a22bc32477f02867c6e37271
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2357289Reviewed-by: default avatarYaron Friedman <yfriedman@chromium.org>
Commit-Queue: Michael Thiessen <mthiesse@chromium.org>
Cr-Commit-Position: refs/heads/master@{#798862}
parent cd65cac7
...@@ -3262,11 +3262,7 @@ android_library("native_java_unittests_java") { ...@@ -3262,11 +3262,7 @@ android_library("native_java_unittests_java") {
"//url/mojom:url_mojom_gurl_java", "//url/mojom:url_mojom_gurl_java",
] ]
sources = [ sources = native_java_unittests_tests
"native_java_unittests/src/org/chromium/chrome/browser/UnitTestUtils.java",
]
sources += native_java_unittests_tests
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ] annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
} }
......
...@@ -63,8 +63,8 @@ public class BookmarkPersonalizedSigninPromoDismissTest { ...@@ -63,8 +63,8 @@ public class BookmarkPersonalizedSigninPromoDismissTest {
BookmarkModel bookmarkModel = new BookmarkModel(Profile.fromWebContents( BookmarkModel bookmarkModel = new BookmarkModel(Profile.fromWebContents(
mSyncTestRule.getActivity().getActivityTab().getWebContents())); mSyncTestRule.getActivity().getActivityTab().getWebContents()));
bookmarkModel.loadFakePartnerBookmarkShimForTesting(); bookmarkModel.loadFakePartnerBookmarkShimForTesting();
BookmarkTestUtil.waitForBookmarkModelLoaded();
}); });
BookmarkTestUtil.waitForBookmarkModelLoaded();
} }
@After @After
......
...@@ -686,12 +686,9 @@ public class LayoutManagerTest implements MockTabModelDelegate { ...@@ -686,12 +686,9 @@ public class LayoutManagerTest implements MockTabModelDelegate {
CriteriaHelper.pollUiThread( CriteriaHelper.pollUiThread(
mActivityTestRule.getActivity().getTabModelSelector()::isTabStateInitialized); mActivityTestRule.getActivity().getTabModelSelector()::isTabStateInitialized);
TestThreadUtils.runOnUiThreadBlocking(() -> { LayoutManagerChrome layoutManager = mActivityTestRule.getActivity().getLayoutManager();
LayoutManagerChrome layoutManager = mActivityTestRule.getActivity().getLayoutManager(); TestThreadUtils.runOnUiThreadBlocking(() -> layoutManager.showOverview(false));
layoutManager.showOverview(false); CriteriaHelper.pollUiThread(layoutManager::overviewVisible);
CriteriaHelper.pollUiThread(layoutManager::overviewVisible);
});
} }
private Layout getActiveLayout() { private Layout getActiveLayout() {
......
...@@ -31,6 +31,7 @@ import androidx.test.espresso.contrib.RecyclerViewActions; ...@@ -31,6 +31,7 @@ import androidx.test.espresso.contrib.RecyclerViewActions;
import androidx.test.filters.MediumTest; import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import org.hamcrest.Matchers;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
...@@ -493,16 +494,23 @@ public class HomepagePromoTest { ...@@ -493,16 +494,23 @@ public class HomepagePromoTest {
private void scrollToHomepagePromo() { private void scrollToHomepagePromo() {
onView(instanceOf(RecyclerView.class)) onView(instanceOf(RecyclerView.class))
.perform(RecyclerViewActions.scrollToPosition(NTP_HEADER_POSITION + 1)); .perform(RecyclerViewActions.scrollToPosition(NTP_HEADER_POSITION + 1));
waitForView((ViewGroup) mActivityTestRule.getActivity().findViewById(R.id.homepage_promo), TestThreadUtils.runOnUiThreadBlocking(() -> {
allOf(withId(R.id.promo_primary_button), isDisplayed())); waitForView(
(ViewGroup) mActivityTestRule.getActivity().findViewById(R.id.homepage_promo),
allOf(withId(R.id.promo_primary_button), isDisplayed()));
});
// Verify impress tracking metrics is working. CriteriaHelper.pollUiThread(() -> {
Assert.assertEquals("Promo created should be seen.", 1, // Verify impress tracking metrics is working.
RecordHistogram.getHistogramValueCountForTesting( Criteria.checkThat("Promo created should be seen.",
METRICS_HOMEPAGE_PROMO, HomepagePromoAction.SEEN)); RecordHistogram.getHistogramValueCountForTesting(
Assert.assertEquals("Impression should be tracked in shared preference.", 1, METRICS_HOMEPAGE_PROMO, HomepagePromoAction.SEEN),
SharedPreferencesManager.getInstance().readInt( Matchers.is(1));
HomepagePromoUtils.getTimesSeenKey())); Criteria.checkThat("Impression should be tracked in shared preference.",
SharedPreferencesManager.getInstance().readInt(
HomepagePromoUtils.getTimesSeenKey()),
Matchers.is(1));
});
Mockito.verify(mTracker).notifyEvent(EventConstants.HOMEPAGE_PROMO_SEEN); Mockito.verify(mTracker).notifyEvent(EventConstants.HOMEPAGE_PROMO_SEEN);
} }
...@@ -533,8 +541,10 @@ public class HomepagePromoTest { ...@@ -533,8 +541,10 @@ public class HomepagePromoTest {
// screen. // screen.
onView(instanceOf(RecyclerView.class)) onView(instanceOf(RecyclerView.class))
.perform(RecyclerViewActions.scrollToPosition(feedHeaderPosition)); .perform(RecyclerViewActions.scrollToPosition(feedHeaderPosition));
waitForView(rootView, TestThreadUtils.runOnUiThreadBlocking(() -> {
allOf(withId(R.id.header_status), waitForView(rootView,
withText(expanded ? R.string.hide : R.string.show))); allOf(withId(R.id.header_status),
withText(expanded ? R.string.hide : R.string.show)));
});
} }
} }
...@@ -216,7 +216,9 @@ public class SigninSignoutIntegrationTest { ...@@ -216,7 +216,9 @@ public class SigninSignoutIntegrationTest {
mBookmarkModel = new BookmarkModel(Profile.fromWebContents( mBookmarkModel = new BookmarkModel(Profile.fromWebContents(
mActivityTestRule.getActivity().getActivityTab().getWebContents())); mActivityTestRule.getActivity().getActivityTab().getWebContents()));
mBookmarkModel.loadFakePartnerBookmarkShimForTesting(); mBookmarkModel.loadFakePartnerBookmarkShimForTesting();
BookmarkTestUtil.waitForBookmarkModelLoaded(); });
BookmarkTestUtil.waitForBookmarkModelLoaded();
TestThreadUtils.runOnUiThreadBlocking(() -> {
Assert.assertEquals(0, mBookmarkModel.getChildCount(mBookmarkModel.getDefaultFolder())); Assert.assertEquals(0, mBookmarkModel.getChildCount(mBookmarkModel.getDefaultFolder()));
mBookmarkModel.addBookmark( mBookmarkModel.addBookmark(
mBookmarkModel.getDefaultFolder(), 0, "Test Bookmark", "http://google.com"); mBookmarkModel.getDefaultFolder(), 0, "Test Bookmark", "http://google.com");
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser;
import android.os.Handler;
import android.os.Looper;
import org.junit.Assert;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.TimeoutTimer;
import org.chromium.content_public.browser.test.NestedSystemMessageHandler;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Utilities for use in Native Java Unit Tests.
*/
public class UnitTestUtils {
/**
* Polls the UI thread waiting for the Callable |criteria| to return true.
*
* In practice, this nests the looper and checks the criteria after each task is run as these
* tests run on the UI thread and sleeping would block the thing we're waiting for.
*/
public static void pollUiThread(final Callable<Boolean> criteria) throws Exception {
assert ThreadUtils.runningOnUiThread();
boolean isSatisfied = criteria.call();
TimeoutTimer timer = new TimeoutTimer(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL);
Handler handler = new Handler(Looper.myLooper());
AtomicBoolean called = new AtomicBoolean(true);
while (!isSatisfied && !timer.isTimedOut()) {
// Ensure we pump the message handler in case no new tasks arrive.
if (called.get()) {
called.set(false);
handler.postDelayed(
() -> { called.set(true); }, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
}
NestedSystemMessageHandler.runSingleNestedLooperTask(Looper.myQueue());
isSatisfied = criteria.call();
}
Assert.assertFalse("Timed out waiting for condition", timer.isTimedOut());
Assert.assertTrue(isSatisfied);
}
}
\ No newline at end of file
...@@ -18,9 +18,9 @@ import org.chromium.base.Callback; ...@@ -18,9 +18,9 @@ import org.chromium.base.Callback;
import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.CalledByNativeJavaTest; import org.chromium.base.annotations.CalledByNativeJavaTest;
import org.chromium.base.annotations.DisabledCalledByNativeJavaTest; import org.chromium.base.annotations.DisabledCalledByNativeJavaTest;
import org.chromium.chrome.browser.UnitTestUtils;
import org.chromium.chrome.browser.instantapps.InstantAppsHandler; import org.chromium.chrome.browser.instantapps.InstantAppsHandler;
import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.installedapp.mojom.InstalledAppProvider; import org.chromium.installedapp.mojom.InstalledAppProvider;
import org.chromium.installedapp.mojom.RelatedApplication; import org.chromium.installedapp.mojom.RelatedApplication;
import org.chromium.url.GURL; import org.chromium.url.GURL;
...@@ -372,7 +372,7 @@ public class InstalledAppProviderTest { ...@@ -372,7 +372,7 @@ public class InstalledAppProviderTest {
called.set(true); called.set(true);
} }
}); });
UnitTestUtils.pollUiThread(() -> called.get()); CriteriaHelper.pollUiThreadNested(() -> called.get());
} }
@CalledByNative @CalledByNative
......
...@@ -14,10 +14,10 @@ import org.mockito.MockitoAnnotations; ...@@ -14,10 +14,10 @@ import org.mockito.MockitoAnnotations;
import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.CalledByNativeJavaTest; import org.chromium.base.annotations.CalledByNativeJavaTest;
import org.chromium.chrome.browser.UnitTestUtils;
import org.chromium.components.payments.PayerData; import org.chromium.components.payments.PayerData;
import org.chromium.components.payments.PaymentApp; import org.chromium.components.payments.PaymentApp;
import org.chromium.components.payments.SupportedDelegations; import org.chromium.components.payments.SupportedDelegations;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.payments.mojom.PaymentCurrencyAmount; import org.chromium.payments.mojom.PaymentCurrencyAmount;
import org.chromium.payments.mojom.PaymentDetailsModifier; import org.chromium.payments.mojom.PaymentDetailsModifier;
import org.chromium.payments.mojom.PaymentItem; import org.chromium.payments.mojom.PaymentItem;
...@@ -93,7 +93,7 @@ public class AndroidPaymentAppUnitTest { ...@@ -93,7 +93,7 @@ public class AndroidPaymentAppUnitTest {
mReadyToPayResponse = isReadyToPay; mReadyToPayResponse = isReadyToPay;
} }
}); });
UnitTestUtils.pollUiThread(() -> mReadyToPayQueryFinished); CriteriaHelper.pollUiThreadNested(() -> mReadyToPayQueryFinished);
Assert.assertTrue("Payment app should be ready to pay", mReadyToPayResponse); Assert.assertTrue("Payment app should be ready to pay", mReadyToPayResponse);
PaymentItem total = new PaymentItem(); PaymentItem total = new PaymentItem();
...@@ -131,6 +131,6 @@ public class AndroidPaymentAppUnitTest { ...@@ -131,6 +131,6 @@ public class AndroidPaymentAppUnitTest {
intentResult.data.putExtras(extras); intentResult.data.putExtras(extras);
app.onIntentCompletedForTesting(intentResult); app.onIntentCompletedForTesting(intentResult);
UnitTestUtils.pollUiThread(() -> mInvokePaymentAppFinished); CriteriaHelper.pollUiThreadNested(() -> mInvokePaymentAppFinished);
} }
} }
...@@ -27,7 +27,6 @@ import org.chromium.base.annotations.CalledByNative; ...@@ -27,7 +27,6 @@ import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.CalledByNativeJavaTest; import org.chromium.base.annotations.CalledByNativeJavaTest;
import org.chromium.base.task.TaskRunner; import org.chromium.base.task.TaskRunner;
import org.chromium.base.test.util.Feature; import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.UnitTestUtils;
import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabImpl; import org.chromium.chrome.browser.tab.TabImpl;
import org.chromium.chrome.browser.tab.TabLaunchType; import org.chromium.chrome.browser.tab.TabLaunchType;
...@@ -37,6 +36,7 @@ import org.chromium.chrome.browser.tabmodel.TabPersistentStore.TabPersistentStor ...@@ -37,6 +36,7 @@ import org.chromium.chrome.browser.tabmodel.TabPersistentStore.TabPersistentStor
import org.chromium.chrome.browser.tabmodel.TabPersistentStore.TabRestoreDetails; import org.chromium.chrome.browser.tabmodel.TabPersistentStore.TabRestoreDetails;
import org.chromium.components.embedder_support.util.UrlConstants; import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.content_public.browser.LoadUrlParams; import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -107,7 +107,7 @@ public class TabPersistentStoreUnitTest { ...@@ -107,7 +107,7 @@ public class TabPersistentStoreUnitTest {
final AtomicBoolean flushed = new AtomicBoolean(false); final AtomicBoolean flushed = new AtomicBoolean(false);
if (mPersistentStore != null) { if (mPersistentStore != null) {
mPersistentStore.getTaskRunnerForTests().postTask(() -> { flushed.set(true); }); mPersistentStore.getTaskRunnerForTests().postTask(() -> { flushed.set(true); });
UnitTestUtils.pollUiThread(() -> flushed.get()); CriteriaHelper.pollUiThreadNested(() -> flushed.get());
} }
} }
......
...@@ -113,7 +113,7 @@ public class ViewUtils { ...@@ -113,7 +113,7 @@ public class ViewUtils {
*/ */
public static void waitForView( public static void waitForView(
ViewGroup root, Matcher<View> viewMatcher, @ExpectedViewState int viewState) { ViewGroup root, Matcher<View> viewMatcher, @ExpectedViewState int viewState) {
CriteriaHelper.pollUiThread(new ExpectedViewCriteria(viewMatcher, viewState, root)); CriteriaHelper.pollUiThreadNested(new ExpectedViewCriteria(viewMatcher, viewState, root));
} }
/** /**
...@@ -133,7 +133,7 @@ public class ViewUtils { ...@@ -133,7 +133,7 @@ public class ViewUtils {
Matcher<View> viewMatcher, @ExpectedViewState int viewState) { Matcher<View> viewMatcher, @ExpectedViewState int viewState) {
return (View view, NoMatchingViewException noMatchException) -> { return (View view, NoMatchingViewException noMatchException) -> {
if (noMatchException != null) throw noMatchException; if (noMatchException != null) throw noMatchException;
CriteriaHelper.pollUiThread( CriteriaHelper.pollUiThreadNested(
new ExpectedViewCriteria(viewMatcher, viewState, (ViewGroup) view)); new ExpectedViewCriteria(viewMatcher, viewState, (ViewGroup) view));
}; };
} }
...@@ -167,12 +167,13 @@ public class ViewUtils { ...@@ -167,12 +167,13 @@ public class ViewUtils {
* @return An interaction on the matching view. * @return An interaction on the matching view.
*/ */
public static ViewInteraction onViewWaiting(Matcher<View> viewMatcher) { public static ViewInteraction onViewWaiting(Matcher<View> viewMatcher) {
Espresso.onView(ViewMatchers.isRoot()) CriteriaHelper.pollInstrumentationThread(() -> {
.check((View view, NoMatchingViewException noMatchException) -> { Espresso.onView(ViewMatchers.isRoot())
if (noMatchException != null) throw noMatchException; .check((View view, NoMatchingViewException noMatchException) -> {
CriteriaHelper.pollUiThread( if (noMatchException != null) throw noMatchException;
new ExpectedViewCriteria(viewMatcher, VIEW_VISIBLE, (ViewGroup) view)); new ExpectedViewCriteria(viewMatcher, VIEW_VISIBLE, (ViewGroup) view).run();
}); });
});
return Espresso.onView(viewMatcher); return Espresso.onView(viewMatcher);
} }
......
...@@ -481,6 +481,7 @@ android_library("content_javatests") { ...@@ -481,6 +481,7 @@ android_library("content_javatests") {
"javatests/src/org/chromium/content/browser/ContentViewLocationTest.java", "javatests/src/org/chromium/content/browser/ContentViewLocationTest.java",
"javatests/src/org/chromium/content/browser/ContentViewPointerTypeTest.java", "javatests/src/org/chromium/content/browser/ContentViewPointerTypeTest.java",
"javatests/src/org/chromium/content/browser/ContentViewScrollingTest.java", "javatests/src/org/chromium/content/browser/ContentViewScrollingTest.java",
"javatests/src/org/chromium/content/browser/CriteriaHelperTest.java",
"javatests/src/org/chromium/content/browser/EncodeHtmlDataUriTest.java", "javatests/src/org/chromium/content/browser/EncodeHtmlDataUriTest.java",
"javatests/src/org/chromium/content/browser/GestureDetectorResetTest.java", "javatests/src/org/chromium/content/browser/GestureDetectorResetTest.java",
"javatests/src/org/chromium/content/browser/GestureListenerManagerTest.java", "javatests/src/org/chromium/content/browser/GestureListenerManagerTest.java",
...@@ -542,7 +543,6 @@ junit_binary("content_junit_tests") { ...@@ -542,7 +543,6 @@ junit_binary("content_junit_tests") {
sources = [ sources = [
"junit/src/org/chromium/content/browser/BindingManagerTest.java", "junit/src/org/chromium/content/browser/BindingManagerTest.java",
"junit/src/org/chromium/content/browser/ChildProcessRankingTest.java", "junit/src/org/chromium/content/browser/ChildProcessRankingTest.java",
"junit/src/org/chromium/content/browser/CriteriaHelperTest.java",
"junit/src/org/chromium/content/browser/ScreenOrientationProviderImplTest.java", "junit/src/org/chromium/content/browser/ScreenOrientationProviderImplTest.java",
"junit/src/org/chromium/content/browser/SpareChildConnectionTest.java", "junit/src/org/chromium/content/browser/SpareChildConnectionTest.java",
"junit/src/org/chromium/content/browser/UiThreadTaskTraitsImplTest.java", "junit/src/org/chromium/content/browser/UiThreadTaskTraitsImplTest.java",
......
...@@ -5,11 +5,8 @@ ...@@ -5,11 +5,8 @@
package org.chromium.content.browser; package org.chromium.content.browser;
import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.chromium.base.task.TaskTraits.THREAD_POOL;
import static org.chromium.content_public.browser.test.util.CriteriaHelper.DEFAULT_POLLING_INTERVAL; import static org.chromium.content_public.browser.test.util.CriteriaHelper.DEFAULT_POLLING_INTERVAL;
import androidx.test.filters.MediumTest; import androidx.test.filters.MediumTest;
...@@ -22,8 +19,9 @@ import org.junit.rules.ExpectedException; ...@@ -22,8 +19,9 @@ import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.chromium.base.ThreadUtils; import org.chromium.base.ThreadUtils;
import org.chromium.base.task.PostTask; import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.BaseRobolectricTestRunner; import org.chromium.base.test.UiThreadTest;
import org.chromium.base.test.util.Batch;
import org.chromium.content_public.browser.test.util.Criteria; import org.chromium.content_public.browser.test.util.Criteria;
import org.chromium.content_public.browser.test.util.CriteriaHelper; import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.content_public.browser.test.util.CriteriaNotSatisfiedException; import org.chromium.content_public.browser.test.util.CriteriaNotSatisfiedException;
...@@ -34,7 +32,8 @@ import java.io.StringWriter; ...@@ -34,7 +32,8 @@ import java.io.StringWriter;
/** /**
* Tests for {@link CriteriaHelper}. * Tests for {@link CriteriaHelper}.
*/ */
@RunWith(BaseRobolectricTestRunner.class) @RunWith(BaseJUnit4ClassRunner.class)
@Batch(Batch.UNIT_TESTS)
public class CriteriaHelperTest { public class CriteriaHelperTest {
private static final String ERROR_MESSAGE = "my special error message"; private static final String ERROR_MESSAGE = "my special error message";
...@@ -44,29 +43,23 @@ public class CriteriaHelperTest { ...@@ -44,29 +43,23 @@ public class CriteriaHelperTest {
@Test @Test
@MediumTest @MediumTest
public void testUiThread() { public void testUiThread() {
// Robolectric runs the test on UI thread. CriteriaHelper.pollUiThread(
assertTrue(ThreadUtils.runningOnUiThread()); () -> Criteria.checkThat(ThreadUtils.runningOnUiThread(), Matchers.is(true)));
// In Instrumented tests, the tests would be on instrumentation thread instead. }
// Emulate that behavior by posting the body of the test.
PostTask.postTask(THREAD_POOL, () -> { @Test
assertFalse(ThreadUtils.runningOnUiThread()); @MediumTest
CriteriaHelper.pollUiThread( @UiThreadTest
() -> Criteria.checkThat(ThreadUtils.runningOnUiThread(), Matchers.is(true))); public void testUiThreadNested() {
}); CriteriaHelper.pollUiThreadNested(
() -> Criteria.checkThat(ThreadUtils.runningOnUiThread(), Matchers.is(true)));
} }
@Test @Test
@MediumTest @MediumTest
public void testInstrumentationThread() { public void testInstrumentationThread() {
// Robolectric runs the test on UI thread. CriteriaHelper.pollInstrumentationThread(
assertTrue(ThreadUtils.runningOnUiThread()); () -> Criteria.checkThat(ThreadUtils.runningOnUiThread(), Matchers.is(false)));
// In Instrumented tests, the tests would be on instrumentation thread instead.
// Emulate that behavior by posting the body of the test.
PostTask.postTask(THREAD_POOL, () -> {
assertFalse(ThreadUtils.runningOnUiThread());
CriteriaHelper.pollInstrumentationThread(
() -> Criteria.checkThat(ThreadUtils.runningOnUiThread(), Matchers.is(false)));
});
} }
@Test @Test
...@@ -75,6 +68,13 @@ public class CriteriaHelperTest { ...@@ -75,6 +68,13 @@ public class CriteriaHelperTest {
CriteriaHelper.pollUiThread(() -> {}); CriteriaHelper.pollUiThread(() -> {});
} }
@Test
@MediumTest
@UiThreadTest
public void testPass_Runnable_UiThreadNested() {
CriteriaHelper.pollUiThreadNested(() -> {});
}
@Test @Test
@MediumTest @MediumTest
public void testPass_Runnable_InstrumentationThread() { public void testPass_Runnable_InstrumentationThread() {
...@@ -87,6 +87,13 @@ public class CriteriaHelperTest { ...@@ -87,6 +87,13 @@ public class CriteriaHelperTest {
CriteriaHelper.pollUiThread(() -> true); CriteriaHelper.pollUiThread(() -> true);
} }
@Test
@MediumTest
@UiThreadTest
public void testPass_Callable_UiThreadNested() {
CriteriaHelper.pollUiThreadNested(() -> true);
}
@Test @Test
@MediumTest @MediumTest
public void testPass_Callable_InstrumentationThread() { public void testPass_Callable_InstrumentationThread() {
...@@ -102,6 +109,16 @@ public class CriteriaHelperTest { ...@@ -102,6 +109,16 @@ public class CriteriaHelperTest {
}, 0, DEFAULT_POLLING_INTERVAL); }, 0, DEFAULT_POLLING_INTERVAL);
} }
@Test
@MediumTest
@UiThreadTest
public void testThrow_Runnable_UiThreadNested() {
thrown.expect(AssertionError.class);
CriteriaHelper.pollUiThreadNested(() -> {
throw new CriteriaNotSatisfiedException("");
}, 0, DEFAULT_POLLING_INTERVAL);
}
@Test @Test
@MediumTest @MediumTest
public void testThrow_Runnable_InstrumentationThread() { public void testThrow_Runnable_InstrumentationThread() {
...@@ -118,6 +135,14 @@ public class CriteriaHelperTest { ...@@ -118,6 +135,14 @@ public class CriteriaHelperTest {
CriteriaHelper.pollUiThread(() -> false, 0, DEFAULT_POLLING_INTERVAL); CriteriaHelper.pollUiThread(() -> false, 0, DEFAULT_POLLING_INTERVAL);
} }
@Test
@MediumTest
@UiThreadTest
public void testThrow_Callable_UiThreadNested() {
thrown.expect(AssertionError.class);
CriteriaHelper.pollUiThreadNested(() -> false, 0, DEFAULT_POLLING_INTERVAL);
}
@Test @Test
@MediumTest @MediumTest
public void testThrow_Callable_InstrumentationThread() { public void testThrow_Callable_InstrumentationThread() {
...@@ -134,6 +159,16 @@ public class CriteriaHelperTest { ...@@ -134,6 +159,16 @@ public class CriteriaHelperTest {
}, 0, DEFAULT_POLLING_INTERVAL); }, 0, DEFAULT_POLLING_INTERVAL);
} }
@Test
@MediumTest
@UiThreadTest
public void testMessage_Runnable_UiThreadNested() {
thrown.expectMessage(ERROR_MESSAGE);
CriteriaHelper.pollUiThreadNested(() -> {
throw new CriteriaNotSatisfiedException(ERROR_MESSAGE);
}, 0, DEFAULT_POLLING_INTERVAL);
}
@Test @Test
@MediumTest @MediumTest
public void testMessage_Runnable_InstrumentationThread() { public void testMessage_Runnable_InstrumentationThread() {
...@@ -179,6 +214,22 @@ public class CriteriaHelperTest { ...@@ -179,6 +214,22 @@ public class CriteriaHelperTest {
Assert.fail(); Assert.fail();
} }
@Test
@MediumTest
@UiThreadTest
public void testStack_Runnable_UiThreadNested() {
try {
CriteriaHelper.pollUiThreadNested(() -> {
throw new CriteriaNotSatisfiedException("test");
}, 0, DEFAULT_POLLING_INTERVAL);
} catch (AssertionError e) {
assertThat(getStackTrace(e),
containsString("CriteriaHelperTest.testStack_Runnable_UiThreadNested("));
return;
}
Assert.fail();
}
@Test @Test
@MediumTest @MediumTest
public void testStack_Runnable_InstrumentationThread() { public void testStack_Runnable_InstrumentationThread() {
...@@ -207,6 +258,20 @@ public class CriteriaHelperTest { ...@@ -207,6 +258,20 @@ public class CriteriaHelperTest {
Assert.fail(); Assert.fail();
} }
@Test
@MediumTest
@UiThreadTest
public void testStack_Callable_UiThreadNested() {
try {
CriteriaHelper.pollUiThreadNested(() -> false, 0, DEFAULT_POLLING_INTERVAL);
} catch (AssertionError e) {
assertThat(getStackTrace(e),
containsString("CriteriaHelperTest.testStack_Callable_UiThreadNested("));
return;
}
Assert.fail();
}
@Test @Test
@MediumTest @MediumTest
public void testStack_Callable_InstrumentationThread() { public void testStack_Callable_InstrumentationThread() {
......
...@@ -4,12 +4,18 @@ ...@@ -4,12 +4,18 @@
package org.chromium.content_public.browser.test.util; package org.chromium.content_public.browser.test.util;
import android.os.Handler;
import android.os.Looper;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.chromium.base.ThreadUtils; import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.TimeoutTimer; import org.chromium.base.test.util.TimeoutTimer;
import org.chromium.content_public.browser.test.NestedSystemMessageHandler;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
/** /**
...@@ -73,6 +79,12 @@ public class CriteriaHelper { ...@@ -73,6 +79,12 @@ public class CriteriaHelper {
*/ */
public static void pollInstrumentationThread( public static void pollInstrumentationThread(
Runnable criteria, long maxTimeoutMs, long checkIntervalMs) { Runnable criteria, long maxTimeoutMs, long checkIntervalMs) {
assert !ThreadUtils.runningOnUiThread();
pollThreadInternal(criteria, maxTimeoutMs, checkIntervalMs, false);
}
private static void pollThreadInternal(
Runnable criteria, long maxTimeoutMs, long checkIntervalMs, boolean isUiThread) {
CriteriaNotSatisfiedException throwable; CriteriaNotSatisfiedException throwable;
try { try {
criteria.run(); criteria.run();
...@@ -82,11 +94,10 @@ public class CriteriaHelper { ...@@ -82,11 +94,10 @@ public class CriteriaHelper {
} }
TimeoutTimer timer = new TimeoutTimer(maxTimeoutMs); TimeoutTimer timer = new TimeoutTimer(maxTimeoutMs);
while (!timer.isTimedOut()) { while (!timer.isTimedOut()) {
try { if (isUiThread) {
Thread.sleep(checkIntervalMs); loopUiThread(checkIntervalMs);
} catch (InterruptedException e) { } else {
// Catch the InterruptedException. If the exception occurs before maxTimeoutMs sleepInstrumentationThread(checkIntervalMs);
// and the criteria is not satisfied, the while loop will run again.
} }
try { try {
criteria.run(); criteria.run();
...@@ -98,6 +109,34 @@ public class CriteriaHelper { ...@@ -98,6 +109,34 @@ public class CriteriaHelper {
throw new AssertionError(throwable); throw new AssertionError(throwable);
} }
private static void sleepInstrumentationThread(long checkIntervalMs) {
assert !ThreadUtils.runningOnUiThread();
try {
Thread.sleep(checkIntervalMs);
} catch (InterruptedException e) {
// Catch the InterruptedException. If the exception occurs before maxTimeoutMs
// and the criteria is not satisfied, the while loop will run again.
}
}
private static void loopUiThread(long checkIntervalMs) {
assert ThreadUtils.runningOnUiThread();
AtomicBoolean called = new AtomicBoolean(false);
// Ensure we pump the message handler in case no new tasks arrive.
new Handler(Looper.myLooper()).postDelayed(() -> { called.set(true); }, checkIntervalMs);
TimeoutTimer timer = new TimeoutTimer(checkIntervalMs);
while (!timer.isTimedOut() && !called.get()) {
try {
NestedSystemMessageHandler.runSingleNestedLooperTask(Looper.myQueue());
} catch (IllegalArgumentException | IllegalAccessException | SecurityException
| InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
/** /**
* Checks whether the given Runnable completes without exception at the default interval. * Checks whether the given Runnable completes without exception at the default interval.
* *
...@@ -193,6 +232,7 @@ public class CriteriaHelper { ...@@ -193,6 +232,7 @@ public class CriteriaHelper {
*/ */
public static void pollUiThread( public static void pollUiThread(
final Runnable criteria, long maxTimeoutMs, long checkIntervalMs) { final Runnable criteria, long maxTimeoutMs, long checkIntervalMs) {
assert !ThreadUtils.runningOnUiThread();
pollInstrumentationThread(() -> { pollInstrumentationThread(() -> {
AtomicReference<Throwable> throwableRef = new AtomicReference<>(); AtomicReference<Throwable> throwableRef = new AtomicReference<>();
ThreadUtils.runOnUiThreadBlocking(() -> { ThreadUtils.runOnUiThreadBlocking(() -> {
...@@ -283,6 +323,63 @@ public class CriteriaHelper { ...@@ -283,6 +323,63 @@ public class CriteriaHelper {
pollUiThread(criteria, null); pollUiThread(criteria, null);
} }
/**
* Checks whether the given Runnable completes without exception at a given interval on the UI
* thread, until either the Runnable successfully completes, or the maxTimeoutMs number of ms
* has elapsed.
* This call will nest the Looper in order to wait for the Runnable to complete.
*
* @param criteria The Runnable that will be attempted.
* @param maxTimeoutMs The maximum number of ms that this check will be performed for
* before timeout.
* @param checkIntervalMs The number of ms between checks.
*
* @see #pollInstrumentationThread(Runnable)
*/
public static void pollUiThreadNested(
Runnable criteria, long maxTimeoutMs, long checkIntervalMs) {
pollThreadInternal(criteria, maxTimeoutMs, checkIntervalMs, true);
}
/**
* Checks whether the given Runnable is satisfied polling at a given interval on the UI
* thread, until either the criteria is satisfied, or the maxTimeoutMs number of ms has elapsed.
* This call will nest the Looper in order to wait for the Criteria to be satisfied.
*
* @param criteria The Callable<Boolean> that will be checked.
* @param maxTimeoutMs The maximum number of ms that this check will be performed for
* before timeout.
* @param checkIntervalMs The number of ms between checks.
*
* @see #pollInstrumentationThread(Criteria)
*/
public static void pollUiThreadNested(
final Callable<Boolean> criteria, long maxTimeoutMs, long checkIntervalMs) {
pollUiThreadNested(toNotSatisfiedRunnable(criteria, null), maxTimeoutMs, checkIntervalMs);
}
/**
* Checks whether the given Runnable completes without exception at the default interval on
* the UI thread. This call will nest the Looper in order to wait for the Runnable to complete.
* @param criteria The Runnable that will be attempted.
*
* @see #pollInstrumentationThread(Runnable)
*/
public static void pollUiThreadNested(final Runnable criteria) {
pollUiThreadNested(criteria, DEFAULT_MAX_TIME_TO_POLL, DEFAULT_POLLING_INTERVAL);
}
/**
* Checks whether the given Callable<Boolean> is satisfied polling at a default interval on the
* UI thread. This call will nest the Looper in order to wait for the Criteria to be satisfied.
* @param criteria The Callable<Boolean> that will be checked.
*
* @see #pollInstrumentationThread(Criteria)
*/
public static void pollUiThreadNested(final Callable<Boolean> criteria) {
pollUiThreadNested(toNotSatisfiedRunnable(criteria, null));
}
private static Runnable toNotSatisfiedRunnable( private static Runnable toNotSatisfiedRunnable(
Callable<Boolean> criteria, String failureReason) { Callable<Boolean> criteria, String failureReason) {
return () -> { return () -> {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment