blob: 66a2f41d7bf9eb044e1c498f44380b0f3ca37e54 [file] [log] [blame]
// Copyright (c) 2012 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.
#include "chrome/browser/sync/test/integration/sync_test.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/path_service.h"
#include "base/rand_util.h"
#include "base/run_loop.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/gcm/gcm_profile_service_factory.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/invalidation/profile_invalidation_provider_factory.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/browser/signin/chrome_signin_client_factory.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/browser/sync/sync_invalidations_service_factory.h"
#include "chrome/browser/sync/test/integration/profile_sync_service_harness.h"
#include "chrome/browser/sync/test/integration/single_client_status_change_checker.h"
#include "chrome/browser/sync/test/integration/sync_datatype_helper.h"
#include "chrome/browser/sync/test/integration/sync_disabled_checker.h"
#include "chrome/browser/sync/test/integration/sync_integration_test_util.h"
#include "chrome/browser/sync/test/integration/updated_progress_marker_checker.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "components/bookmarks/test/bookmark_test_helpers.h"
#include "components/gcm_driver/gcm_profile_service.h"
#include "components/invalidation/impl/fake_invalidation_service.h"
#include "components/invalidation/impl/fcm_invalidation_service.h"
#include "components/invalidation/impl/fcm_network_handler.h"
#include "components/invalidation/impl/invalidation_prefs.h"
#include "components/invalidation/impl/invalidation_switches.h"
#include "components/invalidation/impl/per_user_topic_subscription_manager.h"
#include "components/invalidation/impl/profile_identity_provider.h"
#include "components/invalidation/impl/profile_invalidation_provider.h"
#include "components/invalidation/public/invalidation_service.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/os_crypt/os_crypt_mocker.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/signin/public/identity_manager/consent_level.h"
#include "components/sync/base/invalidation_helper.h"
#include "components/sync/base/sync_base_switches.h"
#include "components/sync/driver/profile_sync_service.h"
#include "components/sync/driver/sync_driver_switches.h"
#include "components/sync/driver/sync_user_settings.h"
#include "components/sync/engine/sync_engine_switches.h"
#include "components/sync/engine_impl/sync_scheduler_impl.h"
#include "components/sync/invalidations/switches.h"
#include "components/sync/invalidations/sync_invalidations_service_impl.h"
#include "components/sync/protocol/sync.pb.h"
#include "components/sync/test/fake_server/fake_server_network_resources.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "google_apis/gaia/gaia_urls.h"
#include "net/base/port_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "url/gurl.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/sync/test/integration/printers_helper.h"
#include "chrome/browser/sync/test/integration/sync_arc_package_helper.h"
#include "chrome/browser/ui/app_list/arc/arc_app_list_prefs_factory.h"
#include "chrome/browser/ui/app_list/test/fake_app_list_model_updater.h"
#include "chromeos/components/account_manager/account_manager.h"
#include "chromeos/components/account_manager/account_manager_factory.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/constants/chromeos_switches.h"
#include "components/arc/arc_util.h"
#endif // defined(OS_CHROMEOS)
#if defined(OS_ANDROID)
#include "chrome/browser/sync/test/integration/sync_test_utils_android.h"
#else
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/webui/signin/login_ui_service.h"
#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
#endif
using syncer::ProfileSyncService;
namespace switches {
const char kPasswordFileForTest[] = "password-file-for-test";
const char kSyncUserForTest[] = "sync-user-for-test";
const char kSyncPasswordForTest[] = "sync-password-for-test";
} // namespace switches
namespace {
// Sender ID coming from the Firebase console.
const char kInvalidationGCMSenderId[] = "8181035976";
void SetURLLoaderFactoryForTest(
Profile* profile,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) {
ChromeSigninClient* signin_client = static_cast<ChromeSigninClient*>(
ChromeSigninClientFactory::GetForProfile(profile));
signin_client->SetURLLoaderFactoryForTest(url_loader_factory);
#if defined(OS_CHROMEOS)
chromeos::AccountManagerFactory* factory =
g_browser_process->platform_part()->GetAccountManagerFactory();
chromeos::AccountManager* account_manager =
factory->GetAccountManager(profile->GetPath().value());
account_manager->SetUrlLoaderFactoryForTests(url_loader_factory);
#endif // defined(OS_CHROMEOS)
}
class FakePerUserTopicSubscriptionManager
: public syncer::PerUserTopicSubscriptionManager {
public:
explicit FakePerUserTopicSubscriptionManager(PrefService* local_state)
: syncer::PerUserTopicSubscriptionManager(
/*identity_provider=*/nullptr,
/*pref_service=*/local_state,
/*url_loader_factory=*/nullptr,
/*project_id*/ kInvalidationGCMSenderId,
/*migrate_prefs=*/false) {}
~FakePerUserTopicSubscriptionManager() override = default;
void UpdateSubscribedTopics(const syncer::Topics& topics,
const std::string& instance_id_token) override {}
private:
DISALLOW_COPY_AND_ASSIGN(FakePerUserTopicSubscriptionManager);
};
std::unique_ptr<syncer::FCMNetworkHandler> CreateFCMNetworkHandler(
Profile* profile,
std::map<const Profile*, syncer::FCMNetworkHandler*>*
profile_to_fcm_network_handler_map,
gcm::GCMDriver* gcm_driver,
instance_id::InstanceIDDriver* instance_id_driver,
const std::string& sender_id,
const std::string& app_id) {
auto handler = std::make_unique<syncer::FCMNetworkHandler>(
gcm_driver, instance_id_driver, sender_id, app_id);
(*profile_to_fcm_network_handler_map)[profile] = handler.get();
return handler;
}
std::unique_ptr<syncer::PerUserTopicSubscriptionManager>
CreatePerUserTopicSubscriptionManager(
invalidation::IdentityProvider* identity_provider,
PrefService* local_state,
network::mojom::URLLoaderFactory* url_loader_factory,
const std::string& project_id,
bool migrate_prefs) {
return std::make_unique<FakePerUserTopicSubscriptionManager>(local_state);
}
syncer::FCMNetworkHandler* GetFCMNetworkHandler(
Profile* profile,
std::map<const Profile*, syncer::FCMNetworkHandler*>*
profile_to_fcm_network_handler_map) {
// Delivering FCM notifications does not work if explicitly signed-out.
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile);
if (!identity_manager ||
!identity_manager->HasPrimaryAccount(signin::ConsentLevel::kNotRequired))
return nullptr;
auto it = profile_to_fcm_network_handler_map->find(profile);
return it != profile_to_fcm_network_handler_map->end() ? it->second : nullptr;
}
// Helper class to ensure a profile is registered before the manager is
// notified of creation.
class SyncProfileDelegate : public Profile::Delegate {
public:
explicit SyncProfileDelegate(
const base::Callback<void(Profile*)>& on_profile_created_callback)
: on_profile_created_callback_(on_profile_created_callback) {}
~SyncProfileDelegate() override = default;
void OnProfileCreated(Profile* profile,
bool success,
bool is_new_profile) override {
g_browser_process->profile_manager()->RegisterTestingProfile(
base::WrapUnique(profile), true);
// Perform any custom work needed before the profile is initialized.
if (!on_profile_created_callback_.is_null())
on_profile_created_callback_.Run(profile);
g_browser_process->profile_manager()->OnProfileCreated(profile, success,
is_new_profile);
}
private:
base::Callback<void(Profile*)> on_profile_created_callback_;
DISALLOW_COPY_AND_ASSIGN(SyncProfileDelegate);
};
instance_id::InstanceIDDriver* GetOrCreateInstanceIDDriver(
Profile* profile,
std::map<const Profile*, std::unique_ptr<instance_id::InstanceIDDriver>>*
profile_to_instance_id_driver_map) {
if (!profile_to_instance_id_driver_map->count(profile)) {
(*profile_to_instance_id_driver_map)[profile] =
std::make_unique<SyncTest::FakeInstanceIDDriver>();
}
return (*profile_to_instance_id_driver_map)[profile].get();
}
} // namespace
int SyncTest::FakeInstanceID::next_token_id_ = 1;
SyncTest::FakeInstanceID::FakeInstanceID(const std::string& app_id)
: instance_id::InstanceID(app_id, /*gcm_driver = */ nullptr),
token_(base::StringPrintf("token %d", next_token_id_++)) {}
void SyncTest::FakeInstanceID::GetToken(
const std::string& authorized_entity,
const std::string& scope,
base::TimeDelta time_to_live,
const std::map<std::string, std::string>& options,
std::set<Flags> flags,
GetTokenCallback callback) {
std::move(callback).Run(token_, instance_id::InstanceID::Result::SUCCESS);
}
SyncTest::FakeInstanceIDDriver::FakeInstanceIDDriver()
: instance_id::InstanceIDDriver(/*gcm_driver=*/nullptr) {}
SyncTest::FakeInstanceIDDriver::~FakeInstanceIDDriver() = default;
instance_id::InstanceID* SyncTest::FakeInstanceIDDriver::GetInstanceID(
const std::string& app_id) {
if (!fake_instance_ids_.count(app_id)) {
fake_instance_ids_[app_id] = std::make_unique<FakeInstanceID>(app_id);
}
return fake_instance_ids_[app_id].get();
}
bool SyncTest::FakeInstanceIDDriver::ExistsInstanceID(
const std::string& app_id) const {
return fake_instance_ids_.count(app_id);
}
SyncTest::SyncTest(TestType test_type)
: test_type_(test_type),
test_construction_time_(base::Time::Now()),
server_type_(SERVER_TYPE_UNDECIDED),
previous_profile_(nullptr),
num_clients_(-1),
use_verifier_(true) {
sync_datatype_helper::AssociateWithTest(this);
switch (test_type_) {
case SINGLE_CLIENT: {
num_clients_ = 1;
break;
}
case TWO_CLIENT: {
num_clients_ = 2;
break;
}
}
}
SyncTest::~SyncTest() = default;
void SyncTest::SetUp() {
#if defined(OS_ANDROID)
sync_test_utils_android::SetUpAuthForTesting();
sync_test_utils_android::SetUpAndroidSyncSettingsForTesting();
#endif
// Sets |server_type_| if it wasn't specified by the test.
DecideServerType();
base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
if (cl->HasSwitch(switches::kPasswordFileForTest)) {
ReadPasswordFile();
} else {
// Decide on username to use or create one.
if (cl->HasSwitch(switches::kSyncUserForTest)) {
username_ = cl->GetSwitchValueASCII(switches::kSyncUserForTest);
} else if (!UsingExternalServers()) {
username_ = "user@gmail.com";
}
// Decide on password to use.
password_ = cl->HasSwitch(switches::kSyncPasswordForTest)
? cl->GetSwitchValueASCII(switches::kSyncPasswordForTest)
: "password";
}
if (username_.empty() || password_.empty())
LOG(FATAL) << "Cannot run sync tests without GAIA credentials.";
// Mock the Mac Keychain service. The real Keychain can block on user input.
OSCryptMocker::SetUp();
// Yield control back to the PlatformBrowserTest framework.
PlatformBrowserTest::SetUp();
LOG(INFO) << "SyncTest::SetUp() completed; elapsed time since construction: "
<< (base::Time::Now() - test_construction_time_);
}
void SyncTest::TearDown() {
// Clear any mock gaia responses that might have been set.
ClearMockGaiaResponses();
// Allow the PlatformBrowserTest framework to perform its tear down.
PlatformBrowserTest::TearDown();
// Return OSCrypt to its real behaviour
OSCryptMocker::TearDown();
}
void SyncTest::PostRunTestOnMainThread() {
PlatformBrowserTest::PostRunTestOnMainThread();
#if defined(OS_ANDROID)
sync_test_utils_android::TearDownAuthForTesting();
#endif
}
void SyncTest::SetUpCommandLine(base::CommandLine* cl) {
AddTestSwitches(cl);
#if defined(OS_CHROMEOS)
cl->AppendSwitch(chromeos::switches::kIgnoreUserProfileMappingForTests);
cl->AppendSwitch(chromeos::switches::kDisableArcOptInVerification);
arc::SetArcAvailableCommandLineForTesting(cl);
#endif
}
void SyncTest::AddTestSwitches(base::CommandLine* cl) {
// Disable non-essential access of external network resources.
if (!cl->HasSwitch(switches::kDisableBackgroundNetworking))
cl->AppendSwitch(switches::kDisableBackgroundNetworking);
if (!cl->HasSwitch(switches::kSyncShortInitialRetryOverride))
cl->AppendSwitch(switches::kSyncShortInitialRetryOverride);
if (!cl->HasSwitch(switches::kSyncShortNudgeDelayForTest))
cl->AppendSwitch(switches::kSyncShortNudgeDelayForTest);
// TODO(crbug.com/1060366): This is a temporary switch to allow having two
// profiles syncing the same account. Having a profile outside of the user
// directory isn't supported in Chrome.
if (!cl->HasSwitch(switches::kAllowProfilesOutsideUserDir))
cl->AppendSwitch(switches::kAllowProfilesOutsideUserDir);
}
void SyncTest::BeforeSetupClient(int index,
const base::FilePath& profile_path) {}
bool SyncTest::CreateProfile(int index) {
base::FilePath profile_path;
// For Android, we don't create profile because Clank doesn't support
// multiple profiles.
#if !defined(OS_ANDROID)
base::ScopedAllowBlockingForTesting allow_blocking;
if (UsingExternalServers() && (num_clients_ > 1 || use_new_user_data_dir_)) {
scoped_temp_dirs_.push_back(std::make_unique<base::ScopedTempDir>());
// For multi profile UI signin, profile paths should be outside user data
// dir to allow signing-in multiple profiles to same account. Otherwise, we
// get an error that the profile has already signed in on this device.
// Note: Various places in Chrome assume that all profiles are within the
// user data dir. We violate that assumption here, which can lead to weird
// issues, see https://crbug.com/801569 and the workaround in
// TearDownOnMainThread.
if (!scoped_temp_dirs_.back()->CreateUniqueTempDir()) {
ADD_FAILURE();
return false;
}
profile_path = scoped_temp_dirs_.back()->GetPath();
} else {
base::FilePath user_data_dir;
base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
// Create new profiles in user data dir so that other profiles can know
// about it. This is needed in tests such as supervised user cases which
// assume browser->profile() as the custodian profile. Instead of creating
// a new directory, we use a deterministic name such that PRE_ tests (i.e.
// test that span browser restarts) can reuse the same directory and carry
// over state.
profile_path = user_data_dir.AppendASCII(
base::StringPrintf("SyncIntegrationTestClient%d", index));
}
#endif
BeforeSetupClient(index, profile_path);
if (UsingExternalServers()) {
// If running against an EXTERNAL_LIVE_SERVER, we signin profiles using real
// GAIA server. This requires creating profiles with no test hooks.
InitializeProfile(index, MakeProfileForUISignin(profile_path));
} else {
// Use default profile for Android.
#if defined(OS_ANDROID)
DCHECK(index == 0);
Profile* profile = ProfileManager::GetLastUsedProfile();
InitializeProfile(index, profile);
#else
// Without need of real GAIA authentication, we create new test profiles.
// For test profiles, a custom delegate needs to be used to do the
// initialization work before the profile is registered.
profile_delegates_[index] =
std::make_unique<SyncProfileDelegate>(base::Bind(
&SyncTest::InitializeProfile, base::Unretained(this), index));
Profile* profile = MakeTestProfile(profile_path, index);
#endif
SetupMockGaiaResponsesForProfile(profile);
}
// Once profile initialization has kicked off, wait for it to finish.
WaitForDataModels(GetProfile(index));
return true;
}
// Called when the ProfileManager has created a profile.
// static
void SyncTest::CreateProfileCallback(const base::Closure& quit_closure,
Profile* profile,
Profile::CreateStatus status) {
EXPECT_TRUE(profile);
EXPECT_NE(Profile::CREATE_STATUS_LOCAL_FAIL, status);
EXPECT_NE(Profile::CREATE_STATUS_REMOTE_FAIL, status);
// This will be called multiple times. Wait until the profile is initialized
// fully to quit the loop.
if (status == Profile::CREATE_STATUS_INITIALIZED)
quit_closure.Run();
}
// TODO(shadi): Ideally creating a new profile should not depend on signin
// process. We should try to consolidate MakeProfileForUISignin() and
// MakeProfile(). Major differences are profile paths and creation methods. For
// UI signin we need profiles in unique user data dir's and we need to use
// ProfileManager::CreateProfileAsync() for proper profile creation.
// static
Profile* SyncTest::MakeProfileForUISignin(base::FilePath profile_path) {
ProfileManager* profile_manager = g_browser_process->profile_manager();
base::RunLoop run_loop;
ProfileManager::CreateCallback create_callback =
base::Bind(&CreateProfileCallback, run_loop.QuitClosure());
profile_manager->CreateProfileAsync(profile_path, create_callback,
base::string16(), std::string());
run_loop.Run();
return profile_manager->GetProfileByPath(profile_path);
}
Profile* SyncTest::MakeTestProfile(base::FilePath profile_path, int index) {
std::unique_ptr<Profile> profile =
Profile::CreateProfile(profile_path, profile_delegates_[index].get(),
Profile::CREATE_MODE_SYNCHRONOUS);
return profile.release();
}
Profile* SyncTest::GetProfile(int index) {
EXPECT_FALSE(profiles_.empty()) << "SetupClients() has not yet been called.";
EXPECT_FALSE(index < 0 || index >= static_cast<int>(profiles_.size()))
<< "GetProfile(): Index is out of bounds.";
Profile* profile = profiles_[index];
EXPECT_NE(nullptr, profile) << "No profile found at index: " << index;
return profile;
}
std::vector<Profile*> SyncTest::GetAllProfiles() {
std::vector<Profile*> profiles;
if (use_verifier()) {
profiles.push_back(verifier());
}
for (int i = 0; i < num_clients(); ++i) {
profiles.push_back(GetProfile(i));
}
return profiles;
}
#if !defined(OS_ANDROID)
Browser* SyncTest::GetBrowser(int index) {
EXPECT_FALSE(browsers_.empty()) << "SetupClients() has not yet been called.";
EXPECT_FALSE(index < 0 || index >= static_cast<int>(browsers_.size()))
<< "GetBrowser(): Index is out of bounds.";
Browser* browser = browsers_[index];
EXPECT_NE(browser, nullptr);
return browsers_[index];
}
Browser* SyncTest::AddBrowser(int profile_index) {
Profile* profile = GetProfile(profile_index);
browsers_.push_back(new Browser(Browser::CreateParams(profile, true)));
profiles_.push_back(profile);
return browsers_[browsers_.size() - 1];
}
#endif
ProfileSyncServiceHarness* SyncTest::GetClient(int index) {
if (clients_.empty())
LOG(FATAL) << "SetupClients() has not yet been called.";
if (index < 0 || index >= static_cast<int>(clients_.size()))
LOG(FATAL) << "GetClient(): Index is out of bounds.";
return clients_[index].get();
}
std::vector<ProfileSyncServiceHarness*> SyncTest::GetSyncClients() {
std::vector<ProfileSyncServiceHarness*> clients(clients_.size());
for (size_t i = 0; i < clients_.size(); ++i)
clients[i] = clients_[i].get();
return clients;
}
ProfileSyncService* SyncTest::GetSyncService(int index) {
return ProfileSyncServiceFactory::GetAsProfileSyncServiceForProfile(
GetProfile(index));
}
syncer::UserSelectableTypeSet SyncTest::GetRegisteredSelectableTypes(
int index) {
return GetSyncService(index)
->GetUserSettings()
->GetRegisteredSelectableTypes();
}
std::vector<ProfileSyncService*> SyncTest::GetSyncServices() {
std::vector<ProfileSyncService*> services;
for (int i = 0; i < num_clients(); ++i) {
services.push_back(GetSyncService(i));
}
return services;
}
Profile* SyncTest::verifier() {
if (!use_verifier_)
LOG(FATAL) << "Verifier account is disabled.";
if (verifier_ == nullptr)
LOG(FATAL) << "SetupClients() has not yet been called.";
return verifier_;
}
void SyncTest::DisableVerifier() {
use_verifier_ = false;
}
bool SyncTest::SetupClients() {
previous_profile_ =
g_browser_process->profile_manager()->GetLastUsedProfile();
base::ScopedAllowBlockingForTesting allow_blocking;
if (num_clients_ <= 0)
LOG(FATAL) << "num_clients_ incorrectly initialized.";
bool has_any_browser = false;
#if !defined(OS_ANDROID)
has_any_browser = !browsers_.empty();
#endif
if (!profiles_.empty() || has_any_browser || !clients_.empty())
LOG(FATAL) << "SetupClients() has already been called.";
// Create the required number of sync profiles, browsers and clients.
profiles_.resize(num_clients_);
profile_delegates_.resize(num_clients_ + 1); // + 1 for the verifier.
clients_.resize(num_clients_);
fake_server_invalidation_observers_.resize(num_clients_);
auto* cl = base::CommandLine::ForCurrentProcess();
if (!cl->HasSwitch(switches::kSyncDeferredStartupTimeoutSeconds)) {
cl->AppendSwitchASCII(switches::kSyncDeferredStartupTimeoutSeconds, "1");
}
#if defined(OS_CHROMEOS)
// ARC_PACKAGE do not support supervised users, switches::kSupervisedUserId
// need to be set in SetUpCommandLine() when a test will use supervise users.
if (!cl->HasSwitch(switches::kSupervisedUserId)) {
// Sets Arc flags, need to be called before create test profiles.
ArcAppListPrefsFactory::SetFactoryForSyncTest();
}
// Uses a fake app list model updater to avoid interacting with Ash.
model_updater_factory_ = std::make_unique<
app_list::AppListSyncableService::ScopedModelUpdaterFactoryForTest>(
base::Bind([]() -> std::unique_ptr<AppListModelUpdater> {
return std::make_unique<FakeAppListModelUpdater>();
}));
#endif
for (int i = 0; i < num_clients_; ++i) {
if (!CreateProfile(i)) {
return false;
}
LOG(INFO) << "SyncTest::SetupClients() created profile " << i
<< "; elapsed time since construction: "
<< (base::Time::Now() - test_construction_time_);
}
// Verifier account is not useful when running against external servers.
if (UsingExternalServers())
DisableVerifier();
// Verifier needs to create a test profile. But Clank doesn't support multiple
// profiles.
#if defined(OS_ANDROID)
DisableVerifier();
#endif
// Create the verifier profile.
if (use_verifier_) {
base::FilePath user_data_dir;
base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
profile_delegates_[num_clients_] =
std::make_unique<SyncProfileDelegate>(base::Callback<void(Profile*)>());
verifier_ = MakeTestProfile(
user_data_dir.Append(FILE_PATH_LITERAL("Verifier")), num_clients_);
WaitForDataModels(verifier());
}
#if defined(OS_CHROMEOS)
// SplitSettingsSync makes several types (e.g. APPS, APP_LIST, PRINTERS) into
// OS sync types. OS sync is on-by-default, so enable it here.
if (chromeos::features::IsSplitSettingsSyncEnabled()) {
for (int i = 0; i < num_clients(); ++i) {
GetSyncService(i)->GetUserSettings()->SetOsSyncFeatureEnabled(true);
}
}
if (ArcAppListPrefsFactory::IsFactorySetForSyncTest()) {
// Init SyncArcPackageHelper to ensure that the arc services are initialized
// for each Profile, only can be called after test profiles are created.
if (!sync_arc_helper())
return false;
}
#endif
// Create the fake server sync invalidations sender.
if (server_type_ == IN_PROCESS_FAKE_SERVER) {
fake_server_sync_invalidation_sender_ =
std::make_unique<fake_server::FakeServerSyncInvalidationSender>(
GetFakeServer(), sync_invalidations_fcm_handlers_);
}
LOG(INFO)
<< "SyncTest::SetupClients() completed; elapsed time since construction: "
<< (base::Time::Now() - test_construction_time_);
return true;
}
void SyncTest::InitializeProfile(int index, Profile* profile) {
DCHECK(profile);
profiles_[index] = profile;
SetUpInvalidations(index);
#if !defined(OS_ANDROID)
AddBrowser(index);
#endif
// Make sure the ProfileSyncService has been created before creating the
// ProfileSyncServiceHarness - some tests expect the ProfileSyncService to
// already exist.
ProfileSyncService* profile_sync_service =
ProfileSyncServiceFactory::GetAsProfileSyncServiceForProfile(
GetProfile(index));
if (server_type_ == IN_PROCESS_FAKE_SERVER) {
profile_sync_service->OverrideNetworkForTest(
fake_server::CreateFakeServerHttpPostProviderFactory(
GetFakeServer()->AsWeakPtr()));
}
ProfileSyncServiceHarness::SigninType singin_type =
UsingExternalServers()
? ProfileSyncServiceHarness::SigninType::UI_SIGNIN
: ProfileSyncServiceHarness::SigninType::FAKE_SIGNIN;
DCHECK(!clients_[index]);
clients_[index] = ProfileSyncServiceHarness::Create(
GetProfile(index), username_, password_, singin_type);
EXPECT_NE(nullptr, GetClient(index)) << "Could not create Client " << index;
InitializeInvalidations(index);
}
void SyncTest::DisableNotificationsForClient(int index) {
fake_server_->RemoveObserver(
fake_server_invalidation_observers_[index].get());
}
void SyncTest::SetEncryptionPassphraseForClient(int index,
const std::string& passphrase) {
// Must be called before client initialization.
DCHECK(clients_.empty());
client_encryption_passphrases_[index] = passphrase;
}
void SyncTest::SetDecryptionPassphraseForClient(int index,
const std::string& passphrase) {
// Must be called before client initialization.
DCHECK(clients_.empty());
client_decryption_passphrases_[index] = passphrase;
}
void SyncTest::SetupMockGaiaResponsesForProfile(Profile* profile) {
SetURLLoaderFactoryForTest(profile,
test_url_loader_factory_.GetSafeWeakWrapper());
}
void SyncTest::SetUpInvalidations(int index) {
switch (server_type_) {
case EXTERNAL_LIVE_SERVER:
// DO NOTHING. External live sync servers use GCM to notify profiles of
// any invalidations in sync'ed data. In this case, to notify other
// profiles of invalidations, we use sync refresh notifications instead.
break;
case IN_PROCESS_FAKE_SERVER: {
const std::string client_id = "Client " + base::NumberToString(index);
// Listen for fake server changes.
fake_server_invalidation_observers_[index] =
std::make_unique<fake_server::FakeServerInvalidationSender>(
client_id, TestUsesSelfNotifications(),
base::BindRepeating(&GetFCMNetworkHandler, GetProfile(index),
&profile_to_fcm_network_handler_map_));
fake_server_->AddObserver(
fake_server_invalidation_observers_[index].get());
// Store in prefs the mapping between public and private topics names. In
// real clients, those are stored upon subscription with the
// per-user-topic server. The pref name is defined in
// per_user_topic_subscription_manager.cc.
DictionaryPrefUpdate update(
GetProfile(index)->GetPrefs(),
"invalidation.per_sender_registered_for_invalidation");
update->SetDictionary(kInvalidationGCMSenderId,
std::make_unique<base::DictionaryValue>());
for (syncer::ModelType model_type :
GetSyncService(index)->GetPreferredDataTypes()) {
std::string notification_type;
if (!RealModelTypeToNotificationType(model_type, &notification_type)) {
continue;
}
update->FindDictKey(kInvalidationGCMSenderId)
->SetKey(notification_type,
base::Value("/private/" + notification_type +
"-topic_server_user_id"));
}
DictionaryPrefUpdate update_client_id(
GetProfile(index)->GetPrefs(),
invalidation::prefs::kInvalidationClientIDCache);
update_client_id->SetString(kInvalidationGCMSenderId, client_id);
break;
}
case SERVER_TYPE_UNDECIDED:
NOTREACHED();
}
}
void SyncTest::InitializeInvalidations(int index) {
// Lazily create |configuration_refresher_| the first time we get here (or the
// first time after a previous call to StopConfigurationRefresher).
if (!configuration_refresher_) {
configuration_refresher_ = std::make_unique<ConfigurationRefresher>();
}
switch (server_type_) {
case EXTERNAL_LIVE_SERVER:
// DO NOTHING. External live sync servers use GCM to notify profiles of
// any invalidations in sync'ed data. In this case, to notify other
// profiles of invalidations, we use sync refresh notifications instead.
break;
case IN_PROCESS_FAKE_SERVER: {
configuration_refresher_->Observe(
ProfileSyncServiceFactory::GetForProfile(GetProfile(index)));
break;
}
case SERVER_TYPE_UNDECIDED:
NOTREACHED();
}
}
void SyncTest::SetupSyncNoWaitingForCompletion() {
SetupSyncInternal(/*setup_mode=*/NO_WAITING);
}
void SyncTest::SetupSyncOneClientAfterAnother() {
SetupSyncInternal(/*setup_mode=*/WAIT_FOR_COMMITS_TO_COMPLETE);
}
void SyncTest::SetupSyncInternal(SetupSyncMode setup_mode) {
// Create sync profiles and clients if they haven't already been created.
if (profiles_.empty()) {
if (!SetupClients()) {
LOG(FATAL) << "SetupClients() failed.";
}
}
// TODO(crbug.com/801482): If we ever start running tests against external
// servers again, we might have to find a way to clear any pre-existing data
// from the test account.
if (UsingExternalServers()) {
LOG(ERROR) << "WARNING: Running against external servers with an existing "
"account. If there is any pre-existing data in the account, "
"things will likely break.";
}
// Sync each of the profiles.
for (int client_index = 0; client_index < num_clients_; client_index++) {
ProfileSyncServiceHarness* client = GetClient(client_index);
DVLOG(1) << "Setting up " << client_index << " client";
auto decryption_passphrase_it =
client_decryption_passphrases_.find(client_index);
auto encryption_passphrase_it =
client_encryption_passphrases_.find(client_index);
bool decryption_passphrase_provided =
(decryption_passphrase_it != client_decryption_passphrases_.end());
bool encryption_passphrase_provided =
(encryption_passphrase_it != client_encryption_passphrases_.end());
if (decryption_passphrase_provided && encryption_passphrase_provided) {
LOG(FATAL) << "Both an encryption and decryption passphrase were "
"provided for the client. This is disallowed.";
}
if (encryption_passphrase_provided) {
CHECK(client->SetupSyncWithEncryptionPassphraseNoWaitForCompletion(
GetRegisteredSelectableTypes(client_index),
encryption_passphrase_it->second))
<< "SetupSync() failed.";
} else if (decryption_passphrase_provided) {
CHECK(client->SetupSyncWithDecryptionPassphraseNoWaitForCompletion(
GetRegisteredSelectableTypes(client_index),
decryption_passphrase_it->second))
<< "SetupSync() failed.";
} else {
CHECK(client->SetupSyncNoWaitForCompletion(
GetRegisteredSelectableTypes(client_index)))
<< "SetupSync() failed.";
}
// It's important to wait for each client before setting up the next one,
// otherwise multi-client tests get flaky.
// TODO(crbug.com/956043): It would be nice to figure out why.
switch (setup_mode) {
case NO_WAITING:
break;
case WAIT_FOR_SYNC_SETUP_TO_COMPLETE:
client->AwaitSyncSetupCompletion();
break;
case WAIT_FOR_COMMITS_TO_COMPLETE:
DCHECK(TestUsesSelfNotifications())
<< "We need that for the UpdatedProgressMarkerChecker";
UpdatedProgressMarkerChecker checker(GetSyncService(client_index));
checker.Wait();
break;
}
}
}
void SyncTest::ClearProfiles() {
profiles_.clear();
profile_delegates_.clear();
scoped_temp_dirs_.clear();
#if !defined(OS_ANDROID)
browsers_.clear();
#endif
clients_.clear();
}
bool SyncTest::SetupSync() {
#if defined(OS_ANDROID)
// For Android, currently the framework only supports one client.
// The client uses the default profile.
DCHECK(num_clients_ == 1) << "For Android, currently it only supports "
<< "one client.";
#endif
base::ScopedAllowBlockingForTesting allow_blocking;
SetupSyncInternal(/*setup_mode=*/WAIT_FOR_SYNC_SETUP_TO_COMPLETE);
// Because clients may modify sync data as part of startup (for example
// local session-releated data is rewritten), we need to ensure all
// startup-based changes have propagated between the clients.
//
// Tests that don't use self-notifications can't await quiescense. They'll
// have to find their own way of waiting for an initial state if they really
// need such guarantees.
if (TestUsesSelfNotifications()) {
if (!AwaitQuiescence()) {
LOG(FATAL) << "AwaitQuiescence() failed.";
return false;
}
}
#if !defined(OS_ANDROID)
if (UsingExternalServers()) {
// OneClickSigninSyncStarter observer is created with a real user sign in.
// It is deleted on certain conditions which are not satisfied by our tests,
// and this causes the SigninTracker observer to stay hanging at shutdown.
// Calling LoginUIService::SyncConfirmationUIClosed forces the observer to
// be removed. http://crbug.com/484388
for (int i = 0; i < num_clients_; ++i) {
LoginUIServiceFactory::GetForProfile(GetProfile(i))
->SyncConfirmationUIClosed(
LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
}
}
#endif
return true;
}
void SyncTest::TearDownOnMainThread() {
// Workaround for https://crbug.com/801569: |prefs::kProfileLastUsed| stores
// the profile path relative to the user dir, but our testing profiles are
// outside the user dir (see CreateProfile). So code trying to access the last
// used profile by path will fail. To work around that, set the last used
// profile back to the originally created default profile (which does live in
// the user data dir, and which we don't use otherwise).
if (previous_profile_) {
profiles::SetLastUsedProfile(
previous_profile_->GetPath().BaseName().MaybeAsASCII());
}
#if !defined(OS_ANDROID)
// Closing all browsers created by this test. The calls here block until
// they are closed. Other browsers created outside SyncTest setup should be
// closed by the creator of that browser.
size_t init_browser_count = chrome::GetTotalBrowserCount();
for (size_t i = 0; i < browsers_.size(); ++i) {
CloseBrowserSynchronously(browsers_[i]);
}
ASSERT_EQ(chrome::GetTotalBrowserCount(),
init_browser_count - browsers_.size());
#endif
if (fake_server_.get()) {
for (const std::unique_ptr<fake_server::FakeServerInvalidationSender>&
observer : fake_server_invalidation_observers_) {
fake_server_->RemoveObserver(observer.get());
}
fake_server_sync_invalidation_sender_.reset();
fake_server_.reset();
}
// Delete things that unsubscribe in destructor before their targets are gone.
configuration_refresher_.reset();
PlatformBrowserTest::TearDownOnMainThread();
}
void SyncTest::SetUpInProcessBrowserTestFixture() {
create_services_subscription_ =
BrowserContextDependencyManager::GetInstance()
->RegisterCreateServicesCallbackForTesting(
base::BindRepeating(&SyncTest::OnWillCreateBrowserContextServices,
base::Unretained(this)));
}
void SyncTest::OnWillCreateBrowserContextServices(
content::BrowserContext* context) {
if (UsingExternalServers()) {
// DO NOTHING. External live sync servers use GCM to notify profiles of
// any invalidations in sync'ed data. No need to provide a testing
// factory for ProfileInvalidationProvider and SyncInvalidationsService.
return;
}
invalidation::ProfileInvalidationProviderFactory::GetInstance()
->SetTestingFactory(
context,
base::BindRepeating(&SyncTest::CreateProfileInvalidationProvider,
&profile_to_fcm_network_handler_map_,
&profile_to_instance_id_driver_map_));
SyncInvalidationsServiceFactory::GetInstance()->SetTestingFactory(
context, base::BindRepeating(&SyncTest::CreateSyncInvalidationsService,
&profile_to_instance_id_driver_map_,
&sync_invalidations_fcm_handlers_));
}
// static
std::unique_ptr<KeyedService> SyncTest::CreateProfileInvalidationProvider(
std::map<const Profile*, syncer::FCMNetworkHandler*>*
profile_to_fcm_network_handler_map,
std::map<const Profile*, std::unique_ptr<instance_id::InstanceIDDriver>>*
profile_to_instance_id_driver_map,
content::BrowserContext* context) {
Profile* profile = Profile::FromBrowserContext(context);
gcm::GCMProfileService* gcm_profile_service =
gcm::GCMProfileServiceFactory::GetForProfile(profile);
instance_id::InstanceIDDriver* instance_id_driver =
GetOrCreateInstanceIDDriver(profile, profile_to_instance_id_driver_map);
auto profile_identity_provider =
std::make_unique<invalidation::ProfileIdentityProvider>(
IdentityManagerFactory::GetForProfile(profile));
auto fcm_invalidation_service =
std::make_unique<invalidation::FCMInvalidationService>(
profile_identity_provider.get(),
base::BindRepeating(&CreateFCMNetworkHandler, profile,
profile_to_fcm_network_handler_map,
gcm_profile_service->driver(),
instance_id_driver),
base::BindRepeating(
&CreatePerUserTopicSubscriptionManager,
profile_identity_provider.get(), profile->GetPrefs(),
base::RetainedRef(
content::BrowserContext::GetDefaultStoragePartition(profile)
->GetURLLoaderFactoryForBrowserProcess()
.get())),
instance_id_driver, profile->GetPrefs(), kInvalidationGCMSenderId);
fcm_invalidation_service->Init();
return std::make_unique<invalidation::ProfileInvalidationProvider>(
std::move(fcm_invalidation_service), std::move(profile_identity_provider),
/*custom_sender_invalidation_service_factory=*/
base::BindRepeating(
[](const std::string&)
-> std::unique_ptr<invalidation::InvalidationService> {
return std::make_unique<invalidation::FakeInvalidationService>();
}));
}
// static
std::unique_ptr<KeyedService> SyncTest::CreateSyncInvalidationsService(
std::map<const Profile*, std::unique_ptr<instance_id::InstanceIDDriver>>*
profile_to_instance_id_driver_map,
std::vector<syncer::FCMHandler*>* sync_invalidations_fcm_handlers,
content::BrowserContext* context) {
if (!base::FeatureList::IsEnabled(switches::kSyncSendInterestedDataTypes)) {
return nullptr;
}
Profile* profile = Profile::FromBrowserContext(context);
gcm::GCMDriver* gcm_driver =
gcm::GCMProfileServiceFactory::GetForProfile(profile)->driver();
instance_id::InstanceIDDriver* instance_id_driver =
GetOrCreateInstanceIDDriver(profile, profile_to_instance_id_driver_map);
auto service = std::make_unique<syncer::SyncInvalidationsServiceImpl>(
gcm_driver, instance_id_driver);
sync_invalidations_fcm_handlers->push_back(
service->GetFCMHandlerForTesting());
return std::move(service);
}
void SyncTest::ResetSyncForPrimaryAccount() {
if (UsingExternalServers()) {
base::ScopedAllowBlockingForTesting allow_blocking;
// For external server testing, we need to have a clean account.
// The following code will sign in one chrome browser, get
// the client id and access token, then clean the server data.
int old_num_clients = num_clients_;
int old_use_new_user_data_dir = use_new_user_data_dir_;
use_new_user_data_dir_ = true;
num_clients_ = 1;
// Do not wait for sync complete. Some tests set passphrase and sync
// will fail. NO_WAITING mode gives access token and birthday so
// ProfileSyncServiceHarness::ResetSyncForPrimaryAccount() can succeed.
// The passphrase will be reset together with the rest of the sync data
// clearing.
SetupSyncNoWaitingForCompletion();
GetClient(0)->ResetSyncForPrimaryAccount();
// After reset account, the client should get a NOT_MY_BIRTHDAY error
// and disable sync. Adding a wait to make sure this is propagated.
ASSERT_TRUE(SyncDisabledChecker(GetSyncService(0)).Wait());
#if !defined(OS_ANDROID)
CloseBrowserSynchronously(browsers_[0]);
#endif
// After reset, this client will disable sync. It may log some messages
// that do not contribute to test failures. It includes:
// PostClientToServerMessage with SERVER_RETURN_NOT_MY_BIRTHDAY
// PostClientToServerMessage with NETWORK_CONNECTION_UNAVAILABLE
// mcs_client fails with 401.
LOG(WARNING) << "Finished reset account. Warning logs before "
<< "this log may be safe to ignore.";
ClearProfiles();
use_new_user_data_dir_ = old_use_new_user_data_dir;
num_clients_ = old_num_clients;
}
}
void SyncTest::SetUpOnMainThread() {
// Start up a sync test server if one is needed and setup mock gaia responses.
// Note: This must be done prior to the call to SetupClients() because we want
// the mock gaia responses to be available before GaiaUrls is initialized.
SetUpTestServerIfRequired();
if (UsingExternalServers()) {
// Allows google.com as well as country-specific TLDs.
host_resolver()->AllowDirectLookup("*.google.com");
host_resolver()->AllowDirectLookup("accounts.google.*");
host_resolver()->AllowDirectLookup("*.googleusercontent.com");
// Allow connection to googleapis.com for oauth token requests in E2E tests.
host_resolver()->AllowDirectLookup("*.googleapis.com");
// On Linux, we use Chromium's NSS implementation which uses the following
// hosts for certificate verification. Without these overrides, running the
// integration tests on Linux causes error as we make external DNS lookups.
host_resolver()->AllowDirectLookup("*.thawte.com");
host_resolver()->AllowDirectLookup("*.geotrust.com");
host_resolver()->AllowDirectLookup("*.gstatic.com");
} else {
SetupMockGaiaResponsesForProfile(ProfileManager::GetActiveUserProfile());
}
}
void SyncTest::WaitForDataModels(Profile* profile) {
bookmarks::test::WaitForBookmarkModelToLoad(
BookmarkModelFactory::GetForBrowserContext(profile));
#if defined(OS_CHROMEOS)
printers_helper::WaitForPrinterStoreToLoad(profile);
#endif
}
void SyncTest::ReadPasswordFile() {
base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
password_file_ = cl->GetSwitchValuePath(switches::kPasswordFileForTest);
if (password_file_.empty())
LOG(FATAL) << "Can't run live server test without specifying --"
<< switches::kPasswordFileForTest << "=<filename>";
std::string file_contents;
base::ReadFileToString(password_file_, &file_contents);
ASSERT_NE(file_contents, "")
<< "Password file \"" << password_file_.value() << "\" does not exist.";
std::vector<std::string> tokens = base::SplitString(
file_contents, "\r\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
ASSERT_EQ(2U, tokens.size()) << "Password file \"" << password_file_.value()
<< "\" must contain exactly two lines of text.";
username_ = tokens[0];
password_ = tokens[1];
}
void SyncTest::SetupMockGaiaResponses() {
test_url_loader_factory_.AddResponse(
GaiaUrls::GetInstance()->get_user_info_url().spec(),
"email=user@gmail.com\ndisplayEmail=user@gmail.com");
test_url_loader_factory_.AddResponse(
GaiaUrls::GetInstance()->oauth2_token_url().spec(),
R"({
"refresh_token": "rt1",
"access_token": "at1",
"expires_in": 3600,
"token_type": "Bearer"
})");
test_url_loader_factory_.AddResponse(
GaiaUrls::GetInstance()->oauth_user_info_url().spec(),
"{ \"id\": \"12345\" }");
test_url_loader_factory_.AddResponse(
GaiaUrls::GetInstance()->oauth1_login_url().spec(),
"SID=sid\nLSID=lsid\nAuth=auth_token");
test_url_loader_factory_.AddResponse(
GaiaUrls::GetInstance()->oauth2_revoke_url().spec(), "");
}
void SyncTest::SetOAuth2TokenResponse(const std::string& response_data,
net::HttpStatusCode status_code,
net::Error net_error) {
network::URLLoaderCompletionStatus completion_status(net_error);
completion_status.decoded_body_length = response_data.size();
std::string response = base::StringPrintf("HTTP/1.1 %d %s\r\n", status_code,
GetHttpReasonPhrase(status_code));
auto response_head = network::mojom::URLResponseHead::New();
response_head->headers =
base::MakeRefCounted<net::HttpResponseHeaders>(response);
test_url_loader_factory_.AddResponse(
GaiaUrls::GetInstance()->oauth2_token_url(), std::move(response_head),
response_data, completion_status);
base::RunLoop().RunUntilIdle();
}
void SyncTest::ClearMockGaiaResponses() {
// Clear any mock gaia responses that might have been set.
test_url_loader_factory_.ClearResponses();
}
void SyncTest::DecideServerType() {
// Only set |server_type_| if it hasn't already been set. This allows for
// tests to explicitly set this value in each test class if needed.
if (server_type_ == SERVER_TYPE_UNDECIDED) {
base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
if (!cl->HasSwitch(switches::kSyncServiceURL)) {
// If no sync server URL is provided, start up a local sync test server
// and point Chrome to its URL. This is the most common configuration,
server_type_ = IN_PROCESS_FAKE_SERVER;
} else {
// If a sync server URL is provided, it is assumed that the server is
// already running. Chrome will automatically connect to it at the URL
// provided. There is nothing to do here.
server_type_ = EXTERNAL_LIVE_SERVER;
}
}
}
// Start up a local sync server based on the value of server_type_, which
// was determined from the command line parameters.
void SyncTest::SetUpTestServerIfRequired() {
if (UsingExternalServers()) {
// Nothing to do; we'll just talk to the URL we were given.
} else if (server_type_ == IN_PROCESS_FAKE_SERVER) {
base::FilePath user_data_dir;
base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
fake_server_ = std::make_unique<fake_server::FakeServer>(user_data_dir);
SetupMockGaiaResponses();
} else {
LOG(FATAL) << "Don't know which server environment to run test in.";
}
}
bool SyncTest::TestUsesSelfNotifications() {
// Default is True unless we are running against external servers.
return !UsingExternalServers();
}
bool SyncTest::AwaitQuiescence() {
return ProfileSyncServiceHarness::AwaitQuiescence(GetSyncClients());
}
bool SyncTest::UsingExternalServers() {
return server_type_ == EXTERNAL_LIVE_SERVER;
}
void SyncTest::TriggerMigrationDoneError(syncer::ModelTypeSet model_types) {
ASSERT_TRUE(server_type_ == IN_PROCESS_FAKE_SERVER);
fake_server_->TriggerMigrationDoneError(model_types);
}
fake_server::FakeServer* SyncTest::GetFakeServer() const {
return fake_server_.get();
}
void SyncTest::TriggerSyncForModelTypes(int index,
syncer::ModelTypeSet model_types) {
GetSyncService(index)->TriggerRefresh(model_types);
}
void SyncTest::StopConfigurationRefresher() {
configuration_refresher_.reset();
}
arc::SyncArcPackageHelper* SyncTest::sync_arc_helper() {
#if defined(OS_CHROMEOS)
return arc::SyncArcPackageHelper::GetInstance();
#else
return nullptr;
#endif
}