Commit e61a2847 authored by Victor Hugo Vianna Silva's avatar Victor Hugo Vianna Silva Committed by Commit Bot

Adapt SyncTestRule to allow AndroidSyncSettings to depend on PSS

Problem:
AndroidSyncSettings (ASS) will soon depend on ProfileSyncService (PSS),
particularly in its constructor. Although this is is fine in production
code, SyncTestRule poses two issues for this setup on test code.
- ASS is created before a fake PSS provided by tests can be injected.
This is a problem because the initial state of PSS will be relevant to
the constructor logic of ASS, so we need to be able to fake this initial
state to write good tests.
- ASS is created before the JNI loads in startMainActivityForSyncTest().
For tests that don't provide a fake PSS, ASS will then attempt to talk
to the real PSS before JNI is loaded, causing an error.

Solution:
When SyncTestRule creates an ASS, the goal is actually just to fake one
of its internal dependencies, SyncContentResolverDelegate. In this
CL, we convert SyncContentResolverDelegate into a singleton. In this way,
a fake for this object can be injected without needing to early construct
ASS*. Furthermore, JNI is loaded earlier so that the rule can safely use
tools like FakeServer and FakeProfileSyncService.

* Note that constructing after startMainActivityForSyncTest() is not an
option because there's already code talking to ASS when this function
is running.

