Commit 02f9f701 authored by Alan Cutter's avatar Alan Cutter Committed by Commit Bot

Refactor ExternalWebAppManager config loading flow

This CL splits ExternalWebAppManager's config loading flow into discrete
steps and removes the various testing entry points that omit or
duplicate logic used by the real thing.

Tests can now inject data (file_utils, config_dir, configs) into the
config loading pipeline instead of having separate mini pipelines just
for them.

This change is in preparation for adding a new "FilterExtensionIds" step
to the config loading flow. Without this refactor there would be
duplication of logic between real and test code code paths that would be
prone to bitrot and missing real coverage.

Bug: 1128801
Change-Id: Ie5e87b26ab47782fab6589a4127df3ea5e876fec
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2423823Reviewed-by: default avatarDevlin <rdevlin.cronin@chromium.org>
Reviewed-by: default avatarGlen Robertson <glenrob@chromium.org>
Commit-Queue: Alan Cutter <alancutter@chromium.org>
Cr-Commit-Position: refs/heads/master@{#810025}
parent 210f1b4a
......@@ -53,29 +53,27 @@ const base::FilePath::CharType kWebAppsSubDirectory[] =
#endif
bool g_skip_startup_for_testing_ = false;
const base::FilePath* g_config_dir_for_testing = nullptr;
const std::vector<base::Value>* g_configs_for_testing = nullptr;
const FileUtilsWrapper* g_file_utils_for_testing = nullptr;
std::vector<ExternalInstallOptions> LoadInstallOptionsBlocking(
std::unique_ptr<FileUtilsWrapper> file_utils,
const base::FilePath& dir,
const std::string& user_type) {
std::vector<ExternalInstallOptions> install_options_list;
if (!base::FeatureList::IsEnabled(features::kDefaultWebAppInstallation))
return install_options_list;
struct LoadedConfig {
base::Value contents;
base::FilePath file;
};
int disabled_count = 0;
struct LoadedConfigs {
std::vector<LoadedConfig> configs;
int error_count = 0;
};
// Load hard coded apps.
PreinstalledWebApps preinstalled_web_apps = GetPreinstalledWebApps();
for (ExternalInstallOptions& options : preinstalled_web_apps.options)
install_options_list.push_back(std::move(options));
disabled_count += preinstalled_web_apps.disabled_count;
// Load JSON config apps.
LoadedConfigs LoadConfigsBlocking(const base::FilePath& config_dir) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
LoadedConfigs result;
base::FilePath::StringType extension(FILE_PATH_LITERAL(".json"));
base::FileEnumerator json_files(dir,
base::FileEnumerator json_files(config_dir,
false, // Recursive.
base::FileEnumerator::FILES);
for (base::FilePath file = json_files.Next(); !file.empty();
......@@ -90,91 +88,49 @@ std::vector<ExternalInstallOptions> LoadInstallOptionsBlocking(
deserializer.Deserialize(nullptr, &error_msg);
if (!app_config) {
LOG(ERROR) << file.value() << " was not valid JSON: " << error_msg;
++error_count;
++result.error_count;
continue;
}
result.configs.push_back(
{.contents = std::move(*app_config), .file = file});
}
return result;
}
ExternalConfigParseResult result =
ParseConfig(*file_utils, dir, file, user_type, *app_config);
switch (result.type) {
struct ParsedConfigs {
std::vector<ExternalInstallOptions> options_list;
int disabled_count = 0;
int error_count = 0;
};
ParsedConfigs ParseConfigsBlocking(const base::FilePath& config_dir,
const std::string& user_type,
LoadedConfigs loaded_configs) {
ParsedConfigs result;
result.error_count = loaded_configs.error_count;
auto file_utils = g_file_utils_for_testing
? g_file_utils_for_testing->Clone()
: std::make_unique<FileUtilsWrapper>();
for (const LoadedConfig& loaded_config : loaded_configs.configs) {
ExternalConfigParseResult parse_result =
ParseConfig(*file_utils, config_dir, loaded_config.file, user_type,
loaded_config.contents);
switch (parse_result.type) {
case ExternalConfigParseResult::kEnabled:
install_options_list.push_back(std::move(result.options.value()));
result.options_list.push_back(std::move(parse_result.options.value()));
break;
case ExternalConfigParseResult::kDisabled:
++disabled_count;
++result.disabled_count;
break;
case ExternalConfigParseResult::kError:
++error_count;
++result.error_count;
break;
}
}
base::UmaHistogramCounts100(ExternalWebAppManager::kHistogramEnabledCount,
install_options_list.size());
base::UmaHistogramCounts100(ExternalWebAppManager::kHistogramDisabledCount,
disabled_count);
base::UmaHistogramCounts100(ExternalWebAppManager::kHistogramConfigErrorCount,
error_count);
return install_options_list;
}
base::FilePath DetermineLoadDir(const Profile* profile) {
base::FilePath dir;
#if defined(OS_CHROMEOS)
// As of mid 2018, only Chrome OS has default/external web apps, and
// chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS is only defined for OS_LINUX,
// which includes OS_CHROMEOS.
if (chromeos::ProfileHelper::IsPrimaryProfile(profile)) {
// For manual testing, you can change s/STANDALONE/USER/, as writing to
// "$HOME/.config/chromium/test-user/.config/chromium/External
// Extensions/web_apps" does not require root ACLs, unlike
// "/usr/share/chromium/extensions/web_apps".
if (!base::PathService::Get(chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS,
&dir)) {
LOG(ERROR) << "ExternalWebAppManager::LoadInstallOptions: "
"base::PathService::Get failed";
} else {
dir = dir.Append(kWebAppsSubDirectory);
}
}
#endif
return dir;
}
void OnExternalWebAppsSynchronized(
std::map<GURL, InstallResultCode> install_results,
std::map<GURL, bool> uninstall_results) {
RecordExternalAppInstallResultCode("Webapp.InstallResult.Default",
install_results);
}
std::vector<ExternalInstallOptions> SynchronizeAppsBlockingForTesting(
std::unique_ptr<FileUtilsWrapper> file_utils,
std::vector<std::string> app_configs,
const std::string& user_type) {
std::vector<ExternalInstallOptions> install_options_list;
for (const std::string& app_config_string : app_configs) {
base::Optional<base::Value> app_config =
base::JSONReader::Read(app_config_string);
DCHECK(app_config);
ExternalConfigParseResult result =
ParseConfig(*file_utils, base::FilePath(FILE_PATH_LITERAL("test_dir")),
base::FilePath(FILE_PATH_LITERAL("test_dir/test.json")),
user_type, *app_config);
if (result.type == ExternalConfigParseResult::kEnabled)
install_options_list.push_back(std::move(*result.options));
}
// TODO(crbug.com/1128801): Dedupe this with LoadInstallOptionsBlocking().
for (ExternalInstallOptions& options : GetPreinstalledWebApps().options)
install_options_list.push_back(std::move(options));
return install_options_list;
return result;
}
} // namespace
......@@ -186,6 +142,25 @@ const char* ExternalWebAppManager::kHistogramDisabledCount =
const char* ExternalWebAppManager::kHistogramConfigErrorCount =
"WebApp.Preinstalled.ConfigErrorCount";
void ExternalWebAppManager::SkipStartupForTesting() {
g_skip_startup_for_testing_ = true;
}
void ExternalWebAppManager::SetConfigDirForTesting(
const base::FilePath* config_dir) {
g_config_dir_for_testing = config_dir;
}
void ExternalWebAppManager::SetConfigsForTesting(
const std::vector<base::Value>* configs) {
g_configs_for_testing = configs;
}
void ExternalWebAppManager::SetFileUtilsForTesting(
const FileUtilsWrapper* file_utils) {
g_file_utils_for_testing = file_utils;
}
ExternalWebAppManager::ExternalWebAppManager(Profile* profile)
: profile_(profile) {}
......@@ -197,71 +172,135 @@ void ExternalWebAppManager::SetSubsystems(
}
void ExternalWebAppManager::Start() {
if (!g_skip_startup_for_testing_) {
LoadInstallOptions(base::BindOnce(
&ExternalWebAppManager::SynchronizeExternalInstallOptions,
weak_ptr_factory_.GetWeakPtr(),
base::BindOnce(&OnExternalWebAppsSynchronized)));
}
if (!g_skip_startup_for_testing_)
LoadAndSynchronize({});
}
// static
std::vector<ExternalInstallOptions>
ExternalWebAppManager::ReloadInstallOptionsForTesting(
std::unique_ptr<FileUtilsWrapper> file_utils,
const base::FilePath& dir,
Profile* profile) {
return LoadInstallOptionsBlocking(std::move(file_utils), dir,
apps::DetermineUserType(profile));
void ExternalWebAppManager::LoadForTesting(ConsumeInstallOptions callback) {
Load(std::move(callback));
}
void ExternalWebAppManager::LoadInstallOptions(LoadCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Do a two-part callback dance, across different TaskRunners.
//
// 1. Schedule LoadInstallOptionsBlocking to happen on a background thread, so
// that we don't block the UI thread. When that's done,
// base::PostTaskAndReplyWithResult will bounce us back to the originating
// thread (the UI thread).
//
// 2. In |callback|, forward the vector of ExternalInstallOptions on to the
// pending_app_manager_, which can only be called on the UI thread.
void ExternalWebAppManager::LoadAndSynchronizeForTesting(
SynchronizeCallback callback) {
LoadAndSynchronize(std::move(callback));
}
void ExternalWebAppManager::LoadAndSynchronize(SynchronizeCallback callback) {
Load(base::BindOnce(&ExternalWebAppManager::Synchronize,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ExternalWebAppManager::Load(ConsumeInstallOptions callback) {
if (!base::FeatureList::IsEnabled(features::kDefaultWebAppInstallation)) {
std::move(callback).Run({});
return;
}
LoadConfigs(base::BindOnce(
&ExternalWebAppManager::ParseConfigs, weak_ptr_factory_.GetWeakPtr(),
base::BindOnce(&ExternalWebAppManager::PostProcessConfigs,
weak_ptr_factory_.GetWeakPtr(), std::move(callback))));
}
void ExternalWebAppManager::LoadConfigs(ConsumeLoadedConfigs callback) {
if (g_configs_for_testing) {
LoadedConfigs loaded_configs;
for (const base::Value& config : *g_configs_for_testing) {
loaded_configs.configs.push_back(
{.contents = config.Clone(),
.file = base::FilePath(FILE_PATH_LITERAL("test.json"))});
}
std::move(callback).Run(std::move(loaded_configs));
return;
}
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(
&LoadInstallOptionsBlocking, std::make_unique<FileUtilsWrapper>(),
DetermineLoadDir(profile_), apps::DetermineUserType(profile_)),
base::BindOnce(&LoadConfigsBlocking, GetConfigDir()),
std::move(callback));
}
void ExternalWebAppManager::SkipStartupForTesting() {
g_skip_startup_for_testing_ = true;
}
void ExternalWebAppManager::SynchronizeAppsForTesting(
std::unique_ptr<FileUtilsWrapper> file_utils,
std::vector<std::string> app_configs,
PendingAppManager::SynchronizeCallback callback) {
void ExternalWebAppManager::ParseConfigs(ConsumeParsedConfigs callback,
LoadedConfigs loaded_configs) {
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&SynchronizeAppsBlockingForTesting, std::move(file_utils),
std::move(app_configs), apps::DetermineUserType(profile_)),
base::BindOnce(&ExternalWebAppManager::SynchronizeExternalInstallOptions,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
base::BindOnce(&ParseConfigsBlocking, GetConfigDir(),
apps::DetermineUserType(profile_),
std::move(loaded_configs)),
std::move(callback));
}
void ExternalWebAppManager::SynchronizeExternalInstallOptions(
void ExternalWebAppManager::PostProcessConfigs(ConsumeInstallOptions callback,
ParsedConfigs parsed_configs) {
PreinstalledWebApps preinstalled_web_apps = GetPreinstalledWebApps();
for (ExternalInstallOptions& options : preinstalled_web_apps.options)
parsed_configs.options_list.push_back(std::move(options));
parsed_configs.disabled_count += preinstalled_web_apps.disabled_count;
base::UmaHistogramCounts100(ExternalWebAppManager::kHistogramEnabledCount,
parsed_configs.options_list.size());
base::UmaHistogramCounts100(ExternalWebAppManager::kHistogramDisabledCount,
parsed_configs.disabled_count);
base::UmaHistogramCounts100(ExternalWebAppManager::kHistogramConfigErrorCount,
parsed_configs.error_count);
std::move(callback).Run(std::move(parsed_configs.options_list));
}
void ExternalWebAppManager::Synchronize(
PendingAppManager::SynchronizeCallback callback,
std::vector<ExternalInstallOptions> desired_apps_install_options) {
DCHECK(pending_app_manager_);
pending_app_manager_->SynchronizeInstalledApps(
std::move(desired_apps_install_options),
ExternalInstallSource::kExternalDefault, std::move(callback));
ExternalInstallSource::kExternalDefault,
base::BindOnce(&ExternalWebAppManager::OnExternalWebAppsSynchronized,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ExternalWebAppManager::OnExternalWebAppsSynchronized(
PendingAppManager::SynchronizeCallback callback,
std::map<GURL, InstallResultCode> install_results,
std::map<GURL, bool> uninstall_results) {
RecordExternalAppInstallResultCode("Webapp.InstallResult.Default",
install_results);
if (callback) {
std::move(callback).Run(std::move(install_results),
std::move(uninstall_results));
}
}
base::FilePath ExternalWebAppManager::GetConfigDir() {
base::FilePath dir;
#if defined(OS_CHROMEOS)
// As of mid 2018, only Chrome OS has default/external web apps, and
// chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS is only defined for OS_LINUX,
// which includes OS_CHROMEOS.
if (chromeos::ProfileHelper::IsPrimaryProfile(profile_)) {
if (g_config_dir_for_testing) {
dir = *g_config_dir_for_testing;
} else {
// For manual testing, you can change s/STANDALONE/USER/, as writing to
// "$HOME/.config/chromium/test-user/.config/chromium/External
// Extensions/web_apps" does not require root ACLs, unlike
// "/usr/share/chromium/extensions/web_apps".
if (!base::PathService::Get(chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS,
&dir)) {
LOG(ERROR) << "base::PathService::Get failed";
} else {
dir = dir.Append(kWebAppsSubDirectory);
}
}
}
#endif
return dir;
}
} // namespace web_app
......@@ -10,6 +10,7 @@
#include "base/callback_forward.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/values.h"
#include "chrome/browser/web_applications/components/external_install_options.h"
#include "chrome/browser/web_applications/components/pending_app_manager.h"
#include "chrome/browser/web_applications/file_utils_wrapper.h"
......@@ -22,47 +23,65 @@ class Profile;
namespace web_app {
namespace {
struct LoadedConfigs;
struct ParsedConfigs;
} // namespace
class PendingAppManager;
// Installs web apps to be preinstalled on the device (AKA default apps) during
// start up. Will keep the apps installed on the device in sync with the set of
// apps configured for preinstall, adding or removing as necessary. Works very
// similar to WebAppPolicyManager.
class ExternalWebAppManager {
public:
using ConsumeLoadedConfigs = base::OnceCallback<void(LoadedConfigs)>;
using ConsumeParsedConfigs = base::OnceCallback<void(ParsedConfigs)>;
using ConsumeInstallOptions =
base::OnceCallback<void(std::vector<ExternalInstallOptions>)>;
using SynchronizeCallback = PendingAppManager::SynchronizeCallback;
static const char* kHistogramEnabledCount;
static const char* kHistogramDisabledCount;
static const char* kHistogramConfigErrorCount;
static void SkipStartupForTesting();
static void SetConfigDirForTesting(const base::FilePath* config_dir);
static void SetConfigsForTesting(const std::vector<base::Value>* configs);
static void SetFileUtilsForTesting(const FileUtilsWrapper* file_utils);
explicit ExternalWebAppManager(Profile* profile);
~ExternalWebAppManager();
void SetSubsystems(PendingAppManager* pending_app_manager);
// Loads the preinstalled app configs and synchronizes them with the device's
// installed apps.
void Start();
// Scans the given directory (non-recursively) for *.json files that define
// "external web apps", the Web App analogs of "external extensions",
// described at https://developer.chrome.com/apps/external_extensions
//
// This function performs file I/O, and must not be scheduled on UI threads.
static std::vector<ExternalInstallOptions> ReloadInstallOptionsForTesting(
std::unique_ptr<FileUtilsWrapper> file_utils,
const base::FilePath& dir,
Profile* profile);
using LoadCallback =
base::OnceCallback<void(std::vector<ExternalInstallOptions>)>;
void LoadInstallOptions(LoadCallback callback);
void LoadAndSynchronizeForTesting(SynchronizeCallback callback);
static void SkipStartupForTesting();
void SynchronizeAppsForTesting(
std::unique_ptr<FileUtilsWrapper> file_utils,
std::vector<std::string> app_configs,
PendingAppManager::SynchronizeCallback callback);
void LoadForTesting(ConsumeInstallOptions callback);
private:
void SynchronizeExternalInstallOptions(
void LoadAndSynchronize(SynchronizeCallback callback);
void Load(ConsumeInstallOptions callback);
void LoadConfigs(ConsumeLoadedConfigs callback);
void ParseConfigs(ConsumeParsedConfigs callback,
LoadedConfigs loaded_configs);
void PostProcessConfigs(ConsumeInstallOptions callback,
ParsedConfigs parsed_configs);
void Synchronize(SynchronizeCallback callback,
std::vector<ExternalInstallOptions>);
void OnExternalWebAppsSynchronized(
PendingAppManager::SynchronizeCallback callback,
std::vector<ExternalInstallOptions>);
std::map<GURL, InstallResultCode> install_results,
std::map<GURL, bool> uninstall_results);
base::FilePath GetConfigDir();
PendingAppManager* pending_app_manager_ = nullptr;
Profile* const profile_;
......
......@@ -5,6 +5,7 @@
#include "chrome/browser/web_applications/external_web_app_manager.h"
#include "base/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/path_service.h"
#include "base/strings/string_util.h"
#include "base/test/bind_test_util.h"
......@@ -41,34 +42,48 @@ class ExternalWebAppManagerBrowserTest
// Mocks "icon.png" as available in the config's directory.
InstallResultCode SyncDefaultAppConfig(const GURL& install_url,
std::string app_config_string) {
base::FilePath test_config_dir(FILE_PATH_LITERAL("test_dir"));
ExternalWebAppManager::SetConfigDirForTesting(&test_config_dir);
base::FilePath source_root_dir;
CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &source_root_dir));
base::FilePath test_icon_path =
source_root_dir.Append(GetChromeTestDataDir())
.AppendASCII("web_apps/blue-192.png");
TestFileUtils file_utils(
{{base::FilePath(FILE_PATH_LITERAL("test_dir/icon.png")),
test_icon_path}});
ExternalWebAppManager::SetFileUtilsForTesting(&file_utils);
std::vector<base::Value> app_configs;
app_configs.push_back(*base::JSONReader::Read(app_config_string));
ExternalWebAppManager::SetConfigsForTesting(&app_configs);
base::Optional<InstallResultCode> code;
base::RunLoop sync_run_loop;
WebAppProvider::Get(browser()->profile())
->external_web_app_manager_for_testing()
.SynchronizeAppsForTesting(
TestFileUtils::Create(
{{base::FilePath(FILE_PATH_LITERAL("test_dir/icon.png")),
test_icon_path}}),
{app_config_string},
base::BindLambdaForTesting(
[&](std::map<GURL, InstallResultCode> install_results,
std::map<GURL, bool> uninstall_results) {
code = install_results.at(install_url);
sync_run_loop.Quit();
}));
.LoadAndSynchronizeForTesting(base::BindLambdaForTesting(
[&](std::map<GURL, InstallResultCode> install_results,
std::map<GURL, bool> uninstall_results) {
code = install_results.at(install_url);
sync_run_loop.Quit();
}));
sync_run_loop.Run();
ExternalWebAppManager::SetConfigDirForTesting(nullptr);
ExternalWebAppManager::SetFileUtilsForTesting(nullptr);
ExternalWebAppManager::SetConfigsForTesting(nullptr);
return *code;
}
~ExternalWebAppManagerBrowserTest() override = default;
};
// This JSON config functionality is only available on Chrome OS.
#if defined(OS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(ExternalWebAppManagerBrowserTest, UninstallAndReplace) {
ASSERT_TRUE(embedded_test_server()->Start());
Profile* profile = browser()->profile();
......@@ -102,10 +117,6 @@ IN_PROC_BROWSER_TEST_F(ExternalWebAppManagerBrowserTest, UninstallAndReplace) {
EXPECT_EQ(app, uninstalled_app.get());
}
// TODO(crbug.com/1119710): Loading icon.png fails on Windows.
// This JSON config functionality is only used on Chrome OS.
#if !defined(OS_WIN)
// Check that offline fallback installs work offline.
IN_PROC_BROWSER_TEST_F(ExternalWebAppManagerBrowserTest,
OfflineFallbackManifestSiteOffline) {
......@@ -286,6 +297,6 @@ IN_PROC_BROWSER_TEST_F(ExternalWebAppManagerBrowserTest,
SK_ColorBLUE);
}
#endif // !defined(OS_WIN)
#endif // defined(OS_CHROMEOS)
} // namespace web_app
......@@ -15,6 +15,7 @@
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/test/bind_test_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_path_override.h"
......@@ -39,11 +40,10 @@ namespace web_app {
namespace {
constexpr char kGoodJsonTestDir[] = "good_json";
constexpr char kWebAppDefaultApps[] = "web_app_default_apps";
constexpr char kUserTypesTestDir[] = "user_types";
#if defined(OS_CHROMEOS)
constexpr char kGoodJsonTestDir[] = "good_json";
constexpr char kAppAllUrl[] = "https://www.google.com/all";
constexpr char kAppChildUrl[] = "https://www.google.com/child";
constexpr char kAppGuestUrl[] = "https://www.google.com/guest";
......@@ -52,20 +52,6 @@ constexpr char kAppSupervisedUrl[] = "https://www.google.com/supervised";
constexpr char kAppUnmanagedUrl[] = "https://www.google.com/unmanaged";
#endif
// Returns the chrome/test/data/web_app_default_apps/sub_dir directory that
// holds the *.json data files from which ScanDirForExternalWebAppsForTesting
// should extract URLs from.
static base::FilePath GetTestDir(const std::string& sub_dir) {
base::FilePath dir;
if (!base::PathService::Get(chrome::DIR_TEST_DATA, &dir)) {
ADD_FAILURE()
<< "base::PathService::Get could not resolve chrome::DIR_TEST_DATA";
}
return dir.AppendASCII(kWebAppDefaultApps).AppendASCII(sub_dir);
}
using InstallOptionsList = std::vector<ExternalInstallOptions>;
} // namespace
class ExternalWebAppManagerTest : public testing::Test {
......@@ -90,35 +76,44 @@ class ExternalWebAppManagerTest : public testing::Test {
}
protected:
// Helper that makes blocking call to
// |ExternalWebAppManager::LoadInstallOptions| and returns read app infos.
static InstallOptionsList LoadApps(Profile* profile,
const base::FilePath& test_dir) {
std::vector<ExternalInstallOptions> LoadApps(const char* test_dir,
Profile* profile = nullptr) {
std::unique_ptr<TestingProfile> testing_profile;
if (!profile) {
#if defined(OS_CHROMEOS)
base::ScopedPathOverride path_override(
chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS, test_dir);
testing_profile = CreateProfileAndLogin();
profile = testing_profile.get();
#else
NOTREACHED();
#endif
}
// Uses the chrome/test/data/web_app_default_apps/test_dir directory that
// holds the *.json data files from which tests should parse as app configs.
base::FilePath config_dir;
if (!base::PathService::Get(chrome::DIR_TEST_DATA, &config_dir)) {
ADD_FAILURE()
<< "base::PathService::Get could not resolve chrome::DIR_TEST_DATA";
}
config_dir =
config_dir.AppendASCII("web_app_default_apps").AppendASCII(test_dir);
ExternalWebAppManager::SetConfigDirForTesting(&config_dir);
auto external_web_app_manager =
std::make_unique<ExternalWebAppManager>(profile);
InstallOptionsList result;
std::vector<ExternalInstallOptions> result;
base::RunLoop run_loop;
external_web_app_manager->LoadInstallOptions(base::BindOnce(
[](base::RunLoop* run_loop, InstallOptionsList* result,
InstallOptionsList install_options_list) {
*result = install_options_list;
run_loop->Quit();
},
&run_loop, &result));
external_web_app_manager->LoadForTesting(base::BindLambdaForTesting(
[&](std::vector<ExternalInstallOptions> install_options_list) {
result = install_options_list;
run_loop.Quit();
}));
run_loop.Run();
return result;
}
std::vector<ExternalInstallOptions> ReloadInstallOptions(
const std::string& dir) {
return ExternalWebAppManager::ReloadInstallOptionsForTesting(
std::make_unique<FileUtilsWrapper>(), GetTestDir(dir),
CreateProfile().get());
ExternalWebAppManager::SetConfigDirForTesting(nullptr);
return result;
}
// Helper that creates simple test profile.
......@@ -127,6 +122,7 @@ class ExternalWebAppManagerTest : public testing::Test {
return profile_builder.Build();
}
#if defined(OS_CHROMEOS)
// Helper that creates simple test guest profile.
std::unique_ptr<TestingProfile> CreateGuestProfile() {
TestingProfile::Builder profile_builder;
......@@ -134,7 +130,6 @@ class ExternalWebAppManagerTest : public testing::Test {
return profile_builder.Build();
}
#if defined(OS_CHROMEOS)
// Helper that creates simple test profile and logs it into user manager.
// This makes profile appears as a primary profile in ChromeOS.
std::unique_ptr<TestingProfile> CreateProfileAndLogin() {
......@@ -156,8 +151,7 @@ class ExternalWebAppManagerTest : public testing::Test {
}
void VerifySetOfApps(Profile* profile, const std::set<GURL>& expectations) {
const auto install_options_list =
LoadApps(profile, GetTestDir(kUserTypesTestDir));
const auto install_options_list = LoadApps(kUserTypesTestDir, profile);
ASSERT_EQ(expectations.size(), install_options_list.size());
for (const auto& install_options : install_options_list)
ASSERT_EQ(1u, expectations.count(install_options.install_url));
......@@ -182,7 +176,7 @@ class ExternalWebAppManagerTest : public testing::Test {
user_manager::UserManager::Get());
}
// To supprot primary/non-primary users.
// To support primary/non-primary users.
std::unique_ptr<user_manager::ScopedUserManager> user_manager_enabler_;
#endif
......@@ -192,8 +186,10 @@ class ExternalWebAppManagerTest : public testing::Test {
DISALLOW_COPY_AND_ASSIGN(ExternalWebAppManagerTest);
};
// Only Chrome OS parses config files.
#if defined(OS_CHROMEOS)
TEST_F(ExternalWebAppManagerTest, GoodJson) {
const auto install_options_list = ReloadInstallOptions(kGoodJsonTestDir);
const auto install_options_list = LoadApps(kGoodJsonTestDir);
// The good_json directory contains two good JSON files:
// chrome_platform_status.json and google_io_2016.json.
......@@ -234,7 +230,7 @@ TEST_F(ExternalWebAppManagerTest, GoodJson) {
}
TEST_F(ExternalWebAppManagerTest, BadJson) {
const auto app_infos = ReloadInstallOptions("bad_json");
const auto app_infos = LoadApps("bad_json");
// The bad_json directory contains one (malformed) JSON file.
EXPECT_EQ(0u, app_infos.size());
......@@ -242,7 +238,7 @@ TEST_F(ExternalWebAppManagerTest, BadJson) {
}
TEST_F(ExternalWebAppManagerTest, TxtButNoJson) {
const auto app_infos = ReloadInstallOptions("txt_but_no_json");
const auto app_infos = LoadApps("txt_but_no_json");
// The txt_but_no_json directory contains one file, and the contents of that
// file is valid JSON, but that file's name does not end with ".json".
......@@ -251,7 +247,7 @@ TEST_F(ExternalWebAppManagerTest, TxtButNoJson) {
}
TEST_F(ExternalWebAppManagerTest, MixedJson) {
const auto app_infos = ReloadInstallOptions("mixed_json");
const auto app_infos = LoadApps("mixed_json");
// The mixed_json directory contains one empty JSON file, one malformed JSON
// file and one good JSON file. ScanDirForExternalWebAppsForTesting should
......@@ -265,7 +261,7 @@ TEST_F(ExternalWebAppManagerTest, MixedJson) {
}
TEST_F(ExternalWebAppManagerTest, MissingAppUrl) {
const auto app_infos = ReloadInstallOptions("missing_app_url");
const auto app_infos = LoadApps("missing_app_url");
// The missing_app_url directory contains one JSON file which is correct
// except for a missing "app_url" field.
......@@ -274,7 +270,7 @@ TEST_F(ExternalWebAppManagerTest, MissingAppUrl) {
}
TEST_F(ExternalWebAppManagerTest, EmptyAppUrl) {
const auto app_infos = ReloadInstallOptions("empty_app_url");
const auto app_infos = LoadApps("empty_app_url");
// The empty_app_url directory contains one JSON file which is correct
// except for an empty "app_url" field.
......@@ -283,7 +279,7 @@ TEST_F(ExternalWebAppManagerTest, EmptyAppUrl) {
}
TEST_F(ExternalWebAppManagerTest, InvalidAppUrl) {
const auto app_infos = ReloadInstallOptions("invalid_app_url");
const auto app_infos = LoadApps("invalid_app_url");
// The invalid_app_url directory contains one JSON file which is correct
// except for an invalid "app_url" field.
......@@ -292,7 +288,7 @@ TEST_F(ExternalWebAppManagerTest, InvalidAppUrl) {
}
TEST_F(ExternalWebAppManagerTest, TrueHideFromUser) {
const auto app_infos = ReloadInstallOptions("true_hide_from_user");
const auto app_infos = LoadApps("true_hide_from_user");
EXPECT_EQ(1u, app_infos.size());
const auto& app = app_infos[0];
......@@ -303,7 +299,7 @@ TEST_F(ExternalWebAppManagerTest, TrueHideFromUser) {
}
TEST_F(ExternalWebAppManagerTest, InvalidHideFromUser) {
const auto app_infos = ReloadInstallOptions("invalid_hide_from_user");
const auto app_infos = LoadApps("invalid_hide_from_user");
// The invalid_hide_from_user directory contains on JSON file which is correct
// except for an invalid "hide_from_user" field.
......@@ -312,7 +308,7 @@ TEST_F(ExternalWebAppManagerTest, InvalidHideFromUser) {
}
TEST_F(ExternalWebAppManagerTest, InvalidCreateShortcuts) {
const auto app_infos = ReloadInstallOptions("invalid_create_shortcuts");
const auto app_infos = LoadApps("invalid_create_shortcuts");
// The invalid_create_shortcuts directory contains one JSON file which is
// correct except for an invalid "create_shortcuts" field.
......@@ -321,7 +317,7 @@ TEST_F(ExternalWebAppManagerTest, InvalidCreateShortcuts) {
}
TEST_F(ExternalWebAppManagerTest, MissingLaunchContainer) {
const auto app_infos = ReloadInstallOptions("missing_launch_container");
const auto app_infos = LoadApps("missing_launch_container");
// The missing_launch_container directory contains one JSON file which is
// correct except for a missing "launch_container" field.
......@@ -330,7 +326,7 @@ TEST_F(ExternalWebAppManagerTest, MissingLaunchContainer) {
}
TEST_F(ExternalWebAppManagerTest, InvalidLaunchContainer) {
const auto app_infos = ReloadInstallOptions("invalid_launch_container");
const auto app_infos = LoadApps("invalid_launch_container");
// The invalid_launch_container directory contains one JSON file which is
// correct except for an invalid "launch_container" field.
......@@ -339,7 +335,7 @@ TEST_F(ExternalWebAppManagerTest, InvalidLaunchContainer) {
}
TEST_F(ExternalWebAppManagerTest, InvalidUninstallAndReplace) {
const auto app_infos = ReloadInstallOptions("invalid_uninstall_and_replace");
const auto app_infos = LoadApps("invalid_uninstall_and_replace");
// The invalid_uninstall_and_replace directory contains 2 JSON files which are
// correct except for invalid "uninstall_and_replace" fields.
......@@ -351,7 +347,7 @@ TEST_F(ExternalWebAppManagerTest, DefaultWebAppInstallDisabled) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndDisableFeature(
features::kDefaultWebAppInstallation);
const auto app_infos = ReloadInstallOptions(kGoodJsonTestDir);
const auto app_infos = LoadApps(kGoodJsonTestDir);
EXPECT_EQ(0u, app_infos.size());
histograms_.ExpectTotalCount(
......@@ -366,7 +362,7 @@ TEST_F(ExternalWebAppManagerTest, EnabledByFinch) {
base::AutoReset<bool> testing_scope =
SetExternalAppInstallFeatureAlwaysEnabledForTesting();
const auto app_infos = ReloadInstallOptions("enabled_by_finch");
const auto app_infos = LoadApps("enabled_by_finch");
// The enabled_by_finch directory contains two JSON file containing apps
// that have field trials. As the matching feature is enabled, they should be
......@@ -376,7 +372,7 @@ TEST_F(ExternalWebAppManagerTest, EnabledByFinch) {
}
TEST_F(ExternalWebAppManagerTest, NotEnabledByFinch) {
const auto app_infos = ReloadInstallOptions("enabled_by_finch");
const auto app_infos = LoadApps("enabled_by_finch");
// The enabled_by_finch directory contains two JSON file containing apps
// that have field trials. As the matching feature isn't enabled, they should
......@@ -385,7 +381,6 @@ TEST_F(ExternalWebAppManagerTest, NotEnabledByFinch) {
ExpectHistograms(/*enabled=*/0, /*disabled=*/2, /*errors=*/0);
}
#if defined(OS_CHROMEOS)
TEST_F(ExternalWebAppManagerTest, ChildUser) {
const auto profile = CreateProfileAndLogin();
profile->SetSupervisedUserId(supervised_users::kChildAccountSUID);
......@@ -415,15 +410,13 @@ TEST_F(ExternalWebAppManagerTest, UnmanagedUser) {
}
TEST_F(ExternalWebAppManagerTest, NonPrimaryProfile) {
EXPECT_TRUE(
LoadApps(CreateProfile().get(), GetTestDir(kUserTypesTestDir)).empty());
EXPECT_TRUE(LoadApps(kUserTypesTestDir, CreateProfile().get()).empty());
}
#else
#else // defined(OS_CHROMEOS)
// No app is expected for non-ChromeOS builds.
TEST_F(ExternalWebAppManagerTest, NoApp) {
EXPECT_TRUE(
LoadApps(CreateProfile().get(), GetTestDir(kUserTypesTestDir)).empty());
EXPECT_TRUE(LoadApps(kUserTypesTestDir, CreateProfile().get()).empty());
}
#endif
#endif // defined(OS_CHROMEOS)
} // namespace web_app
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "base/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/test/bind_test_util.h"
......@@ -176,9 +177,9 @@ class ExternalWebAppMigrationBrowserTest : public InProcessBrowserTest {
run_loop.Quit();
});
std::vector<std::string> configs;
std::vector<base::Value> app_configs;
if (pass_config) {
std::string external_web_app_config = base::ReplaceStringPlaceholders(
std::string app_config_string = base::ReplaceStringPlaceholders(
R"({
"app_url": "$1",
"launch_container": "window",
......@@ -187,15 +188,17 @@ class ExternalWebAppMigrationBrowserTest : public InProcessBrowserTest {
"uninstall_and_replace": ["$3"]
})",
{GetWebAppUrl().spec(), kMigrationFlag, kExtensionId}, nullptr);
configs.push_back(std::move(external_web_app_config));
app_configs.push_back(*base::JSONReader::Read(app_config_string));
}
ExternalWebAppManager::SetConfigsForTesting(&app_configs);
WebAppProvider::Get(profile())
->external_web_app_manager_for_testing()
.SynchronizeAppsForTesting(std::make_unique<FileUtilsWrapper>(),
configs, std::move(callback));
.LoadAndSynchronizeForTesting(std::move(callback));
run_loop.Run();
ExternalWebAppManager::SetConfigsForTesting(nullptr);
}
bool IsWebAppInstalled() {
......@@ -444,15 +447,12 @@ IN_PROC_BROWSER_TEST_F(ExternalWebAppMigrationBrowserTest,
EXPECT_FALSE(IsWebAppInstalled());
EXPECT_TRUE(IsExtensionAppInstalled());
// TODO(crbug.com/1128801): Use the normal LoadInstallOptions() code path
// such that metrics are recorded.
// histograms.ExpectUniqueSample(
// ExternalWebAppManager::kHistogramEnabledCount,
// 0, 1);
// histograms.ExpectUniqueSample(
// ExternalWebAppManager::kHistogramDisabledCount, 1, 1);
// histograms.ExpectUniqueSample(
// ExternalWebAppManager::kHistogramConfigErrorCount, 0, 1);
histograms.ExpectUniqueSample(ExternalWebAppManager::kHistogramEnabledCount,
0, 1);
histograms.ExpectUniqueSample(
ExternalWebAppManager::kHistogramDisabledCount, 1, 1);
histograms.ExpectUniqueSample(
ExternalWebAppManager::kHistogramConfigErrorCount, 0, 1);
}
// Migrate extension app to web app.
......@@ -481,14 +481,12 @@ IN_PROC_BROWSER_TEST_F(ExternalWebAppMigrationBrowserTest,
EXPECT_EQ(uninstalled_app->id(), kExtensionId);
EXPECT_FALSE(IsExtensionAppInstalled());
// TODO(crbug.com/1128801): Use the normal LoadInstallOptions() code path
// such that metrics are recorded.
// histograms.ExpectUniqueSample(
// ExternalWebAppManager::kHistogramEnabledCount, 1, 1);
// histograms.ExpectUniqueSample(
// ExternalWebAppManager::kHistogramDisabledCount, 0, 1);
// histograms.ExpectUniqueSample(
// ExternalWebAppManager::kHistogramConfigErrorCount, 0, 1);
histograms.ExpectUniqueSample(
ExternalWebAppManager::kHistogramEnabledCount, 1, 1);
histograms.ExpectUniqueSample(
ExternalWebAppManager::kHistogramDisabledCount, 0, 1);
histograms.ExpectUniqueSample(
ExternalWebAppManager::kHistogramConfigErrorCount, 0, 1);
}
}
}
......
......@@ -9,7 +9,7 @@
namespace web_app {
std::unique_ptr<FileUtilsWrapper> FileUtilsWrapper::Clone() {
std::unique_ptr<FileUtilsWrapper> FileUtilsWrapper::Clone() const {
return std::make_unique<FileUtilsWrapper>();
}
......
......@@ -34,7 +34,7 @@ class FileUtilsWrapper {
virtual ~FileUtilsWrapper() = default;
// Create a copy to use in IO task.
virtual std::unique_ptr<FileUtilsWrapper> Clone();
virtual std::unique_ptr<FileUtilsWrapper> Clone() const;
bool PathExists(const base::FilePath& path);
......
......@@ -23,7 +23,7 @@ TestFileUtils::TestFileUtils(const TestFileUtils&) = default;
TestFileUtils::~TestFileUtils() = default;
std::unique_ptr<FileUtilsWrapper> TestFileUtils::Clone() {
std::unique_ptr<FileUtilsWrapper> TestFileUtils::Clone() const {
return std::make_unique<TestFileUtils>(*this);
}
......
......@@ -28,7 +28,7 @@ class TestFileUtils : public FileUtilsWrapper {
~TestFileUtils() override;
// FileUtilsWrapper:
std::unique_ptr<FileUtilsWrapper> Clone() override;
std::unique_ptr<FileUtilsWrapper> Clone() const override;
int WriteFile(const base::FilePath& filename,
const char* data,
int size) override;
......
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