Commit b7d218f0 authored by Mugdha Lakhani's avatar Mugdha Lakhani Committed by Commit Bot

[Background Sync] Instrumentation tests.

Add some instrumentation tests for Background Sync.
We currently don't have any that run on Android, and this CL
bridges that gap.

A previous attempt was made at this here:
https://chromium-review.googlesource.com/c/chromium/src/+/1534160

but it worked by fluke, since our test devices happened to have network
connectivity. That can't be guaranteed so this CL mocks out network
connection type.

It also mocks out BackgroundTaskScheduler because the last attempt
was failing to get the BackgroundTaskScheduler delegate for Android L
and below. This was because Google Play services isn't up to date on our
test devices.

Bug: 996664
Change-Id: I904664a87e31439a65c0f9a04a1c9c4b21d03bdc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1768321
Commit-Queue: Mugdha Lakhani <nator@chromium.org>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Reviewed-by: default avatarPeter Conn <peconn@chromium.org>
Reviewed-by: default avatarRayan Kanso <rayankans@chromium.org>
Cr-Commit-Position: refs/heads/master@{#694759}
parent c2ba6bd7
......@@ -912,6 +912,7 @@ android_library("chrome_test_java") {
data = [
"//chrome/test/data/android/",
"//chrome/test/data/autofill/",
"//chrome/test/data/background_sync/",
"//chrome/test/data/banners/",
"//chrome/test/data/browsing_data/",
"//chrome/test/data/encoding_tests/auto_detect/Big5_with_no_encoding_specified.html",
......
......@@ -56,6 +56,7 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/autofill/AutofillTest.java",
"javatests/src/org/chromium/chrome/browser/autofill/AutofillTestHelper.java",
"javatests/src/org/chromium/chrome/browser/autofill/PersonalDataManagerTest.java",
"javatests/src/org/chromium/chrome/browser/background_sync/BackgroundSyncTest.java",
"javatests/src/org/chromium/chrome/browser/banners/AppBannerManagerTest.java",
"javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkBridgeTest.java",
"javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkModelTest.java",
......
......@@ -11,6 +11,7 @@ import android.text.format.DateUtils;
import org.chromium.base.ContextUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.chrome.browser.background_task_scheduler.NativeBackgroundTask;
import org.chromium.components.background_task_scheduler.BackgroundTaskSchedulerFactory;
import org.chromium.components.background_task_scheduler.TaskIds;
......@@ -47,14 +48,17 @@ public class BackgroundSyncBackgroundTaskScheduler {
// this task.
public static final String SOONEST_EXPECTED_WAKETIME = "SoonestWakeupTime";
private static class LazyHolder {
static final BackgroundSyncBackgroundTaskScheduler INSTANCE =
new BackgroundSyncBackgroundTaskScheduler();
}
private static BackgroundSyncBackgroundTaskScheduler sInstance;
@CalledByNative
public static BackgroundSyncBackgroundTaskScheduler getInstance() {
return LazyHolder.INSTANCE;
if (sInstance == null) sInstance = new BackgroundSyncBackgroundTaskScheduler();
return sInstance;
}
@VisibleForTesting
static boolean hasInstance() {
return sInstance != null;
}
/**
......@@ -165,4 +169,16 @@ public class BackgroundSyncBackgroundTaskScheduler {
public void reschedule(@BackgroundSyncTask int taskType) {
scheduleOneOffTask(MIN_SYNC_RECOVERY_TIME, taskType);
}
@NativeMethods
interface Natives {
/**
* Chrome currently disables BackgroundSyncManager if Google Play Services aren't up-to-date
* at startup. Disable this check for tests, since we mock out interaction with GCM.
* This method can be removed once our test devices start updating Google Play Services
* before tests are run. https://crbug.com/514449
* @param disabled disable or enable the version check for Google Play Services.
*/
void setPlayServicesVersionCheckDisabledForTests(boolean disabled);
}
}
// Copyright 2019 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.background_sync;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.chromium.base.test.util.ScalableTimeout.scaleTimeout;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.chromium.base.ContextUtils;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.background_task_scheduler.ChromeBackgroundTaskFactory;
import org.chromium.chrome.browser.externalauth.ExternalAuthUtils;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.util.browser.TabTitleObserver;
import org.chromium.components.background_task_scheduler.BackgroundTaskScheduler;
import org.chromium.components.background_task_scheduler.BackgroundTaskSchedulerFactory;
import org.chromium.components.background_task_scheduler.TaskInfo;
import org.chromium.content_public.browser.test.NativeLibraryTestRule;
import org.chromium.content_public.browser.test.util.BackgroundSyncNetworkUtils;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.net.ConnectionType;
import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.net.test.ServerCertificate;
import java.util.concurrent.TimeoutException;
/**
* Instrumentation test for Background Sync.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public final class BackgroundSyncTest {
@Mock
private BackgroundTaskScheduler mTaskScheduler;
@Rule
public ChromeActivityTestRule<ChromeActivity> mActivityTestRule =
new ChromeActivityTestRule<>(ChromeActivity.class);
@Rule
public NativeLibraryTestRule mNativeLibraryTestRule = new NativeLibraryTestRule();
private EmbeddedTestServer mTestServer;
private String mTestPage;
private static final String BACKGROUND_SYNC_TEST_PAGE =
"/chrome/test/data/background_sync/background_sync_test.html";
private static final int TITLE_UPDATE_TIMEOUT_SECONDS = (int) scaleTimeout(10);
private static final long WAIT_TIME_MS = scaleTimeout(5000);
@Before
public void setUp() throws InterruptedException {
MockitoAnnotations.initMocks(this);
BackgroundTaskSchedulerFactory.setSchedulerForTesting(mTaskScheduler);
ChromeBackgroundTaskFactory.setAsDefault();
doReturn(true)
.when(mTaskScheduler)
.schedule(eq(ContextUtils.getApplicationContext()), any(TaskInfo.class));
// This is necessary because our test devices don't have Google Play Services up to date,
// and BackgroundSync requires that. Remove this once https://crbug.com/514449 has been
// fixed.
// Note that this should be done before the startMainActivityOnBlankPage(), because Chrome
// will otherwise run this check on startup and disable BackgroundSync code.
if (!ExternalAuthUtils.canUseGooglePlayServices()) {
mNativeLibraryTestRule.loadNativeLibraryAndInitBrowserProcess();
disableGooglePlayServicesVersionCheck();
}
mActivityTestRule.startMainActivityOnBlankPage();
// BackgroundSync only works with HTTPS.
mTestServer = EmbeddedTestServer.createAndStartHTTPSServer(
InstrumentationRegistry.getInstrumentation().getContext(),
ServerCertificate.CERT_OK);
}
@After
public void tearDown() throws TimeoutException {
if (mTestServer != null) mTestServer.stopAndDestroyServer();
}
@Test
@MediumTest
@Feature({"BackgroundSync"})
public void onSyncCalledWithNetworkConnectivity() throws Exception {
forceConnectionType(ConnectionType.CONNECTION_NONE);
mActivityTestRule.loadUrl(mTestServer.getURL(BACKGROUND_SYNC_TEST_PAGE));
runJavaScript("SetupReplyForwardingForTests();");
// Register Sync.
runJavaScript("RegisterSyncForTag('tagSucceedsSync');");
assertTitleBecomes("registered sync");
verify(mTaskScheduler, timeout(WAIT_TIME_MS))
.schedule(eq(ContextUtils.getApplicationContext()),
argThat(taskInfo
-> taskInfo.getBackgroundTaskClass()
== BackgroundSyncBackgroundTask.class));
forceConnectionType(ConnectionType.CONNECTION_WIFI);
assertTitleBecomes("onsync: tagSucceedsSync");
// Another invocation when all events are firing but haven't completed, to cover the case
// when the browser is closed mid-event. This one races with the completion of the sync
// event, and might not happen.
// The wait is to ensure we wait for this invocation to happen, since it happens in
// parallel with dispatching the sync event.
verify(mTaskScheduler, after(WAIT_TIME_MS).atMost(2))
.schedule(eq(ContextUtils.getApplicationContext()),
argThat(taskInfo
-> taskInfo.getBackgroundTaskClass()
== BackgroundSyncBackgroundTask.class));
}
@Test
@MediumTest
@Feature({"BackgroundSync"})
public void browserWakeUpScheduledWhenSyncEventFails() throws Exception {
forceConnectionType(ConnectionType.CONNECTION_NONE);
mActivityTestRule.loadUrl(mTestServer.getURL(BACKGROUND_SYNC_TEST_PAGE));
runJavaScript("SetupReplyForwardingForTests();");
// Register Sync.
runJavaScript("RegisterSyncForTag('tagFailsSync');");
assertTitleBecomes("registered sync");
verify(mTaskScheduler, timeout(WAIT_TIME_MS))
.schedule(eq(ContextUtils.getApplicationContext()),
argThat(taskInfo
-> taskInfo.getBackgroundTaskClass()
== BackgroundSyncBackgroundTask.class));
forceConnectionType(ConnectionType.CONNECTION_WIFI);
// Wait for some time that is less than the retry time period (default 5 minutes).
// One call to schedule wake-up a few minutes after firing the first sync event, to cover
// our bases if the browser is closed mid-sync. This one races with the completion of the
// sync event, and might not happen.
// Another call to waking up the browser for attempt two after completion of the first
// event. This will always happen.
verify(mTaskScheduler, after(WAIT_TIME_MS).atMost(3))
.schedule(eq(ContextUtils.getApplicationContext()),
argThat(taskInfo
-> taskInfo.getBackgroundTaskClass()
== BackgroundSyncBackgroundTask.class));
}
/**
* Helper methods.
*/
private String runJavaScript(String code) throws TimeoutException, InterruptedException {
return mActivityTestRule.runJavaScriptCodeInCurrentTab(code);
}
@SuppressWarnings("MissingFail")
private void assertTitleBecomes(String expectedTitle) throws InterruptedException {
Tab tab = mActivityTestRule.getActivity().getActivityTab();
TabTitleObserver titleObserver = new TabTitleObserver(tab, expectedTitle);
try {
titleObserver.waitForTitleUpdate(TITLE_UPDATE_TIMEOUT_SECONDS);
} catch (TimeoutException e) {
// The title is not as expected, this assertion neatly logs what the difference is.
Assert.assertEquals(expectedTitle, tab.getTitle());
}
}
private void forceConnectionType(int connectionType) {
TestThreadUtils.runOnUiThreadBlocking(
() -> { BackgroundSyncNetworkUtils.setConnectionTypeForTesting(connectionType); });
}
private void disableGooglePlayServicesVersionCheck() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
BackgroundSyncBackgroundTaskSchedulerJni.get()
.setPlayServicesVersionCheckDisabledForTests(
/* disabled= */ true);
});
}
}
......@@ -61,6 +61,13 @@ void JNI_PeriodicBackgroundSyncChromeWakeUpTask_FirePeriodicBackgroundSyncEvents
blink::mojom::BackgroundSyncType::PERIODIC, j_runnable);
}
void JNI_BackgroundSyncBackgroundTaskScheduler_SetPlayServicesVersionCheckDisabledForTests(
JNIEnv* env,
jboolean disabled) {
BackgroundSyncLauncherAndroid::SetPlayServicesVersionCheckDisabledForTests(
disabled);
}
// static
BackgroundSyncLauncherAndroid* BackgroundSyncLauncherAndroid::Get() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
......
<!DOCTYPE html>
<html>
<head>
<title>Background Sync API Instrumentation Test</title>
<script src="../notifications/notification_test_utils.js"></script>
</head>
<body>
<!-- This method is used by the BackgroundSyncTest instrumentation test. -->
<h1>Background_Sync_API_Instrumentation_Test</h1>
<script>
function GetActivatedServiceWorkerForTest() {
return GetActivatedServiceWorker('service_worker.js', location.pathname);
}
function SetupReplyForwardingForTests() {
GetActivatedServiceWorkerForTest()
.then(registration => {
const replyListener = event => {
if (event.data.startsWith('onsync: ')) {
messagePort.removeEventListener('message', replyListener);
sendToTest(event.data);
}
};
messagePort.addEventListener('message', replyListener);
})
.catch(sendToTest);
}
function RegisterSyncForTag(tag) {
navigator.permissions.query({ name: 'background-sync' })
.then(result => {
if (result.state !== 'granted') {
// 'background-sync' permission should be granted by default.
sendToTest('permission denied');
return;
}
GetActivatedServiceWorkerForTest()
.then(registration => {
registration.sync.register(tag)
.then(() => sendToTest('registered sync'));
});
})
.catch(sendToTest);
}
// BackgroundSyncTest observes changes to the tab title as an
// asynchronous response mechanism from JavaScript to Java.
function sendToTest(message) {
document.title = message;
}
</script>
</body>
</html>
\ No newline at end of file
// Copyright 2019 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.
let messagePort = null;
self.addEventListener('message', event => {
messagePort = event.data;
messagePort.postMessage('ready');
});
self.addEventListener('sync', async event => {
const onSync = async () => {
switch (event.tag) {
case 'tagSucceedsSync':
messagePort.postMessage('onsync: ' + event.tag);
return;
case 'tagFailsSync':
throw 'Try again';
}
};
event.waitUntil(onSync());
});
......@@ -12,11 +12,13 @@ import android.os.Process;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.ContextUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeClassQualifiedName;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.net.ConnectionType;
import org.chromium.net.NetworkChangeNotifierAutoDetect;
import org.chromium.net.RegistrationPolicyAlwaysRegister;
......@@ -36,9 +38,10 @@ import java.util.List;
* This class lives on the main thread.
*/
@JNINamespace("content")
class BackgroundSyncNetworkObserver implements NetworkChangeNotifierAutoDetect.Observer {
@VisibleForTesting
public class BackgroundSyncNetworkObserver implements NetworkChangeNotifierAutoDetect.Observer {
private static final String TAG = "cr_BgSyncNetObserver";
private static boolean sSetConnectionTypeForTesting;
private NetworkChangeNotifierAutoDetect mNotifier;
// The singleton instance.
......@@ -48,12 +51,19 @@ class BackgroundSyncNetworkObserver implements NetworkChangeNotifierAutoDetect.O
// List of native observers. These are each called when the network state changes.
private List<Long> mNativePtrs;
private int mLastBroadcastConnectionType;
private @ConnectionType int mLastBroadcastConnectionType;
private boolean mHasBroadcastConnectionType;
@VisibleForTesting
public static void setConnectionTypeForTesting(@ConnectionType int connectionType) {
sSetConnectionTypeForTesting = true;
getBackgroundSyncNetworkObserver().broadcastNetworkChangeIfNecessary(connectionType);
}
private BackgroundSyncNetworkObserver() {
ThreadUtils.assertOnUiThread();
mNativePtrs = new ArrayList<Long>();
sSetConnectionTypeForTesting = false;
}
private static boolean canCreateObserver() {
......@@ -62,13 +72,18 @@ class BackgroundSyncNetworkObserver implements NetworkChangeNotifierAutoDetect.O
== PackageManager.PERMISSION_GRANTED;
}
@CalledByNative
private static BackgroundSyncNetworkObserver createObserver(long nativePtr) {
ThreadUtils.assertOnUiThread();
private static BackgroundSyncNetworkObserver getBackgroundSyncNetworkObserver() {
if (sInstance == null) {
sInstance = new BackgroundSyncNetworkObserver();
}
sInstance.registerObserver(nativePtr);
return sInstance;
}
@CalledByNative
private static BackgroundSyncNetworkObserver createObserver(long nativePtr) {
ThreadUtils.assertOnUiThread();
getBackgroundSyncNetworkObserver().registerObserver(nativePtr);
return sInstance;
}
......@@ -105,9 +120,10 @@ class BackgroundSyncNetworkObserver implements NetworkChangeNotifierAutoDetect.O
}
}
private void broadcastNetworkChangeIfNecessary(int newConnectionType) {
if (mHasBroadcastConnectionType && newConnectionType == mLastBroadcastConnectionType)
private void broadcastNetworkChangeIfNecessary(@ConnectionType int newConnectionType) {
if (mHasBroadcastConnectionType && newConnectionType == mLastBroadcastConnectionType) {
return;
}
mHasBroadcastConnectionType = true;
mLastBroadcastConnectionType = newConnectionType;
......@@ -118,8 +134,10 @@ class BackgroundSyncNetworkObserver implements NetworkChangeNotifierAutoDetect.O
}
@Override
public void onConnectionTypeChanged(int newConnectionType) {
public void onConnectionTypeChanged(@ConnectionType int newConnectionType) {
ThreadUtils.assertOnUiThread();
if (sSetConnectionTypeForTesting) return;
broadcastNetworkChangeIfNecessary(newConnectionType);
}
......@@ -127,8 +145,10 @@ class BackgroundSyncNetworkObserver implements NetworkChangeNotifierAutoDetect.O
public void onConnectionSubtypeChanged(int newConnectionSubtype) {}
@Override
public void onNetworkConnect(long netId, int connectionType) {
public void onNetworkConnect(long netId, @ConnectionType int connectionType) {
ThreadUtils.assertOnUiThread();
if (sSetConnectionTypeForTesting) return;
// If we're in doze mode (N+ devices), onConnectionTypeChanged may not
// be called, but this function should. So update the connection type
// if necessary.
......@@ -141,6 +161,8 @@ class BackgroundSyncNetworkObserver implements NetworkChangeNotifierAutoDetect.O
@Override
public void onNetworkDisconnect(long netId) {
ThreadUtils.assertOnUiThread();
if (sSetConnectionTypeForTesting) return;
// If we're in doze mode (N+ devices), onConnectionTypeChanged may not
// be called, but this function should. So update the connection type
// if necessary.
......
......@@ -36,6 +36,7 @@ android_library("content_java_test_support") {
"javatests/src/org/chromium/content_public/browser/test/RenderFrameHostTestExt.java",
"javatests/src/org/chromium/content_public/browser/test/mock/MockRenderFrameHost.java",
"javatests/src/org/chromium/content_public/browser/test/mock/MockWebContents.java",
"javatests/src/org/chromium/content_public/browser/test/util/BackgroundSyncNetworkUtils.java",
"javatests/src/org/chromium/content_public/browser/test/util/ClickUtils.java",
"javatests/src/org/chromium/content_public/browser/test/util/Coordinates.java",
"javatests/src/org/chromium/content_public/browser/test/util/Criteria.java",
......
// Copyright 2019 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.content_public.browser.test.util;
import org.chromium.base.VisibleForTesting;
import org.chromium.content.browser.BackgroundSyncNetworkObserver;
import org.chromium.net.ConnectionType;
/**
* Used to mock network conditions for Background Sync.
*/
public class BackgroundSyncNetworkUtils {
/**
* Overrides connection type for testing.
* @param connectionType The connectionType to override to. BackgroundSync code will be notified
* of this connection type.
*/
@VisibleForTesting
public static void setConnectionTypeForTesting(@ConnectionType int connectionType) {
BackgroundSyncNetworkObserver.setConnectionTypeForTesting(connectionType);
}
}
\ No newline at end of file
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