blob: 7149fe83036fd6a65cd763ae28a70fbc2fe78115 [file] [log] [blame]
// Copyright 2015 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 <stddef.h>
#include <stdint.h>
#include <memory>
#include <utility>
#include "base/base_switches.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/path_service.h"
#include "base/process/process.h"
#include "base/process/process_handle.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/token.h"
#include "build/build_config.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/platform/platform_channel.h"
#include "mojo/public/cpp/platform/platform_handle.h"
#include "mojo/public/cpp/system/invitation.h"
#include "services/service_manager/public/cpp/binder_registry.h"
#include "services/service_manager/public/cpp/constants.h"
#include "services/service_manager/public/cpp/service.h"
#include "services/service_manager/public/cpp/service_binding.h"
#include "services/service_manager/public/cpp/test/test_service_manager.h"
#include "services/service_manager/public/mojom/service_manager.mojom.h"
#include "services/service_manager/service_process_launcher.h"
#include "services/service_manager/tests/service_manager/service_manager.test-mojom.h"
#include "services/service_manager/tests/service_manager/test_manifests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace service_manager {
namespace {
void OnServiceStartedCallback(int* start_count,
std::string* service_name,
base::OnceClosure continuation,
const service_manager::Identity& identity) {
(*start_count)++;
*service_name = identity.name();
std::move(continuation).Run();
}
void OnServiceFailedToStartCallback(bool* run,
base::OnceClosure continuation,
const service_manager::Identity& identity) {
*run = true;
std::move(continuation).Run();
}
void OnServicePIDReceivedCallback(std::string* service_name,
uint32_t* service_pid,
base::OnceClosure continuation,
const service_manager::Identity& identity,
uint32_t pid) {
*service_name = identity.name();
*service_pid = pid;
std::move(continuation).Run();
}
class TestService : public Service, public test::mojom::CreateInstanceTest {
public:
explicit TestService(mojom::ServiceRequest request)
: service_binding_(this, std::move(request)) {
registry_.AddInterface<test::mojom::CreateInstanceTest>(
base::BindRepeating(&TestService::Create, base::Unretained(this)));
}
~TestService() override = default;
const Identity& target_identity() const { return target_identity_; }
void WaitForTargetIdentityCall() {
wait_for_target_identity_loop_ = std::make_unique<base::RunLoop>();
wait_for_target_identity_loop_->Run();
}
Connector* connector() { return service_binding_.GetConnector(); }
private:
// Service:
void OnBindInterface(const BindSourceInfo& source_info,
const std::string& interface_name,
mojo::ScopedMessagePipeHandle interface_pipe) override {
registry_.BindInterface(interface_name, std::move(interface_pipe));
}
void Create(test::mojom::CreateInstanceTestRequest request) {
binding_.Bind(std::move(request));
}
// test::mojom::CreateInstanceTest:
void SetTargetIdentity(const service_manager::Identity& identity) override {
target_identity_ = identity;
if (!wait_for_target_identity_loop_)
LOG(ERROR) << "SetTargetIdentity call received when not waiting for it.";
else
wait_for_target_identity_loop_->Quit();
}
ServiceBinding service_binding_;
Identity target_identity_;
std::unique_ptr<base::RunLoop> wait_for_target_identity_loop_;
BinderRegistry registry_;
mojo::Binding<test::mojom::CreateInstanceTest> binding_{this};
DISALLOW_COPY_AND_ASSIGN(TestService);
};
class SimpleService : public Service {
public:
explicit SimpleService(mojom::ServiceRequest request)
: binding_(this, std::move(request)) {}
~SimpleService() override {}
Connector* connector() { return binding_.GetConnector(); }
void WaitForDisconnect() {
base::RunLoop loop;
connection_lost_closure_ = loop.QuitClosure();
loop.Run();
}
private:
// Service:
void OnDisconnected() override {
if (connection_lost_closure_)
std::move(connection_lost_closure_).Run();
Terminate();
}
ServiceBinding binding_;
base::OnceClosure connection_lost_closure_;
DISALLOW_COPY_AND_ASSIGN(SimpleService);
};
} // namespace
class ServiceManagerTest : public testing::Test,
public mojom::ServiceManagerListener {
public:
ServiceManagerTest()
: test_service_manager_(GetTestManifests()),
test_service_(
test_service_manager_.RegisterTestInstance(kTestServiceName)) {}
~ServiceManagerTest() override = default;
protected:
struct InstanceInfo {
explicit InstanceInfo(const Identity& identity)
: identity(identity), pid(base::kNullProcessId) {}
Identity identity;
base::ProcessId pid;
};
Connector* connector() { return test_service_.connector(); }
void AddListenerAndWaitForApplications() {
mojo::Remote<mojom::ServiceManager> service_manager;
connector()->Connect(service_manager::mojom::kServiceName,
service_manager.BindNewPipeAndPassReceiver());
mojo::PendingRemote<mojom::ServiceManagerListener> listener;
receiver_.Bind(listener.InitWithNewPipeAndPassReceiver());
service_manager->AddListener(std::move(listener));
wait_for_instances_loop_ = std::make_unique<base::RunLoop>();
wait_for_instances_loop_->Run();
}
bool ContainsInstanceWithName(const std::string& name) const {
for (const auto& instance : initial_instances_) {
if (instance.identity.name() == name)
return true;
}
for (const auto& instance : instances_) {
if (instance.identity.name() == name)
return true;
}
return false;
}
void WaitForTargetIdentityCall() {
test_service_.WaitForTargetIdentityCall();
}
const Identity& target_identity() const {
return test_service_.target_identity();
}
const std::vector<InstanceInfo>& instances() const { return instances_; }
using ServiceStartedCallback =
base::RepeatingCallback<void(const service_manager::Identity&)>;
void set_service_started_callback(const ServiceStartedCallback& callback) {
service_started_callback_ = callback;
}
using ServiceFailedToStartCallback =
base::RepeatingCallback<void(const service_manager::Identity&)>;
void set_service_failed_to_start_callback(
const ServiceFailedToStartCallback& callback) {
service_failed_to_start_callback_ = callback;
}
using ServicePIDReceivedCallback =
base::RepeatingCallback<void(const service_manager::Identity&,
uint32_t pid)>;
void set_service_pid_received_callback(
const ServicePIDReceivedCallback& callback) {
service_pid_received_callback_ = callback;
}
void WaitForInstanceToStart(const Identity& identity) {
base::RunLoop loop;
set_service_started_callback(base::BindRepeating(
[](base::RunLoop* loop, const Identity* expected_identity,
const Identity& identity) {
EXPECT_EQ(expected_identity->name(), identity.name());
EXPECT_EQ(expected_identity->instance_group(),
identity.instance_group());
EXPECT_EQ(expected_identity->instance_id(), identity.instance_id());
loop->Quit();
},
&loop, &identity));
loop.Run();
set_service_started_callback(ServiceStartedCallback());
}
void StartTarget() {
base::FilePath target_path;
CHECK(base::PathService::Get(base::DIR_ASSETS, &target_path));
#if defined(OS_WIN)
target_path =
target_path.AppendASCII(kTestTargetName).AddExtensionASCII("exe");
#else
target_path = target_path.Append(FILE_PATH_LITERAL(kTestTargetName));
#endif
base::CommandLine child_command_line(target_path);
// Forward the wait-for-debugger flag but nothing else - we don't want to
// stamp on the platform-channel flag.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kWaitForDebugger)) {
child_command_line.AppendSwitch(switches::kWaitForDebugger);
}
// Create the channel to be shared with the target process. Pass one end
// on the command line.
mojo::PlatformChannel channel;
base::LaunchOptions options;
channel.PrepareToPassRemoteEndpoint(&options, &child_command_line);
mojo::OutgoingInvitation invitation;
service_manager::mojom::ServicePtr client =
ServiceProcessLauncher::PassServiceRequestOnCommandLine(
&invitation, &child_command_line);
mojo::Remote<service_manager::mojom::ProcessMetadata> metadata;
connector()->RegisterServiceInstance(
service_manager::Identity(kTestTargetName, kSystemInstanceGroup,
base::Token{}, base::Token::CreateRandom()),
client.PassInterface(), metadata.BindNewPipeAndPassReceiver());
target_ = base::LaunchProcess(child_command_line, options);
DCHECK(target_.IsValid());
channel.RemoteProcessLaunchAttempted();
metadata->SetPID(target_.Pid());
mojo::OutgoingInvitation::Send(std::move(invitation), target_.Handle(),
channel.TakeLocalEndpoint());
}
void StartEmbedderService() {
base::RunLoop loop;
int start_count = 0;
std::string service_name;
set_service_started_callback(
base::BindRepeating(&OnServiceStartedCallback, &start_count,
&service_name, loop.QuitClosure()));
bool failed_to_start = false;
set_service_failed_to_start_callback(base::BindRepeating(
&OnServiceFailedToStartCallback, &failed_to_start, loop.QuitClosure()));
connector()->WarmService(
service_manager::ServiceFilter::ByName(kTestEmbedderName));
loop.Run();
EXPECT_FALSE(failed_to_start);
EXPECT_EQ(1, start_count);
EXPECT_EQ(kTestEmbedderName, service_name);
}
void StartService(const ServiceFilter& filter, bool expect_service_started) {
int start_count = 0;
base::RunLoop loop;
std::string service_name;
set_service_started_callback(
base::BindRepeating(&OnServiceStartedCallback, &start_count,
&service_name, loop.QuitClosure()));
bool failed_to_start = false;
set_service_failed_to_start_callback(base::BindRepeating(
&OnServiceFailedToStartCallback, &failed_to_start, loop.QuitClosure()));
connector()->WarmService(filter);
if (!expect_service_started) {
// Wait briefly and test no new service was created.
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, loop.QuitClosure(), base::TimeDelta::FromSeconds(1));
}
loop.Run();
EXPECT_FALSE(failed_to_start);
if (expect_service_started) {
EXPECT_EQ(1, start_count);
EXPECT_EQ(filter.service_name(), service_name);
} else {
// The callback was not invoked, nothing should have been set.
EXPECT_EQ(0, start_count);
EXPECT_TRUE(service_name.empty());
}
}
void KillTarget() {
target_.Terminate(0, false);
}
private:
// Service:
// mojom::ServiceManagerListener:
void OnInit(std::vector<mojom::RunningServiceInfoPtr> instances) override {
for (size_t i = 0; i < instances.size(); ++i)
initial_instances_.push_back(InstanceInfo(instances[i]->identity));
DCHECK(wait_for_instances_loop_);
wait_for_instances_loop_->Quit();
}
void OnServiceCreated(mojom::RunningServiceInfoPtr instance) override {
instances_.push_back(InstanceInfo(instance->identity));
}
void OnServiceStarted(const service_manager::Identity& identity,
uint32_t pid) override {
for (auto& instance : instances_) {
if (instance.identity == identity) {
instance.pid = pid;
break;
}
}
if (!service_started_callback_.is_null())
service_started_callback_.Run(identity);
}
void OnServiceFailedToStart(
const service_manager::Identity& identity) override {
if (!service_failed_to_start_callback_.is_null())
service_failed_to_start_callback_.Run(identity);
}
void OnServiceStopped(const service_manager::Identity& identity) override {
for (auto it = instances_.begin(); it != instances_.end(); ++it) {
auto& instance = *it;
if (instance.identity == identity) {
instances_.erase(it);
break;
}
}
}
void OnServicePIDReceived(const service_manager::Identity& identity,
uint32_t pid) override {
if (!service_pid_received_callback_.is_null())
service_pid_received_callback_.Run(identity, pid);
}
base::test::TaskEnvironment task_environment_;
TestServiceManager test_service_manager_;
TestService test_service_;
mojo::Receiver<mojom::ServiceManagerListener> receiver_{this};
std::vector<InstanceInfo> instances_;
std::vector<InstanceInfo> initial_instances_;
std::unique_ptr<base::RunLoop> wait_for_instances_loop_;
ServiceStartedCallback service_started_callback_;
ServiceFailedToStartCallback service_failed_to_start_callback_;
ServicePIDReceivedCallback service_pid_received_callback_;
base::Process target_;
DISALLOW_COPY_AND_ASSIGN(ServiceManagerTest);
};
TEST_F(ServiceManagerTest, CreateInstance) {
AddListenerAndWaitForApplications();
// 1. Launch a process.
StartTarget();
// 2. Wait for the target to connect to us. (via
// service:service_manager_unittest)
WaitForTargetIdentityCall();
// 3. Validate that this test suite's name was received from the application
// manager.
EXPECT_TRUE(ContainsInstanceWithName(kTestServiceName));
// 4. Validate that the right applications/processes were created.
// Note that the target process will be created even if the tests are
// run with --single-process.
EXPECT_EQ(1u, instances().size());
{
auto& instance = instances().back();
// We learn about the target process id via a ping from it.
EXPECT_EQ(target_identity(), instance.identity);
EXPECT_EQ(kTestTargetName, instance.identity.name());
EXPECT_NE(base::kNullProcessId, instance.pid);
}
KillTarget();
}
// Tests that starting a regular packaged service works, and that when starting
// the service again, a new service is created unless the same user ID and
// instance names are used.
TEST_F(ServiceManagerTest, CreatePackagedRegularInstances) {
AddListenerAndWaitForApplications();
// Connect to the embedder service first.
StartEmbedderService();
auto filter = ServiceFilter::ByName(kTestRegularServiceName);
StartService(filter, /*expect_service_started=*/true);
// Retstarting with the same identity reuses the existing service.
StartService(filter, /*expect_service_started=*/false);
// Starting with a different instance group creates a new service.
auto other_group_filter = ServiceFilter::ByNameInGroup(
kTestRegularServiceName, base::Token::CreateRandom());
StartService(other_group_filter, /*expect_service_started=*/true);
// Starting with a different instance ID creates a new service as well.
auto other_id_filter =
ServiceFilter::ByNameWithId(kTestRegularServiceName, base::Token{1, 2});
StartService(other_id_filter, /*expect_service_started=*/true);
}
// Tests that starting a shared instance packaged service works, and that when
// starting that service again, a new service is created only when a different
// instance name is specified.
TEST_F(ServiceManagerTest, CreatePackagedSharedAcrossGroupsInstances) {
AddListenerAndWaitForApplications();
// Connect to the embedder service first.
StartEmbedderService();
auto filter = ServiceFilter::ByName(kTestSharedServiceName);
StartService(filter, /*expect_service_started=*/true);
// Start again with a different instance group. The existing service should be
// reused.
auto other_group_filter = ServiceFilter::ByNameInGroup(
kTestSharedServiceName, base::Token::CreateRandom());
StartService(other_group_filter, /*expect_service_started=*/false);
// Start again with a difference instance ID. In that case a new service
// should get created.
auto other_id_filter = ServiceFilter::ByNameWithIdInGroup(
kTestSharedServiceName, base::Token{1, 2}, base::Token::CreateRandom());
StartService(other_id_filter, /*expect_service_started=*/true);
}
// Tests that creating a singleton packaged service works, and that when
// starting that service again a new service is never created.
TEST_F(ServiceManagerTest, CreatePackagedSingletonInstances) {
AddListenerAndWaitForApplications();
// Connect to the embedder service first.
StartEmbedderService();
auto filter = ServiceFilter::ByName(kTestSingletonServiceName);
StartService(filter, /*expect_service_started=*/true);
// Start again with a different instance group. The existing service should be
// reused.
auto other_group_filter = ServiceFilter::ByNameInGroup(
kTestSingletonServiceName, base::Token::CreateRandom());
StartService(other_group_filter, /*expect_service_started=*/false);
// Start again with the same instance group but a difference instance ID. The
// existing service should still be reused.
auto other_id_filter =
ServiceFilter::ByNameWithId(kTestSingletonServiceName, base::Token{3, 4});
StartService(other_id_filter, /*expect_service_started=*/false);
}
TEST_F(ServiceManagerTest, PIDReceivedCallback) {
AddListenerAndWaitForApplications();
{
base::RunLoop loop;
std::string service_name;
uint32_t pid = 0u;
set_service_pid_received_callback(
base::BindRepeating(&OnServicePIDReceivedCallback, &service_name, &pid,
loop.QuitClosure()));
bool failed_to_start = false;
set_service_failed_to_start_callback(base::BindRepeating(
&OnServiceFailedToStartCallback, &failed_to_start, loop.QuitClosure()));
connector()->WarmService(ServiceFilter::ByName(kTestEmbedderName));
loop.Run();
EXPECT_FALSE(failed_to_start);
EXPECT_EQ(kTestEmbedderName, service_name);
EXPECT_NE(pid, 0u);
}
}
TEST_F(ServiceManagerTest, ClientProcessCapabilityEnforced) {
AddListenerAndWaitForApplications();
const std::string kTestService = kTestTargetName;
const Identity kInstance1Id(kTestService, kSystemInstanceGroup,
base::Token{1, 2}, base::Token::CreateRandom());
const Identity kInstance2Id(kTestService, kSystemInstanceGroup,
base::Token{3, 4}, base::Token::CreateRandom());
// Introduce a new service instance for service_manager_unittest_target,
// which should be allowed because the test service has
// |can_create_other_service_instances| set to |true| in its manifest.
mojom::ServicePtr test_service_proxy1;
SimpleService test_service1(mojo::MakeRequest(&test_service_proxy1));
mojo::Remote<mojom::ProcessMetadata> metadata1;
connector()->RegisterServiceInstance(kInstance1Id,
test_service_proxy1.PassInterface(),
metadata1.BindNewPipeAndPassReceiver());
metadata1->SetPID(42);
WaitForInstanceToStart(kInstance1Id);
EXPECT_EQ(1u, instances().size());
EXPECT_TRUE(ContainsInstanceWithName(kTestTargetName));
// Now use the new instance (which does not have client_process capability)
// to attempt introduction of yet another instance. This should fail.
mojom::ServicePtr test_service_proxy2;
SimpleService test_service2(mojo::MakeRequest(&test_service_proxy2));
mojo::Remote<mojom::ProcessMetadata> metadata2;
test_service1.connector()->RegisterServiceInstance(
kInstance2Id, test_service_proxy2.PassInterface(),
metadata2.BindNewPipeAndPassReceiver());
metadata2->SetPID(43);
// The new service should be disconnected immediately.
test_service2.WaitForDisconnect();
// And still only one service instance around.
EXPECT_EQ(1u, instances().size());
}
TEST_F(ServiceManagerTest, ClonesDisconnectedConnectors) {
Connector connector((mojo::PendingRemote<mojom::Connector>()));
EXPECT_TRUE(connector.Clone());
}
} // namespace service_manager