Commit 12dc6d4e authored by Wez's avatar Wez Committed by Commit Bot

[fuchsia] Allow the process' ComponentContext to be overridden by tests.

ComponentContextForCurrentProcess() returns a sys::ComponentContext
connected to the process' incoming service directory, outgoing directory
for services & debugging APIs, etc.

Provide a TestComponentContextForProcess, which overrides the process'
ComponentContext with one which by default has no services available,
and which allows services to be provided by the test itself, and for
services available in the test process environment to be exposed to the
test.

Bug: 1038786
Change-Id: I0d754a1629ab25527dce377d6ffaa039791ef3e1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2064908Reviewed-by: default avatarSergey Ulanov <sergeyu@chromium.org>
Commit-Queue: Sergey Ulanov <sergeyu@chromium.org>
Auto-Submit: Wez <wez@chromium.org>
Cr-Commit-Position: refs/heads/master@{#744858}
parent 0a0e4fd2
...@@ -3061,6 +3061,7 @@ test("base_unittests") { ...@@ -3061,6 +3061,7 @@ test("base_unittests") {
"fuchsia/service_directory_test_base.cc", "fuchsia/service_directory_test_base.cc",
"fuchsia/service_directory_test_base.h", "fuchsia/service_directory_test_base.h",
"fuchsia/service_provider_impl_unittest.cc", "fuchsia/service_provider_impl_unittest.cc",
"fuchsia/test_component_context_for_process_unittest.cc",
"fuchsia/time_zone_data_unittest.cc", "fuchsia/time_zone_data_unittest.cc",
"message_loop/fd_watch_controller_posix_unittest.cc", "message_loop/fd_watch_controller_posix_unittest.cc",
"posix/file_descriptor_shuffle_unittest.cc", "posix/file_descriptor_shuffle_unittest.cc",
......
...@@ -11,14 +11,27 @@ ...@@ -11,14 +11,27 @@
#include "base/no_destructor.h" #include "base/no_destructor.h"
namespace base { namespace base {
namespace fuchsia {
sys::ComponentContext* ComponentContextForCurrentProcess() { namespace {
std::unique_ptr<sys::ComponentContext>* ProcessComponentContextPtr() {
static base::NoDestructor<std::unique_ptr<sys::ComponentContext>> value( static base::NoDestructor<std::unique_ptr<sys::ComponentContext>> value(
std::make_unique<sys::ComponentContext>( std::make_unique<sys::ComponentContext>(
sys::ServiceDirectory::CreateFromNamespace())); sys::ServiceDirectory::CreateFromNamespace()));
return value.get()->get(); return value.get();
} }
} // namespace
namespace fuchsia {
sys::ComponentContext* ComponentContextForCurrentProcess() {
return ProcessComponentContextPtr()->get();
}
} // namespace fuchsia } // namespace fuchsia
std::unique_ptr<sys::ComponentContext>
ReplaceComponentContextForCurrentProcessForTest(
std::unique_ptr<sys::ComponentContext> context) {
std::swap(*ProcessComponentContextPtr(), context);
return context;
}
} // namespace base } // namespace base
...@@ -14,12 +14,20 @@ class ComponentContext; ...@@ -14,12 +14,20 @@ class ComponentContext;
} // namespace sys } // namespace sys
namespace base { namespace base {
namespace fuchsia {
namespace fuchsia {
// Returns default sys::ComponentContext for the current process. // Returns default sys::ComponentContext for the current process.
BASE_EXPORT sys::ComponentContext* ComponentContextForCurrentProcess(); BASE_EXPORT sys::ComponentContext* ComponentContextForCurrentProcess();
} // namespace fuchsia } // namespace fuchsia
// Replaces the default sys::ComponentContext for the current process, and
// returns the previously-active one.
// Use the base::TestComponentContextForProcess rather than calling this
// directly.
BASE_EXPORT std::unique_ptr<sys::ComponentContext>
ReplaceComponentContextForCurrentProcessForTest(
std::unique_ptr<sys::ComponentContext> context);
} // namespace base } // namespace base
#endif // BASE_FUCHSIA_DEFAULT_CONTEXT_H_ #endif // BASE_FUCHSIA_DEFAULT_CONTEXT_H_
\ No newline at end of file
// Copyright 2020 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 "base/fuchsia/test_component_context_for_process.h"
#include <fuchsia/io/cpp/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/fidl/cpp/interface_handle.h>
#include <lib/sys/cpp/component_context.h>
#include "base/fuchsia/default_context.h"
#include "base/fuchsia/filtered_service_directory.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/run_loop.h"
namespace base {
TestComponentContextForProcess::TestComponentContextForProcess() {
// Set up |incoming_services_| to use the ServiceDirectory from the current
// default ComponentContext to fetch services from.
context_services_ = std::make_unique<fuchsia::FilteredServiceDirectory>(
base::fuchsia::ComponentContextForCurrentProcess()->svc().get());
// Create a ServiceDirectory backed by the contents of |incoming_directory|.
fidl::InterfaceHandle<::fuchsia::io::Directory> incoming_directory;
context_services_->ConnectClient(incoming_directory.NewRequest());
auto incoming_services =
std::make_shared<sys::ServiceDirectory>(std::move(incoming_directory));
// Create the ComponentContext with the incoming directory connected to the
// directory of |context_services_| published by the test, and with a request
// for the process' root outgoing directory.
fidl::InterfaceHandle<::fuchsia::io::Directory> published_root_directory;
old_context_ = ReplaceComponentContextForCurrentProcessForTest(
std::make_unique<sys::ComponentContext>(
std::move(incoming_services),
published_root_directory.NewRequest().TakeChannel()));
// Connect to the "/svc" directory of the |published_root_directory| and wrap
// that into a ServiceDirectory.
fidl::InterfaceHandle<::fuchsia::io::Directory> published_services;
zx_status_t status = fdio_service_connect_at(
published_root_directory.channel().get(), "svc",
published_services.NewRequest().TakeChannel().release());
ZX_CHECK(status == ZX_OK, status) << "fdio_service_connect_at() to /svc";
published_services_ =
std::make_unique<sys::ServiceDirectory>(std::move(published_services));
}
TestComponentContextForProcess::~TestComponentContextForProcess() {
ReplaceComponentContextForCurrentProcessForTest(std::move(old_context_));
}
sys::OutgoingDirectory* TestComponentContextForProcess::additional_services() {
return context_services_->outgoing_directory();
}
void TestComponentContextForProcess::AddServices(
base::span<const base::StringPiece> services) {
for (auto service : services)
context_services_->AddService(service);
}
} // namespace base
// Copyright 2020 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.
#ifndef BASE_FUCHSIA_TEST_COMPONENT_CONTEXT_FOR_PROCESS_H_
#define BASE_FUCHSIA_TEST_COMPONENT_CONTEXT_FOR_PROCESS_H_
#include <memory>
#include "base/base_export.h"
#include "base/containers/span.h"
#include "base/strings/string_piece.h"
namespace sys {
class ComponentContext;
class OutgoingDirectory;
class ServiceDirectory;
} // namespace sys
namespace base {
namespace fuchsia {
class FilteredServiceDirectory;
} // namespace fuchsia
// Replaces the process-global sys::ComponentContext (as returned by the
// base::fuchsia::ComponentContextForCurrentProcess() function) with an empty
// instance which the calling test can configure, and restores the original
// when deleted.
//
// The test ComponentContext runs on the test main thread, which means that:
// - Tests using TestComponentContextForProcess must instantiate a
// [SingleThread]TaskEnvironment with UI or IO main-thread-type.
// - If all services exposed via the test ComponentContext run on the test
// main thread, and the code under test does as well, then RunUntilIdle() can
// normally be used to "flush" any pending FIDL requests and related work.
// Note that this is not true if any services, or code under test, use threads
// or processes!
//
// The test ComponentContext is typically instantiated within a test body or
// test base-class:
//
// TEST(MyFunkyTest, IsFunky) {
// TestComponentContextForTest test_context;
// // Configure the |test_context|.
// // Run tests of code that uses ComponentContextForProcess().
// }
//
// Services from the original process-global ComponentContext (usually the
// environment in which the test process is running), can be exposed through the
// |test_context| with AddServices(), during test setup:
//
// test_context.AddServices({fuchsia::memorypressure::Provider::Name_, ...});
// // ... Execute tests which use fuchsia.memorypressure.Provider ...
//
// Fake/mock implementations can be exposed via additional_services():
//
// ScopedServiceBinding<funky::Service> binding(
// test_context.additional_services(), &funky_implementation);
// // ... Execute tests which use funky.Service.
//
// Services published to the process' ComponentContext by code-under-test
// can be accessed via published_services():
//
// funky::HamsterPtr hamster_service;
// test_context.published_services()->Connect(hamster_service.NewRequest());
// // ... Execute tests which exercise the funky.Hamster implementation.
//
class BASE_EXPORT TestComponentContextForProcess {
public:
TestComponentContextForProcess();
~TestComponentContextForProcess();
TestComponentContextForProcess(const TestComponentContextForProcess&) =
delete;
TestComponentContextForProcess& operator=(
const TestComponentContextForProcess&) = delete;
// Returns an OutgoingDirectory into which additional services may be
// published for use by the code-under test.
sys::OutgoingDirectory* additional_services();
// Allows the specified services from the original ComponentContext to be
// exposed via the test default ComponentContext.
void AddServices(base::span<const base::StringPiece> services);
// Returns the directory of services that the code under test has published
// to its outgoing service directory.
sys::ServiceDirectory* published_services() const {
return published_services_.get();
}
private:
std::unique_ptr<sys::ComponentContext> old_context_;
std::unique_ptr<fuchsia::FilteredServiceDirectory> context_services_;
std::unique_ptr<sys::ServiceDirectory> published_services_;
};
} // namespace base
#endif // BASE_FUCHSIA_TEST_COMPONENT_CONTEXT_FOR_PROCESS_H_
// Copyright 2020 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 "base/fuchsia/test_component_context_for_process.h"
#include <fuchsia/intl/cpp/fidl.h>
#include <lib/sys/cpp/component_context.h>
#include "base/fuchsia/default_context.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/scoped_service_binding.h"
#include "base/fuchsia/testfidl/cpp/fidl.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
class TestComponentContextForProcessTest
: public testing::Test,
public fuchsia::testfidl::TestInterface {
public:
TestComponentContextForProcessTest()
: task_environment_(base::test::TaskEnvironment::MainThreadType::IO) {}
bool HasTestInterface() {
return VerifyTestInterface(
fuchsia::ComponentContextForCurrentProcess()
->svc()
->Connect<fuchsia::testfidl::TestInterface>());
}
bool HasPublishedTestInterface() {
return VerifyTestInterface(
test_context_.published_services()
->Connect<fuchsia::testfidl::TestInterface>());
}
// fuchsia::testfidl::TestInterface implementation.
void Add(int32_t a, int32_t b, AddCallback callback) override {
callback(a + b);
}
protected:
bool VerifyTestInterface(fuchsia::testfidl::TestInterfacePtr test_interface) {
bool have_interface = false;
RunLoop wait_loop;
test_interface.set_error_handler([quit_loop = wait_loop.QuitClosure(),
&have_interface](zx_status_t status) {
ZX_CHECK(status == ZX_ERR_PEER_CLOSED, status);
have_interface = false;
quit_loop.Run();
});
test_interface->Add(
45, 6,
[quit_loop = wait_loop.QuitClosure(), &have_interface](int32_t result) {
EXPECT_EQ(result, 45 + 6);
have_interface = true;
quit_loop.Run();
});
wait_loop.Run();
return have_interface;
}
const base::test::SingleThreadTaskEnvironment task_environment_;
base::TestComponentContextForProcess test_context_;
};
TEST_F(TestComponentContextForProcessTest, NoServices) {
// No services should be available.
EXPECT_FALSE(HasTestInterface());
}
TEST_F(TestComponentContextForProcessTest, InjectTestInterface) {
// Publish a fake TestInterface for the process' ComponentContext to expose.
base::fuchsia::ScopedServiceBinding<fuchsia::testfidl::TestInterface>
service_binding(test_context_.additional_services(), this);
// Verify that the TestInterface is accessible & usable.
EXPECT_TRUE(HasTestInterface());
}
TEST_F(TestComponentContextForProcessTest, PublishTestInterface) {
// Publish TestInterface to the process' outgoing-directory.
base::fuchsia::ScopedServiceBinding<fuchsia::testfidl::TestInterface>
service_binding(
fuchsia::ComponentContextForCurrentProcess()->outgoing().get(), this);
// Attempt to use the TestInterface from the outgoing-directory.
EXPECT_TRUE(HasPublishedTestInterface());
}
TEST_F(TestComponentContextForProcessTest, ProvideSystemService) {
// Expose fuchsia.device.NameProvider through the ComponentContext.
const base::StringPiece kServiceNames[] = {
::fuchsia::intl::PropertyProvider::Name_};
test_context_.AddServices(kServiceNames);
// Attempt to use the PropertyProvider via the process ComponentContext.
RunLoop wait_loop;
auto property_provider = fuchsia::ComponentContextForCurrentProcess()
->svc()
->Connect<::fuchsia::intl::PropertyProvider>();
property_provider.set_error_handler(
[quit_loop = wait_loop.QuitClosure()](zx_status_t status) {
if (status == ZX_ERR_PEER_CLOSED) {
ADD_FAILURE() << "PropertyProvider disconnected; probably not found.";
} else {
ZX_LOG(FATAL, status);
}
quit_loop.Run();
});
property_provider->GetProfile(
[quit_loop = wait_loop.QuitClosure()](::fuchsia::intl::Profile profile) {
quit_loop.Run();
});
wait_loop.Run();
}
} // namespace base
...@@ -247,6 +247,10 @@ static_library("test_support") { ...@@ -247,6 +247,10 @@ static_library("test_support") {
if (is_fuchsia) { if (is_fuchsia) {
deps += [ "//third_party/fuchsia-sdk/sdk/pkg/zx" ] deps += [ "//third_party/fuchsia-sdk/sdk/pkg/zx" ]
sources += [
"../fuchsia/test_component_context_for_process.cc",
"../fuchsia/test_component_context_for_process.h",
]
} }
if (is_nacl_nonsfi) { if (is_nacl_nonsfi) {
......
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