Bug: 1125622
Change-Id: I9dd938fd5d8211d58a97adb6d484c1a21a3b67db
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2424150
Commit-Queue: Victor Vianna <victorvianna@google.com>
Reviewed-by: default avatarMarc Treib <treib@chromium.org>
Cr-Commit-Position: refs/heads/master@{#810116}
parent cfc09c41
...@@ -1459,7 +1459,6 @@ chrome_java_sources = [ ...@@ -1459,7 +1459,6 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/sync/SyncController.java", "java/src/org/chromium/chrome/browser/sync/SyncController.java",
"java/src/org/chromium/chrome/browser/sync/SyncNotificationController.java", "java/src/org/chromium/chrome/browser/sync/SyncNotificationController.java",
"java/src/org/chromium/chrome/browser/sync/SyncUserDataWiper.java", "java/src/org/chromium/chrome/browser/sync/SyncUserDataWiper.java",
"java/src/org/chromium/chrome/browser/sync/SystemSyncContentResolverDelegate.java",
"java/src/org/chromium/chrome/browser/sync/TrustedVaultClient.java", "java/src/org/chromium/chrome/browser/sync/TrustedVaultClient.java",
"java/src/org/chromium/chrome/browser/sync/settings/AccountManagementFragment.java", "java/src/org/chromium/chrome/browser/sync/settings/AccountManagementFragment.java",
"java/src/org/chromium/chrome/browser/sync/settings/ClearDataProgressDialog.java", "java/src/org/chromium/chrome/browser/sync/settings/ClearDataProgressDialog.java",
......
...@@ -72,8 +72,7 @@ public class AndroidSyncSettings { ...@@ -72,8 +72,7 @@ public class AndroidSyncSettings {
public static AndroidSyncSettings get() { public static AndroidSyncSettings get() {
ThreadUtils.assertOnUiThread(); ThreadUtils.assertOnUiThread();
if (sInstance == null) { if (sInstance == null) {
SyncContentResolverDelegate contentResolver = new SystemSyncContentResolverDelegate(); sInstance = new AndroidSyncSettings(getSyncAccount());
sInstance = new AndroidSyncSettings(contentResolver);
} }
return sInstance; return sInstance;
} }
...@@ -89,29 +88,17 @@ public class AndroidSyncSettings { ...@@ -89,29 +88,17 @@ public class AndroidSyncSettings {
} }
/** /**
* @param syncContentResolverDelegate an implementation of {@link SyncContentResolverDelegate}.
*/
@VisibleForTesting
AndroidSyncSettings(SyncContentResolverDelegate syncContentResolverDelegate) {
this(syncContentResolverDelegate, getSyncAccount());
}
/**
* @param syncContentResolverDelegate an implementation of {@link SyncContentResolverDelegate}.
* @param account The sync account if sync is enabled, null otherwise. * @param account The sync account if sync is enabled, null otherwise.
*/ */
// TODO(crbug.com/1125622): Once this class is used only on the UI thread, |callback|
// can be removed (syncability update will be synchronous). Same goes for the corresponding
// parameter in |updateAccount()|.
// TODO(crbug.com/1125622): Exposing these testing constructors that don't register the // TODO(crbug.com/1125622): Exposing these testing constructors that don't register the
// singleton instance can be dangerous when there's code that explicitly calls |get()| // singleton instance can be dangerous when there's code that explicitly calls |get()|
// (in that case, a new object would be returned, not the one constructed by the test). // (in that case, a new object would be returned, not the one constructed by the test).
// Consider exposing them as static methods that also register a singleton instance. // Consider exposing them as static methods that also register a singleton instance.
@VisibleForTesting @VisibleForTesting
AndroidSyncSettings( public AndroidSyncSettings(@Nullable Account account) {
SyncContentResolverDelegate syncContentResolverDelegate, @Nullable Account account) { ThreadUtils.assertOnUiThread();
mContractAuthority = getContractAuthority(); mContractAuthority = getContractAuthority();
mSyncContentResolverDelegate = syncContentResolverDelegate; mSyncContentResolverDelegate = SyncContentResolverDelegate.get();
mAccount = account; mAccount = account;
updateCachedSettings(); updateCachedSettings();
......
...@@ -5,30 +5,74 @@ ...@@ -5,30 +5,74 @@
package org.chromium.chrome.browser.sync; package org.chromium.chrome.browser.sync;
import android.accounts.Account; import android.accounts.Account;
import android.content.ContentResolver;
import android.content.SyncStatusObserver; import android.content.SyncStatusObserver;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.ThreadUtils;
/** /**
* Since the ContentResolver in Android has a lot of static methods, it is hard to * Since the ContentResolver in Android has a lot of static methods, it is hard to
* mock out for tests. This interface wraps all the sync-related methods we use from * mock out for tests. This class wraps all the sync-related methods we use from
* the Android ContentResolver. * the Android ContentResolver.
* Note that SyncContentResolverDelegate is not an Android concept. In
* particular, it's not this class that will notify observers, Android will
* directly do that.
*/ */
interface SyncContentResolverDelegate { class SyncContentResolverDelegate {
Object addStatusChangeListener(int mask, SyncStatusObserver callback); private static SyncContentResolverDelegate sInstance;
public static SyncContentResolverDelegate get() {
ThreadUtils.assertOnUiThread();
if (sInstance == null) {
sInstance = new SyncContentResolverDelegate();
}
return sInstance;
}
public Object addStatusChangeListener(int mask, SyncStatusObserver callback) {
return ContentResolver.addStatusChangeListener(mask, callback);
}
public void removeStatusChangeListener(Object handle) {
ContentResolver.removeStatusChangeListener(handle);
}
public void setMasterSyncAutomatically(boolean sync) {
ContentResolver.setMasterSyncAutomatically(sync);
}
void removeStatusChangeListener(Object handle); public boolean getMasterSyncAutomatically() {
return ContentResolver.getMasterSyncAutomatically();
}
void setMasterSyncAutomatically(boolean sync); public boolean getSyncAutomatically(Account account, String authority) {
return ContentResolver.getSyncAutomatically(account, authority);
}
boolean getMasterSyncAutomatically(); public void setSyncAutomatically(Account account, String authority, boolean sync) {
ContentResolver.setSyncAutomatically(account, authority, sync);
}
void setSyncAutomatically(Account account, String authority, boolean sync); public void setIsSyncable(Account account, String authority, int syncable) {
ContentResolver.setIsSyncable(account, authority, syncable);
}
boolean getSyncAutomatically(Account account, String authority); public int getIsSyncable(Account account, String authority) {
return ContentResolver.getIsSyncable(account, authority);
}
void setIsSyncable(Account account, String authority, int syncable); public void removePeriodicSync(Account account, String authority, Bundle extras) {
ContentResolver.removePeriodicSync(account, authority, extras);
}
int getIsSyncable(Account account, String authority); @VisibleForTesting
public static void overrideForTests(SyncContentResolverDelegate delegate) {
ThreadUtils.assertOnUiThread();
sInstance = delegate;
}
void removePeriodicSync(Account account, String authority, Bundle extras); protected SyncContentResolverDelegate() {}
} }
// Copyright 2010 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.sync;
import android.accounts.Account;
import android.content.ContentResolver;
import android.content.SyncStatusObserver;
import android.os.Bundle;
/**
* A SyncContentResolverDelegate that simply forwards calls to ContentResolver.
* Note that SyncContentResolverDelegate is not an Android concept. In particular,
* it's not this class that will notify observers, Android will directly do that.
*/
class SystemSyncContentResolverDelegate implements SyncContentResolverDelegate {
@Override
public Object addStatusChangeListener(int mask, SyncStatusObserver callback) {
return ContentResolver.addStatusChangeListener(mask, callback);
}
@Override
public void removeStatusChangeListener(Object handle) {
ContentResolver.removeStatusChangeListener(handle);
}
@Override
public void setMasterSyncAutomatically(boolean sync) {
ContentResolver.setMasterSyncAutomatically(sync);
}
@Override
public boolean getMasterSyncAutomatically() {
return ContentResolver.getMasterSyncAutomatically();
}
@Override
public boolean getSyncAutomatically(Account account, String authority) {
return ContentResolver.getSyncAutomatically(account, authority);
}
@Override
public void setSyncAutomatically(Account account, String authority, boolean sync) {
ContentResolver.setSyncAutomatically(account, authority, sync);
}
@Override
public void setIsSyncable(Account account, String authority, int syncable) {
ContentResolver.setIsSyncable(account, authority, syncable);
}
@Override
public int getIsSyncable(Account account, String authority) {
return ContentResolver.getIsSyncable(account, authority);
}
@Override
public void removePeriodicSync(Account account, String authority, Bundle extras) {
ContentResolver.removePeriodicSync(account, authority, extras);
}
}
...@@ -129,6 +129,8 @@ public class AndroidSyncSettingsTest { ...@@ -129,6 +129,8 @@ public class AndroidSyncSettingsTest {
FeatureList.setTestCanUseDefaultsForTesting(); FeatureList.setTestCanUseDefaultsForTesting();
mSyncContentResolverDelegate = new CountingMockSyncContentResolverDelegate(); mSyncContentResolverDelegate = new CountingMockSyncContentResolverDelegate();
TestThreadUtils.runOnUiThreadBlocking(
() -> SyncContentResolverDelegate.overrideForTests(mSyncContentResolverDelegate));
FakeAccountManagerFacade fakeAccountManagerFacade = new FakeAccountManagerFacade(null); FakeAccountManagerFacade fakeAccountManagerFacade = new FakeAccountManagerFacade(null);
AccountManagerFacadeProvider.setInstanceForTests(fakeAccountManagerFacade); AccountManagerFacadeProvider.setInstanceForTests(fakeAccountManagerFacade);
...@@ -140,7 +142,9 @@ public class AndroidSyncSettingsTest { ...@@ -140,7 +142,9 @@ public class AndroidSyncSettingsTest {
private void createAndroidSyncSettings() { private void createAndroidSyncSettings() {
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
mAndroidSyncSettings = new AndroidSyncSettings(mSyncContentResolverDelegate, mAccount); // TODO(crbug.com/1105795): Consider setting the fake account in the identity manager
// so there's no need to inject it here.
mAndroidSyncSettings = new AndroidSyncSettings(mAccount);
AndroidSyncSettings.overrideForTests(mAndroidSyncSettings); AndroidSyncSettings.overrideForTests(mAndroidSyncSettings);
}); });
......
...@@ -23,18 +23,14 @@ public class AndroidSyncSettingsTestUtils { ...@@ -23,18 +23,14 @@ public class AndroidSyncSettingsTestUtils {
@CalledByNative @CalledByNative
@VisibleForTesting @VisibleForTesting
public static void setUpAndroidSyncSettingsForTesting() { public static void setUpAndroidSyncSettingsForTesting() {
setUpAndroidSyncSettingsForTesting(new MockSyncContentResolverDelegate()); MockSyncContentResolverDelegate delegate = new MockSyncContentResolverDelegate();
}
static AndroidSyncSettings setUpAndroidSyncSettingsForTesting(
SyncContentResolverDelegate delegate) {
delegate.setMasterSyncAutomatically(true); delegate.setMasterSyncAutomatically(true);
TestThreadUtils.runOnUiThreadBlocking(
() -> SyncContentResolverDelegate.overrideForTests(delegate));
// Explicitly pass null account to AndroidSyncSettings ctor. Normally, AndroidSyncSettings // Explicitly pass null account to AndroidSyncSettings ctor. Normally, AndroidSyncSettings
// ctor uses IdentityManager to get the sync account, but some native tests call this method // ctor uses IdentityManager to get the sync account, but some native tests call this method
// before profiles are initialized (when IdentityManager doesn't exist yet). // before profiles are initialized (when IdentityManager doesn't exist yet).
AndroidSyncSettings settings = new AndroidSyncSettings(delegate, null); AndroidSyncSettings.overrideForTests(new AndroidSyncSettings(null));
AndroidSyncSettings.overrideForTests(settings);
return settings;
} }
public static boolean getIsSyncEnabledOnUiThread() { public static boolean getIsSyncEnabledOnUiThread() {
......
...@@ -24,20 +24,14 @@ import java.util.Set; ...@@ -24,20 +24,14 @@ import java.util.Set;
* observers for the SYNC_OBSERVER_TYPE_SETTINGS type and it doesn't allow querying * observers for the SYNC_OBSERVER_TYPE_SETTINGS type and it doesn't allow querying
* settings for a null account. * settings for a null account.
*/ */
class MockSyncContentResolverDelegate implements SyncContentResolverDelegate { class MockSyncContentResolverDelegate extends SyncContentResolverDelegate {
private final Set<String> mSyncAutomaticallySet; private final Set<String> mSyncAutomaticallySet = new HashSet<String>();
private final Map<String, Boolean> mIsSyncableMap; private final Map<String, Boolean> mIsSyncableMap = new HashMap<String, Boolean>();
private final Set<SyncStatusObserver> mObservers; private final Set<SyncStatusObserver> mObservers = new HashSet<SyncStatusObserver>();
private boolean mMasterSyncAutomatically; private boolean mMasterSyncAutomatically;
private Object mLock = new Object(); private Object mLock = new Object();
public MockSyncContentResolverDelegate() {
mSyncAutomaticallySet = new HashSet<String>();
mIsSyncableMap = new HashMap<String, Boolean>();
mObservers = new HashSet<SyncStatusObserver>();
}
@Override @Override
public Object addStatusChangeListener(int mask, SyncStatusObserver observer) { public Object addStatusChangeListener(int mask, SyncStatusObserver observer) {
if (mask != ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS) { if (mask != ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS) {
......
...@@ -38,6 +38,7 @@ import org.chromium.components.sync.protocol.AutofillWalletSpecifics; ...@@ -38,6 +38,7 @@ import org.chromium.components.sync.protocol.AutofillWalletSpecifics;
import org.chromium.components.sync.protocol.EntitySpecifics; import org.chromium.components.sync.protocol.EntitySpecifics;
import org.chromium.components.sync.protocol.SyncEntity; import org.chromium.components.sync.protocol.SyncEntity;
import org.chromium.components.sync.protocol.WalletMaskedCreditCard; import org.chromium.components.sync.protocol.WalletMaskedCreditCard;
import org.chromium.content_public.browser.test.NativeLibraryTestUtils;
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.TestThreadUtils; import org.chromium.content_public.browser.test.util.TestThreadUtils;
...@@ -305,30 +306,28 @@ public class SyncTestRule extends ChromeActivityTestRule<ChromeActivity> { ...@@ -305,30 +306,28 @@ public class SyncTestRule extends ChromeActivityTestRule<ChromeActivity> {
final Statement base = super.apply(new Statement() { final Statement base = super.apply(new Statement() {
@Override @Override
public void evaluate() throws Throwable { public void evaluate() throws Throwable {
TestThreadUtils.runOnUiThreadBlocking(() -> { mSyncContentResolver = new MockSyncContentResolverDelegate();
mSyncContentResolver = new MockSyncContentResolverDelegate(); mSyncContentResolver.setMasterSyncAutomatically(true);
AndroidSyncSettingsTestUtils.setUpAndroidSyncSettingsForTesting( TestThreadUtils.runOnUiThreadBlocking(
mSyncContentResolver); () -> SyncContentResolverDelegate.overrideForTests(mSyncContentResolver));
});
TrustedVaultClient.setInstanceForTesting( TrustedVaultClient.setInstanceForTesting(
new TrustedVaultClient(FakeTrustedVaultClientBackend.get())); new TrustedVaultClient(FakeTrustedVaultClientBackend.get()));
startMainActivityForSyncTest(); // Load native since the FakeServer needs it and possibly ProfileSyncService as well
mContext = InstrumentationRegistry.getTargetContext(); // (depends on what fake is provided by |createProfileSyncService()|).
NativeLibraryTestUtils.loadNativeLibraryAndInitBrowserProcess();
TestThreadUtils.runOnUiThreadBlocking(() -> {
// Ensure SyncController is registered with the new AndroidSyncSettings.
AndroidSyncSettings.get().registerObserver(SyncController.get());
mFakeServerHelper = FakeServerHelper.get();
});
FakeServerHelper.useFakeServer(mContext);
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
ProfileSyncService profileSyncService = createProfileSyncService(); ProfileSyncService profileSyncService = createProfileSyncService();
if (profileSyncService != null) { if (profileSyncService != null) {
ProfileSyncService.overrideForTests(profileSyncService); ProfileSyncService.overrideForTests(profileSyncService);
} }
mProfileSyncService = ProfileSyncService.get(); mProfileSyncService = ProfileSyncService.get();
mContext = InstrumentationRegistry.getTargetContext();
FakeServerHelper.useFakeServer(mContext);
mFakeServerHelper = FakeServerHelper.get();
}); });
UniqueIdentificationGeneratorFactory.registerGenerator( UniqueIdentificationGeneratorFactory.registerGenerator(
...@@ -340,6 +339,12 @@ public class SyncTestRule extends ChromeActivityTestRule<ChromeActivity> { ...@@ -340,6 +339,12 @@ public class SyncTestRule extends ChromeActivityTestRule<ChromeActivity> {
} }
}, },
true); true);
startMainActivityForSyncTest();
// Ensure SyncController is created.
TestThreadUtils.runOnUiThreadBlocking(() -> SyncController.get());
statement.evaluate(); statement.evaluate();
} }
}, desc); }, desc);
......
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