| // 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 <cstddef> |
| #include <memory> |
| #include <optional> |
| #include <sstream> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| namespace component_testing { |
| namespace { |
| constexpr char kCollectionName[] = "realm_builder"; |
| 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, |
| 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; |
| } |
| Realm& Realm::AddLegacyChild(const std::string& child_name, const std::string& url, |
| ChildOptions options) { |
| fuchsia::component::test::Realm_AddLegacyChild_Result result; |
| ZX_COMPONENT_ASSERT_STATUS_AND_RESULT_OK( |
| "Realm/AddLegacyChild", |
| realm_proxy_->AddLegacyChild(child_name, url, internal::ConvertToFidl(options), &result), |
| result); |
| return *this; |
| } |
| Realm& Realm::AddLocalChild(const std::string& child_name, LocalComponent* local_impl, |
| ChildOptions options) { |
| ZX_SYS_ASSERT_NOT_NULL(local_impl); |
| runner_builder_->Register(GetResolvedName(child_name), 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, 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::ReplaceConfigValue(const std::string& name, const std::string& key, |
| ConfigValue value) { |
| fuchsia::component::test::Realm_ReplaceConfigValue_Result result; |
| ZX_COMPONENT_ASSERT_STATUS_AND_RESULT_OK( |
| "Realm/ReplaceConfigValue", |
| realm_proxy_->ReplaceConfigValue(name, key, value.TakeAsFidl(), &result), result); |
| return *this; |
| } |
| |
| 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 relative_url, |
| std::shared_ptr<sys::ServiceDirectory> svc) { |
| return CreateImpl(relative_url, std::move(svc)); |
| } |
| |
| RealmBuilder RealmBuilder::CreateImpl(cpp17::optional<std::string_view> relative_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); |
| exposed_dir.Connect(factory_proxy.NewRequest()); |
| fuchsia::component::test::BuilderSyncPtr builder_proxy; |
| fuchsia::component::test::RealmSyncPtr test_realm_proxy; |
| if (relative_url.has_value()) { |
| ZX_ASSERT_MSG(!relative_url.value().empty(), "relative_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(), relative_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, |
| 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; |
| } |
| |
| RealmBuilder& RealmBuilder::AddLegacyChild(const std::string& child_name, const std::string& url, |
| 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_.AddLegacyChild(child_name, url, options); |
| return *this; |
| } |
| |
| RealmBuilder& RealmBuilder::AddLocalChild(const std::string& child_name, LocalComponent* local_impl, |
| 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_.AddLocalChild(child_name, local_impl, options); |
| return *this; |
| } |
| |
| Realm RealmBuilder::AddChildRealm(const std::string& child_name, 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::ReplaceConfigValue(const std::string& name, const std::string& key, |
| ConfigValue value) { |
| root_.ReplaceConfigValue(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(); } |
| |
| RealmRoot RealmBuilder::Build(async_dispatcher* dispatcher) { |
| if (dispatcher == nullptr) { |
| dispatcher = async_get_default_dispatcher(); |
| } |
| ZX_ASSERT_MSG(dispatcher != nullptr, "Builder::Build() called without configured dispatcher"); |
| ZX_ASSERT_MSG(!realm_commited_, "Builder::Build() called after Realm already created"); |
| 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 = ScopedChild::New(kCollectionName, result.response().root_component_url, svc_); |
| // Connect to fuchsia.component.Binder to automatically start Realm. |
| scoped_child.ConnectSync<fuchsia::component::Binder>(); |
| // Make destructor async so that test teardown is not blocked on calls to |
| // fuchsia.component/Realm.DestroyChild. |
| scoped_child.MakeTeardownAsync(dispatcher); |
| |
| return RealmRoot(std::move(local_component_runner), std::move(scoped_child)); |
| } |
| |
| 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) |
| : local_component_runner_(std::move(local_component_runner)), root_(std::move(root)) {} |
| |
| 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(); } |
| |
| } // namespace component_testing |