blob: 2ec986689a8433c759358ad6738815947018fd92 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/extension_platform_apitest.h"
#include <stddef.h>
#include <memory>
#include <string_view>
#include <utility>
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/path_service.h"
#include "base/test/scoped_run_loop_timeout.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/api_test_util.h"
#include "content/public/common/content_switches.h"
#include "extensions/browser/api/test/test_api.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_paths.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/feature_switch.h"
#include "extensions/test/result_catcher.h"
#include "net/base/filename_util.h"
namespace extensions {
namespace {
const char kTestCustomArg[] = "customArg";
const char kTestDataDirectory[] = "testDataDirectory";
const char kEmbeddedTestServerPort[] = "testServer.port";
} // namespace
ExtensionPlatformApiTest::ExtensionPlatformApiTest(ContextType context_type)
: ExtensionPlatformBrowserTest(context_type) {}
ExtensionPlatformApiTest::~ExtensionPlatformApiTest() = default;
void ExtensionPlatformApiTest::SetUpOnMainThread() {
ExtensionPlatformBrowserTest::SetUpOnMainThread();
test_data_dir_ = test_data_dir_.AppendASCII("api_test");
base::PathService::Get(DIR_TEST_DATA, &shared_test_data_dir_);
shared_test_data_dir_ = shared_test_data_dir_.AppendASCII("api_test");
DCHECK(!test_config_.get()) << "Previous test did not clear config state.";
test_config_ = std::make_unique<base::Value::Dict>();
test_config_->Set(kTestDataDirectory,
net::FilePathToFileURL(test_data_dir_).spec());
if (embedded_test_server()->Started()) {
// InitializeEmbeddedTestServer was called before |test_config_| was set.
// Set the missing port key.
test_config_->SetByDottedPath(kEmbeddedTestServerPort,
embedded_test_server()->port());
}
TestGetConfigFunction::set_test_config_state(test_config_.get());
}
void ExtensionPlatformApiTest::TearDownOnMainThread() {
ExtensionPlatformBrowserTest::TearDownOnMainThread();
TestGetConfigFunction::set_test_config_state(nullptr);
test_config_.reset();
}
bool ExtensionPlatformApiTest::RunExtensionTest(const char* extension_name) {
return RunExtensionTest(extension_name, {}, {});
}
bool ExtensionPlatformApiTest::RunExtensionTest(const char* extension_name,
const RunOptions& run_options) {
return RunExtensionTest(extension_name, run_options, {});
}
bool ExtensionPlatformApiTest::RunExtensionTest(
const char* extension_name,
const RunOptions& run_options,
const LoadOptions& load_options) {
const base::FilePath& root_path = run_options.use_extensions_root_dir
? shared_test_data_dir_
: test_data_dir_;
base::FilePath extension_path = root_path.AppendASCII(extension_name);
return RunExtensionTest(extension_path, run_options, load_options);
}
bool ExtensionPlatformApiTest::RunExtensionTest(
const base::FilePath& extension_path,
const RunOptions& run_options,
const LoadOptions& load_options) {
// Do some sanity checks for options that are mutually exclusive or
// only valid with other options.
CHECK(!(run_options.extension_url && run_options.page_url))
<< "'extension_url' and 'page_url' are mutually exclusive.";
CHECK(!run_options.open_in_incognito || run_options.page_url ||
run_options.extension_url)
<< "'open_in_incognito' is only allowed if specifiying 'page_url'";
CHECK(!(run_options.launch_as_platform_app && run_options.page_url))
<< "'launch_as_platform_app' and 'page_url' are mutually exclusive.";
if (run_options.custom_arg) {
SetCustomArg(run_options.custom_arg);
}
ResultCatcher catcher;
const Extension* extension = LoadExtension(extension_path, load_options);
if (!extension) {
message_ = "Failed to load extension.";
return false;
}
GURL url_to_open;
if (run_options.page_url) {
url_to_open = GURL(run_options.page_url);
DCHECK(url_to_open.has_scheme() && url_to_open.has_host());
// Note: We use is_valid() here in the expectation that the provided url
// may lack a scheme & host and thus be a relative url within the loaded
// extension.
// TODO(crbug.com/40210201): Update callers passing relative paths
// for page URLs to instead use extension_url.
if (!url_to_open.is_valid()) {
url_to_open = extension->GetResourceURL(run_options.page_url);
}
} else if (run_options.extension_url) {
DCHECK(!url_to_open.has_scheme() && !url_to_open.has_host());
url_to_open = extension->GetResourceURL(run_options.extension_url);
}
{
base::test::ScopedRunLoopTimeout timeout(
FROM_HERE, std::nullopt,
base::BindRepeating(
[](const base::FilePath& extension_path) {
return "GetNextResult timeout while RunExtensionTest: " +
extension_path.MaybeAsASCII();
},
extension_path));
if (!catcher.GetNextResult()) {
message_ = catcher.message();
return false;
}
}
return true;
}
void ExtensionPlatformApiTest::SetCustomArg(std::string_view custom_arg) {
test_config_->Set(kTestCustomArg, base::Value(custom_arg));
}
const Extension* ExtensionPlatformApiTest::GetSingleLoadedExtension() {
return api_test_util::GetSingleLoadedExtension(profile(), message_);
}
void ExtensionPlatformApiTest::SetUpCommandLine(
base::CommandLine* command_line) {
ExtensionPlatformBrowserTest::SetUpCommandLine(command_line);
// Backgrounded renderer processes run at a lower priority, causing the
// tests to take more time to complete. Disable backgrounding so that the
// tests don't time out.
command_line->AppendSwitch(::switches::kDisableRendererBackgrounding);
}
bool ExtensionPlatformApiTest::StartEmbeddedTestServer() {
if (!InitializeEmbeddedTestServer()) {
return false;
}
EmbeddedTestServerAcceptConnections();
return true;
}
bool ExtensionPlatformApiTest::InitializeEmbeddedTestServer() {
if (!embedded_test_server()->InitializeAndListen()) {
return false;
}
// Build a dictionary of values that tests can use to build URLs that
// access the test server and local file system. Tests can see these values
// using the extension API function chrome.test.getConfig().
if (test_config_) {
test_config_->SetByDottedPath(kEmbeddedTestServerPort,
embedded_test_server()->port());
}
// else SetUpOnMainThread has not been called yet. Possibly because the
// caller needs a valid port in an overridden SetUpCommandLine method.
return true;
}
void ExtensionPlatformApiTest::EmbeddedTestServerAcceptConnections() {
embedded_test_server()->StartAcceptingConnections();
}
void ExtensionPlatformApiTest::UseHttpsTestServer() {
https_test_server_ = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTPS);
https_test_server_.get()->AddDefaultHandlers(
base::FilePath(FILE_PATH_LITERAL("chrome/test/data")));
https_test_server_.get()->SetSSLConfig(
net::EmbeddedTestServer::CERT_TEST_NAMES);
}
} // namespace extensions