| // Copyright 2021 The Fuchsia 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 <fuchsia/component/config/cpp/fidl.h> |
| #include <fuchsia/component/cpp/fidl.h> |
| #include <fuchsia/component/decl/cpp/fidl.h> |
| #include <fuchsia/component/runner/cpp/fidl.h> |
| #include <fuchsia/component/test/cpp/fidl.h> |
| #include <fuchsia/io/cpp/fidl.h> |
| #include <lib/async/default.h> |
| #include <lib/async/dispatcher.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/io.h> |
| #include <lib/fidl/cpp/interface_handle.h> |
| #include <lib/fidl/cpp/interface_request.h> |
| #include <lib/sys/component/cpp/testing/internal/convert.h> |
| #include <lib/sys/component/cpp/testing/internal/errors.h> |
| #include <lib/sys/component/cpp/testing/internal/local_component_runner.h> |
| #include <lib/sys/component/cpp/testing/internal/realm.h> |
| #include <lib/sys/component/cpp/testing/realm_builder.h> |
| #include <lib/sys/component/cpp/testing/realm_builder_types.h> |
| #include <lib/sys/component/cpp/testing/scoped_child.h> |
| #include <lib/sys/cpp/component_context.h> |
| #include <lib/sys/cpp/service_directory.h> |
| #include <zircon/assert.h> |
| #include <zircon/errors.h> |
| |
| #include <cstddef> |
| #include <functional> |
| #include <memory> |
| #include <optional> |
| #include <sstream> |
| #include <utility> |
| #include <vector> |
| |
| namespace component_testing { |
| namespace { |
| constexpr char kFrameworkIntermediaryChildName[] = "realm_builder_server"; |
| constexpr char kChildPathSeparator[] = "/"; |
| |
| fidl::InterfaceHandle<fuchsia::io::Directory> CreatePkgDirHandle() { |
| int fd; |
| ZX_COMPONENT_ASSERT_STATUS_OK( |
| "fdio_open_fd", fdio_open_fd("/pkg", |
| static_cast<uint32_t>(fuchsia::io::OpenFlags::RIGHT_READABLE | |
| fuchsia::io::OpenFlags::RIGHT_EXECUTABLE), |
| &fd)); |
| zx_handle_t handle; |
| ZX_COMPONENT_ASSERT_STATUS_OK("fdio_fd_transfer", fdio_fd_transfer(fd, &handle)); |
| auto channel = zx::channel(handle); |
| return fidl::InterfaceHandle<fuchsia::io::Directory>(std::move(channel)); |
| } |
| |
| } // namespace |
| |
| // Implementation methods for Realm. |
| |
| Realm& Realm::AddChild(const std::string& child_name, const std::string& url, |
| const ChildOptions& options) { |
| fuchsia::component::test::Realm_AddChild_Result result; |
| ZX_COMPONENT_ASSERT_STATUS_AND_RESULT_OK( |
| "Realm/AddChild", |
| realm_proxy_->AddChild(child_name, url, internal::ConvertToFidl(options), &result), result); |
| return *this; |
| } |
| |
| // TODO(https://fxbug.dev/296292544): Remove when build support for API level 16 is removed. |
| // The newer definition of LocalComponentKind is incompatible with LocalComponent*. |
| #if __Fuchsia_API_level__ < 17 |
| Realm& Realm::AddLocalChild(const std::string& child_name, LocalComponent* local_impl, |
| const ChildOptions& options) { |
| return AddLocalChildImpl(child_name, LocalComponentKind(local_impl), options); |
| } |
| #endif |
| |
| Realm& Realm::AddLocalChild(const std::string& child_name, LocalComponentFactory local_impl, |
| const ChildOptions& options) { |
| return AddLocalChildImpl(child_name, LocalComponentKind(std::move(local_impl)), options); |
| } |
| |
| Realm& Realm::AddLocalChildImpl(const std::string& child_name, LocalComponentKind local_impl, |
| const ChildOptions& options) { |
| // TODO(https://fxbug.dev/296292544): Remove when build support for API level 16 is removed. |
| #if __Fuchsia_API_level__ < 17 |
| // Ignore warnings caused by the use of the deprecated `LocalComponent` type as it is part of the |
| // implementation that supports the deprecated type. |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Wdeprecated-declarations" |
| if (cpp17::holds_alternative<LocalComponent*>(local_impl)) { |
| ZX_SYS_ASSERT_NOT_NULL(cpp17::get<LocalComponent*>(local_impl)); |
| } |
| #pragma clang diagnostic pop |
| #endif |
| runner_builder_->Register(GetResolvedName(child_name), std::move(local_impl)); |
| fuchsia::component::test::Realm_AddLocalChild_Result result; |
| ZX_COMPONENT_ASSERT_STATUS_AND_RESULT_OK( |
| "Realm/AddLocalChild", |
| realm_proxy_->AddLocalChild(child_name, internal::ConvertToFidl(options), &result), result); |
| return *this; |
| } |
| |
| Realm Realm::AddChildRealm(const std::string& child_name, const ChildOptions& options) { |
| fuchsia::component::test::RealmSyncPtr sub_realm_proxy; |
| std::vector<std::string> sub_realm_scope = scope_; |
| sub_realm_scope.push_back(child_name); |
| Realm sub_realm(std::move(sub_realm_proxy), runner_builder_, std::move(sub_realm_scope)); |
| |
| fuchsia::component::test::Realm_AddChildRealm_Result result; |
| ZX_COMPONENT_ASSERT_STATUS_AND_RESULT_OK( |
| "Realm/AddChildRealm", |
| realm_proxy_->AddChildRealm(child_name, internal::ConvertToFidl(options), |
| sub_realm.realm_proxy_.NewRequest(), &result), |
| result); |
| return sub_realm; |
| } |
| |
| Realm& Realm::AddRoute(Route route) { |
| auto capabilities = internal::ConvertToFidlVec<Capability, fuchsia::component::test::Capability>( |
| route.capabilities); |
| auto source = internal::ConvertToFidl(route.source); |
| auto target = internal::ConvertToFidlVec<Ref, fuchsia::component::decl::Ref>(route.targets); |
| |
| fuchsia::component::test::Realm_AddRoute_Result result; |
| ZX_COMPONENT_ASSERT_STATUS_AND_RESULT_OK( |
| "Realm/AddRoute", |
| realm_proxy_->AddRoute(std::move(capabilities), std::move(source), std::move(target), |
| &result), |
| result); |
| return *this; |
| } |
| |
| Realm& Realm::RouteReadOnlyDirectory(const std::string& name, std::vector<Ref> to, |
| DirectoryContents directory) { |
| auto to_fidl = internal::ConvertToFidlVec<Ref, fuchsia::component::decl::Ref>(std::move(to)); |
| auto directory_fidl = directory.TakeAsFidl(); |
| |
| fuchsia::component::test::Realm_ReadOnlyDirectory_Result result; |
| ZX_COMPONENT_ASSERT_STATUS_AND_RESULT_OK( |
| "Realm/ReadOnlyDirectory", |
| realm_proxy_->ReadOnlyDirectory(name, std::move(to_fidl), std::move(directory_fidl), &result), |
| result); |
| |
| return *this; |
| } |
| |
| Realm& Realm::InitMutableConfigFromPackage(const std::string& name) { |
| fuchsia::component::test::Realm_InitMutableConfigFromPackage_Result result; |
| ZX_COMPONENT_ASSERT_STATUS_AND_RESULT_OK( |
| "Realm/InitMutableConfigFromPackage", |
| realm_proxy_->InitMutableConfigFromPackage(name, &result), result); |
| return *this; |
| } |
| |
| Realm& Realm::InitMutableConfigToEmpty(const std::string& name) { |
| fuchsia::component::test::Realm_InitMutableConfigToEmpty_Result result; |
| ZX_COMPONENT_ASSERT_STATUS_AND_RESULT_OK("Realm/InitMutableConfigToEmpty", |
| realm_proxy_->InitMutableConfigToEmpty(name, &result), |
| result); |
| return *this; |
| } |
| |
| Realm& Realm::SetConfigValue(const std::string& name, const std::string& key, ConfigValue value) { |
| fuchsia::component::test::Realm_SetConfigValue_Result result; |
| ZX_COMPONENT_ASSERT_STATUS_AND_RESULT_OK( |
| "Realm/SetConfigValue", realm_proxy_->SetConfigValue(name, key, value.TakeAsFidl(), &result), |
| result); |
| return *this; |
| } |
| |
| #if __Fuchsia_API_level__ >= FUCHSIA_HEAD |
| Realm& Realm::AddConfiguration(std::vector<ConfigCapability> configurations) { |
| for (ConfigCapability& c : configurations) { |
| fuchsia::component::decl::Configuration config; |
| config.set_name(c.name); |
| config.set_value(std::move(*c.value.TakeAsFidl().mutable_value())); |
| fuchsia::component::test::Realm_AddCapability_Result result; |
| ZX_COMPONENT_ASSERT_STATUS_AND_RESULT_OK( |
| "Realm/AddCapability", |
| realm_proxy_->AddCapability( |
| fuchsia::component::decl::Capability::WithConfig(std::move(config)), &result), |
| result); |
| } |
| |
| return *this; |
| } |
| #endif |
| |
| void Realm::ReplaceComponentDecl(const std::string& child_name, |
| fuchsia::component::decl::Component decl) { |
| fuchsia::component::test::Realm_ReplaceComponentDecl_Result result; |
| ZX_COMPONENT_ASSERT_STATUS_AND_RESULT_OK( |
| "Realm/ReplaceComponentDecl", |
| realm_proxy_->ReplaceComponentDecl(child_name, std::move(decl), &result), result); |
| } |
| |
| void Realm::ReplaceRealmDecl(fuchsia::component::decl::Component decl) { |
| fuchsia::component::test::Realm_ReplaceRealmDecl_Result result; |
| ZX_COMPONENT_ASSERT_STATUS_AND_RESULT_OK( |
| "Realm/ReplaceRealmDecl", realm_proxy_->ReplaceRealmDecl(std::move(decl), &result), result); |
| } |
| |
| fuchsia::component::decl::Component Realm::GetComponentDecl(const std::string& child_name) { |
| fuchsia::component::test::Realm_GetComponentDecl_Result result; |
| ZX_COMPONENT_ASSERT_STATUS_AND_RESULT_OK( |
| "Realm/GetComponentDecl", realm_proxy_->GetComponentDecl(child_name, &result), result); |
| |
| return std::move(result.response().component_decl); |
| } |
| |
| fuchsia::component::decl::Component Realm::GetRealmDecl() { |
| fuchsia::component::test::Realm_GetRealmDecl_Result result; |
| ZX_COMPONENT_ASSERT_STATUS_AND_RESULT_OK("Realm/GetRealmDecl", |
| realm_proxy_->GetRealmDecl(&result), result); |
| |
| return std::move(result.response().component_decl); |
| } |
| |
| Realm::Realm(fuchsia::component::test::RealmSyncPtr realm_proxy, |
| std::shared_ptr<internal::LocalComponentRunner::Builder> runner_builder, |
| std::vector<std::string> scope) |
| : realm_proxy_(std::move(realm_proxy)), |
| runner_builder_(std::move(runner_builder)), |
| scope_(std::move(scope)) {} |
| |
| std::string Realm::GetResolvedName(const std::string& child_name) { |
| if (scope_.empty()) { |
| return child_name; |
| } |
| |
| std::stringstream path; |
| for (const auto& s : scope_) { |
| path << s << kChildPathSeparator; |
| } |
| return path.str() + child_name; |
| } |
| |
| // Implementation methods for RealmBuilder. |
| |
| RealmBuilder RealmBuilder::Create(std::shared_ptr<sys::ServiceDirectory> svc) { |
| return CreateImpl(cpp17::nullopt, std::move(svc)); |
| } |
| |
| RealmBuilder RealmBuilder::CreateFromRelativeUrl(std::string_view fragment_only_url, |
| std::shared_ptr<sys::ServiceDirectory> svc) { |
| return CreateImpl(fragment_only_url, std::move(svc)); |
| } |
| |
| RealmBuilder RealmBuilder::CreateImpl(cpp17::optional<std::string_view> fragment_only_url, |
| std::shared_ptr<sys::ServiceDirectory> svc) { |
| if (svc == nullptr) { |
| svc = sys::ServiceDirectory::CreateFromNamespace(); |
| } |
| |
| fuchsia::component::test::RealmBuilderFactorySyncPtr factory_proxy; |
| auto realm_proxy = internal::CreateRealmPtr(svc); |
| auto child_ref = fuchsia::component::decl::ChildRef{.name = kFrameworkIntermediaryChildName}; |
| auto exposed_dir = internal::OpenExposedDir(realm_proxy.get(), child_ref); |
| zx_status_t status = fdio_service_connect_at(exposed_dir.channel().get(), |
| fuchsia::component::test::RealmBuilderFactory::Name_, |
| factory_proxy.NewRequest().TakeChannel().release()); |
| ZX_COMPONENT_ASSERT_STATUS_OK("RealmBuilderFactory/Create", status); |
| fuchsia::component::test::BuilderSyncPtr builder_proxy; |
| fuchsia::component::test::RealmSyncPtr test_realm_proxy; |
| if (fragment_only_url.has_value()) { |
| ZX_ASSERT_MSG(!fragment_only_url.value().empty(), "fragment_only_url can't be empty"); |
| |
| fuchsia::component::test::RealmBuilderFactory_CreateFromRelativeUrl_Result result; |
| ZX_COMPONENT_ASSERT_STATUS_AND_RESULT_OK( |
| "RealmBuilderFactory/CreateFromRelativeUrl", |
| factory_proxy->CreateFromRelativeUrl(CreatePkgDirHandle(), fragment_only_url.value().data(), |
| test_realm_proxy.NewRequest(), |
| builder_proxy.NewRequest(), &result), |
| result); |
| } else { |
| fuchsia::component::test::RealmBuilderFactory_Create_Result result; |
| ZX_COMPONENT_ASSERT_STATUS_AND_RESULT_OK( |
| "RealmBuilderFactory/Create", |
| factory_proxy->Create(CreatePkgDirHandle(), test_realm_proxy.NewRequest(), |
| builder_proxy.NewRequest(), &result), |
| result); |
| } |
| return RealmBuilder(svc, std::move(builder_proxy), std::move(test_realm_proxy)); |
| } |
| |
| RealmBuilder& RealmBuilder::AddChild(const std::string& child_name, const std::string& url, |
| const ChildOptions& options) { |
| ZX_ASSERT_MSG(!child_name.empty(), "child_name can't be empty"); |
| ZX_ASSERT_MSG(!url.empty(), "url can't be empty"); |
| |
| root_.AddChild(child_name, url, options); |
| return *this; |
| } |
| |
| // TODO(https://fxbug.dev/296292544): Remove when build support for API level 16 is removed. |
| // The newer definition of LocalComponentKind, which is a parameter to AddLocalChildImpl(), is |
| // incompatible with LocalComponent*. |
| #if __Fuchsia_API_level__ < 17 |
| RealmBuilder& RealmBuilder::AddLocalChild(const std::string& child_name, LocalComponent* local_impl, |
| const ChildOptions& options) { |
| ZX_ASSERT_MSG(!child_name.empty(), "child_name can't be empty"); |
| ZX_ASSERT_MSG(local_impl != nullptr, "local_impl can't be nullptr"); |
| root_.AddLocalChildImpl(child_name, local_impl, options); |
| return *this; |
| } |
| #endif |
| |
| RealmBuilder& RealmBuilder::AddLocalChild(const std::string& child_name, |
| LocalComponentFactory local_impl, |
| const ChildOptions& options) { |
| ZX_ASSERT_MSG(!child_name.empty(), "child_name can't be empty"); |
| root_.AddLocalChildImpl(child_name, LocalComponentKind(std::move(local_impl)), options); |
| return *this; |
| } |
| |
| Realm RealmBuilder::AddChildRealm(const std::string& child_name, const ChildOptions& options) { |
| ZX_ASSERT_MSG(!child_name.empty(), "child_name can't be empty"); |
| return root_.AddChildRealm(child_name, options); |
| } |
| |
| RealmBuilder& RealmBuilder::AddRoute(Route route) { |
| ZX_ASSERT_MSG(!route.capabilities.empty(), "route.capabilities can't be empty"); |
| ZX_ASSERT_MSG(!route.targets.empty(), "route.targets can't be empty"); |
| |
| root_.AddRoute(std::move(route)); |
| return *this; |
| } |
| |
| RealmBuilder& RealmBuilder::RouteReadOnlyDirectory(const std::string& name, std::vector<Ref> to, |
| DirectoryContents directory) { |
| root_.RouteReadOnlyDirectory(name, std::move(to), std::move(directory)); |
| return *this; |
| } |
| |
| RealmBuilder& RealmBuilder::InitMutableConfigFromPackage(const std::string& name) { |
| root_.InitMutableConfigFromPackage(name); |
| return *this; |
| } |
| |
| RealmBuilder& RealmBuilder::InitMutableConfigToEmpty(const std::string& name) { |
| root_.InitMutableConfigToEmpty(name); |
| return *this; |
| } |
| |
| #if __Fuchsia_API_level__ >= FUCHSIA_HEAD |
| RealmBuilder& RealmBuilder::AddConfiguration(std::vector<ConfigCapability> configurations) { |
| root_.AddConfiguration(std::move(configurations)); |
| return *this; |
| } |
| #endif |
| |
| RealmBuilder& RealmBuilder::SetConfigValue(const std::string& name, const std::string& key, |
| ConfigValue value) { |
| root_.SetConfigValue(name, key, std::move(value)); |
| return *this; |
| } |
| |
| void RealmBuilder::ReplaceComponentDecl(const std::string& child_name, |
| fuchsia::component::decl::Component decl) { |
| root_.ReplaceComponentDecl(child_name, std::move(decl)); |
| } |
| |
| void RealmBuilder::ReplaceRealmDecl(fuchsia::component::decl::Component decl) { |
| root_.ReplaceRealmDecl(std::move(decl)); |
| } |
| |
| fuchsia::component::decl::Component RealmBuilder::GetComponentDecl(const std::string& child_name) { |
| return root_.GetComponentDecl(child_name); |
| } |
| |
| fuchsia::component::decl::Component RealmBuilder::GetRealmDecl() { return root_.GetRealmDecl(); } |
| |
| RealmBuilder& RealmBuilder::SetRealmCollection(const std::string& collection) { |
| realm_collection_ = collection; |
| return *this; |
| } |
| |
| RealmBuilder& RealmBuilder::SetRealmName(const std::string& name) { |
| realm_name_ = name; |
| return *this; |
| } |
| |
| RealmRoot RealmBuilder::Build(async_dispatcher_t* dispatcher) { |
| ZX_ASSERT_MSG(!realm_commited_, "Builder::Build() called after Realm already created"); |
| if (dispatcher == nullptr) { |
| dispatcher = async_get_default_dispatcher(); |
| } |
| ZX_ASSERT_MSG(dispatcher != nullptr, "Builder::Build() called without configured dispatcher"); |
| auto local_component_runner = runner_builder_->Build(dispatcher); |
| fuchsia::component::test::Builder_Build_Result result; |
| ZX_COMPONENT_ASSERT_STATUS_AND_RESULT_OK( |
| "Builder/Build", builder_proxy_->Build(local_component_runner->NewBinding(), &result), |
| result); |
| realm_commited_ = true; |
| |
| auto scoped_child = |
| realm_name_.has_value() |
| ? ScopedChild::New(realm_collection_, realm_name_.value(), |
| result.response().root_component_url, svc_) |
| : ScopedChild::New(realm_collection_, result.response().root_component_url, svc_); |
| |
| // Connect to fuchsia.component.Binder to automatically start Realm. |
| if (start_on_build_) { |
| scoped_child.ConnectSync<fuchsia::component::Binder>(); |
| } |
| |
| return RealmRoot(std::move(local_component_runner), std::move(scoped_child), dispatcher); |
| } |
| |
| Realm& RealmBuilder::root() { return root_; } |
| |
| RealmBuilder::RealmBuilder(std::shared_ptr<sys::ServiceDirectory> svc, |
| fuchsia::component::test::BuilderSyncPtr builder_proxy, |
| fuchsia::component::test::RealmSyncPtr test_realm_proxy) |
| : svc_(std::move(svc)), |
| builder_proxy_(std::move(builder_proxy)), |
| runner_builder_(std::make_shared<internal::LocalComponentRunner::Builder>()), |
| root_(Realm(std::move(test_realm_proxy), runner_builder_)) {} |
| |
| // Implementation methods for RealmRoot. |
| |
| RealmRoot::RealmRoot(std::unique_ptr<internal::LocalComponentRunner> local_component_runner, |
| ScopedChild root, async_dispatcher_t* dispatcher) |
| : local_component_runner_(std::move(local_component_runner)), |
| root_(std::move(root)), |
| dispatcher_(dispatcher) {} |
| |
| RealmRoot::~RealmRoot() = default; |
| |
| zx_status_t RealmRoot::Connect(const std::string& interface_name, zx::channel request) const { |
| return root_.Connect(interface_name, std::move(request)); |
| } |
| |
| std::string RealmRoot::GetChildName() const { return root_.GetChildName(); } |
| |
| void RealmRoot::Teardown(ScopedChild::TeardownCallback on_teardown_complete) { |
| root_.Teardown(dispatcher_, std::move(on_teardown_complete)); |
| } |
| |
| ScopedChild& RealmRoot::component() { return root_; } |
| |
| const ScopedChild& RealmRoot::component() const { return root_; } |
| |
| } // namespace component_testing |