| // Copyright 2018 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/sys/cpp/fidl.h> |
| #include <fuchsia/sys/internal/cpp/fidl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async/default.h> |
| #include <lib/async/dispatcher.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/fdio/spawn.h> |
| #include <lib/fidl/cpp/binding_set.h> |
| #include <lib/sys/cpp/file_descriptor.h> |
| #include <lib/sys/cpp/service_directory.h> |
| #include <lib/sys/cpp/testing/enclosing_environment.h> |
| #include <lib/sys/cpp/testing/test_with_environment.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <zircon/errors.h> |
| #include <zircon/process.h> |
| #include <zircon/rights.h> |
| #include <zircon/status.h> |
| |
| #include <fstream> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include <fidl/examples/echo/cpp/fidl.h> |
| #include <fs/pseudo_dir.h> |
| #include <fs/synchronous_vfs.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include <test/appmgr/integration/cpp/fidl.h> |
| |
| #include "fuchsia/testing/appmgr/cpp/fidl.h" |
| #include "lib/vfs/cpp/service.h" |
| #include "src/lib/files/file.h" |
| #include "src/lib/files/glob.h" |
| #include "src/lib/files/scoped_temp_dir.h" |
| #include "src/lib/fsl/handles/object_info.h" |
| #include "src/lib/fxl/strings/substitute.h" |
| #include "src/sys/appmgr/component_controller_impl.h" |
| #include "src/sys/appmgr/integration_tests/util/data_file_reader_writer_util.h" |
| #include "src/sys/appmgr/realm.h" |
| #include "src/sys/appmgr/util.h" |
| |
| namespace component { |
| namespace { |
| |
| using fuchsia::sys::TerminationReason; |
| |
| using ::testing::AnyOf; |
| using ::testing::Eq; |
| |
| using sys::testing::EnclosingEnvironment; |
| using sys::testing::TestWithEnvironment; |
| using test::appmgr::integration::DataFileReaderWriterPtr; |
| |
| class RealmTest : virtual public TestWithEnvironment { |
| protected: |
| void SetUp() override { |
| TestWithEnvironment::SetUp(); |
| OpenNewOutFile(); |
| } |
| |
| void OpenNewOutFile() { |
| ASSERT_TRUE(tmp_dir_.NewTempFile(&out_file_)); |
| outf_ = fileno(std::fopen(out_file_.c_str(), "w")); |
| } |
| |
| std::string ReadOutFile() { |
| std::string out; |
| if (!files::ReadFileToString(out_file_, &out)) { |
| FX_LOGS(ERROR) << "Could not read output file " << out_file_; |
| return ""; |
| } |
| return out; |
| } |
| |
| fuchsia::sys::LaunchInfo CreateLaunchInfo(const std::string& url, |
| zx::channel directory_request = zx::channel(), |
| const std::vector<std::string>& args = {}) { |
| fuchsia::sys::LaunchInfo launch_info; |
| launch_info.url = url; |
| launch_info.arguments = args; |
| if (directory_request.is_valid()) { |
| launch_info.directory_request = std::move(directory_request); |
| } |
| launch_info.out = sys::CloneFileDescriptor(outf_); |
| launch_info.err = sys::CloneFileDescriptor(STDERR_FILENO); |
| return launch_info; |
| } |
| |
| fuchsia::sys::ComponentControllerPtr RunComponent(EnclosingEnvironment* enclosing_environment, |
| const std::string& url, |
| zx::channel directory_request = zx::channel(), |
| const std::vector<std::string>& args = {}) { |
| return enclosing_environment->CreateComponent( |
| CreateLaunchInfo(url, std::move(directory_request), std::move(args))); |
| } |
| |
| private: |
| files::ScopedTempDir tmp_dir_; |
| std::string out_file_; |
| int outf_; |
| }; |
| |
| constexpr char kRealm[] = "realmintegrationtest"; |
| |
| TEST_F(RealmTest, Resolve) { |
| auto enclosing_environment = CreateNewEnclosingEnvironment(kRealm, CreateServices()); |
| |
| auto resolver = enclosing_environment->ConnectToService<fuchsia::process::Resolver>(); |
| |
| bool wait = false; |
| resolver->Resolve( |
| "fuchsia-pkg://fuchsia.com/appmgr_integration_tests#bin/" |
| "appmgr_realm_integration_tests", |
| [&wait](zx_status_t status, zx::vmo binary, |
| fidl::InterfaceHandle<fuchsia::ldsvc::Loader> loader) { |
| wait = true; |
| |
| ASSERT_EQ(ZX_OK, status); |
| |
| std::string expect; |
| files::ReadFileToString("/pkg/bin/appmgr_realm_integration_tests", &expect); |
| ASSERT_FALSE(expect.empty()); |
| |
| std::vector<char> buf(expect.length()); |
| ASSERT_EQ(ZX_OK, binary.read(buf.data(), 0, buf.size())); |
| std::string actual(buf.begin(), buf.end()); |
| |
| ASSERT_EQ(expect, actual); |
| }); |
| RunLoopUntil([&wait] { return wait; }); |
| } |
| |
| TEST_F(RealmTest, LaunchNonExistentComponent) { |
| auto env_services = CreateServices(); |
| auto enclosing_environment = CreateNewEnclosingEnvironment(kRealm, std::move(env_services)); |
| WaitForEnclosingEnvToStart(enclosing_environment.get()); |
| |
| // try to launch file url. |
| auto controller1 = RunComponent(enclosing_environment.get(), "does_not_exist"); |
| bool wait = false; |
| controller1.events().OnTerminated = [&wait](int64_t return_code, |
| fuchsia::sys::TerminationReason reason) { |
| wait = true; |
| EXPECT_EQ(reason, fuchsia::sys::TerminationReason::PACKAGE_NOT_FOUND); |
| }; |
| RunLoopUntil([&wait] { return wait; }); |
| |
| // try to launch pkg url. |
| auto controller2 = RunComponent(enclosing_environment.get(), |
| "fuchsia-pkg://fuchsia.com/does_not_exist#meta/some.cmx"); |
| wait = false; |
| controller2.events().OnTerminated = [&wait](int64_t return_code, |
| fuchsia::sys::TerminationReason reason) { |
| wait = true; |
| EXPECT_EQ(reason, fuchsia::sys::TerminationReason::PACKAGE_NOT_FOUND); |
| }; |
| RunLoopUntil([&wait] { return wait; }); |
| } |
| |
| // This test exercises the fact that two components should be in separate jobs, |
| // and thus when one component controller kills its job due to a .Kill() call |
| // the other component should run uninterrupted. |
| TEST_F(RealmTest, CreateTwoKillOne) { |
| // launch component as a service. |
| auto env_services = CreateServices(); |
| ASSERT_EQ(ZX_OK, env_services->AddServiceWithLaunchInfo( |
| CreateLaunchInfo("fuchsia-pkg://fuchsia.com/" |
| "echo_server_cpp#meta/echo_server_cpp.cmx"), |
| fidl::examples::echo::Echo::Name_)); |
| auto enclosing_environment = CreateNewEnclosingEnvironment(kRealm, std::move(env_services)); |
| WaitForEnclosingEnvToStart(enclosing_environment.get()); |
| // launch component normally |
| auto controller1 = |
| RunComponent(enclosing_environment.get(), |
| "fuchsia-pkg://fuchsia.com/echo_server_cpp#meta/echo_server_cpp.cmx"); |
| |
| // make sure echo service is running. |
| fidl::examples::echo::EchoPtr echo; |
| enclosing_environment->ConnectToService(echo.NewRequest()); |
| const std::string message = "CreateTwoKillOne"; |
| fidl::StringPtr ret_msg; |
| echo->EchoString(message, [&](::fidl::StringPtr retval) { ret_msg = retval; }); |
| RunLoopUntil([&] { return ret_msg.has_value(); }); |
| |
| // Kill one of the two components, make sure it's exited via Wait |
| bool wait = false; |
| controller1.events().OnTerminated = |
| [&wait](int64_t return_code, fuchsia::sys::TerminationReason reason) { wait = true; }; |
| controller1->Kill(); |
| RunLoopUntil([&wait] { return wait; }); |
| |
| // Make sure the second component is still running. |
| ret_msg.reset(); |
| echo->EchoString(message, [&](::fidl::StringPtr retval) { ret_msg = retval; }); |
| RunLoopUntil([&] { return ret_msg.has_value(); }); |
| } |
| |
| TEST_F(RealmTest, KillRealmKillsComponent) { |
| auto env_services = CreateServices(); |
| ASSERT_EQ(ZX_OK, env_services->AddServiceWithLaunchInfo( |
| CreateLaunchInfo("fuchsia-pkg://fuchsia.com/" |
| "echo_server_cpp#meta/echo_server_cpp.cmx"), |
| fidl::examples::echo::Echo::Name_)); |
| auto enclosing_environment = CreateNewEnclosingEnvironment(kRealm, std::move(env_services)); |
| WaitForEnclosingEnvToStart(enclosing_environment.get()); |
| |
| // make sure echo service is running. |
| fidl::examples::echo::EchoPtr echo; |
| enclosing_environment->ConnectToService(echo.NewRequest()); |
| const std::string message = "CreateTwoKillOne"; |
| fidl::StringPtr ret_msg; |
| echo->EchoString(message, [&](::fidl::StringPtr retval) { ret_msg = retval; }); |
| RunLoopUntil([&] { return ret_msg.has_value(); }); |
| |
| bool killed = false; |
| echo.set_error_handler([&](zx_status_t status) { killed = true; }); |
| enclosing_environment->Kill(); |
| RunLoopUntil([&] { return enclosing_environment->is_running(); }); |
| // send a msg, without that error handler won't be called. |
| echo->EchoString(message, [&](::fidl::StringPtr retval) { ret_msg = retval; }); |
| RunLoopUntil([&] { return killed; }); |
| } |
| |
| // test that service is connected even when realm dies right after connect request. |
| TEST_F(RealmTest, ConnectToServiceWhenRealmDies) { |
| auto env_services = CreateServices(); |
| bool connected = false; |
| auto fake_service = std::make_shared<vfs::Service>( |
| [&](zx::channel client_handle, async_dispatcher_t* /*unused*/) mutable { connected = true; }); |
| env_services->AddSharedService(fake_service, fidl::examples::echo::Echo::Name_); |
| |
| auto enclosing_environment = CreateNewEnclosingEnvironment(kRealm, std::move(env_services)); |
| WaitForEnclosingEnvToStart(enclosing_environment.get()); |
| |
| // first make sure that environment is running and can process requests. |
| fidl::examples::echo::EchoPtr echo; |
| enclosing_environment->ConnectToService(echo.NewRequest()); |
| RunLoopUntil([&] { return connected; }); |
| |
| // reset connection and now connect and kill the environment. |
| connected = false; |
| enclosing_environment->ConnectToService(echo.NewRequest()); |
| bool killed = false; |
| // kill enclosing env |
| enclosing_environment->Kill([&]() { killed = true; }); |
| RunLoopUntil([&] { return killed; }); |
| // make sure we can receive connect request. |
| RunLoopUntil([&] { return connected; }); |
| } |
| |
| TEST_F(RealmTest, EnvironmentControllerRequired) { |
| fuchsia::sys::EnvironmentPtr env; |
| real_env()->CreateNestedEnvironment(env.NewRequest(), /* controller = */ nullptr, kRealm, |
| /* additional_services = */ nullptr, |
| fuchsia::sys::EnvironmentOptions{}); |
| |
| zx_status_t env_status = ZX_OK; |
| env.set_error_handler([&](zx_status_t status) { env_status = status; }); |
| |
| RunLoopUntil([&] { return env_status != ZX_OK; }); |
| } |
| |
| TEST_F(RealmTest, EnvironmentLabelMustBeUnique) { |
| // Create first environment with label kRealm using EnclosingEnvironment since |
| // that's easy. |
| auto enclosing_environment = CreateNewEnclosingEnvironment(kRealm, CreateServices()); |
| |
| // Can't use EnclosingEnvironment here since there's no way to discern between |
| // 'not yet created' and 'failed to create'. This also lets us check the |
| // specific status returned. |
| fuchsia::sys::EnvironmentPtr env; |
| fuchsia::sys::EnvironmentControllerPtr env_controller; |
| |
| zx_status_t env_status, env_controller_status; |
| env.set_error_handler([&](zx_status_t status) { env_status = status; }); |
| env_controller.set_error_handler([&](zx_status_t status) { env_controller_status = status; }); |
| |
| // Same environment label as EnclosingEnvironment created above. |
| real_env()->CreateNestedEnvironment(env.NewRequest(), env_controller.NewRequest(), kRealm, |
| nullptr, fuchsia::sys::EnvironmentOptions{}); |
| |
| RunLoopUntil([&] { return env_status == ZX_ERR_BAD_STATE; }); |
| RunLoopUntil([&] { return env_controller_status == ZX_ERR_BAD_STATE; }); |
| } |
| |
| TEST_F(RealmTest, RealmJobProvider) { |
| fuchsia::sys::JobProviderSyncPtr ptr; |
| fdio_service_connect("/hub/job", ptr.NewRequest().TakeChannel().release()); |
| |
| zx::job job; |
| ASSERT_EQ(ZX_OK, ptr->GetJob(&job)); |
| |
| // check that we can get properties. |
| char name[ZX_MAX_NAME_LEN]; |
| ASSERT_EQ(ZX_OK, job.get_property(ZX_PROP_NAME, name, sizeof(name))); |
| |
| EXPECT_THAT(name, ::testing::StartsWith("test_env")); |
| zx_koid_t koid; |
| size_t actual; |
| size_t avail_count; |
| ASSERT_EQ(ZX_OK, |
| job.get_info(ZX_INFO_JOB_CHILDREN, &koid, sizeof(zx_koid_t), &actual, &avail_count)); |
| ASSERT_EQ(1u, actual); |
| |
| // check that we can get children |
| zx::job child_job; |
| ASSERT_EQ(ZX_OK, job.get_child(koid, ZX_RIGHT_SAME_RIGHTS, &child_job)); |
| } |
| |
| TEST_F(RealmTest, RealmDiesWhenItsJobDies) { |
| auto env_services = CreateServices(); |
| |
| ASSERT_EQ(ZX_OK, env_services->AddServiceWithLaunchInfo( |
| CreateLaunchInfo("fuchsia-pkg://fuchsia.com/" |
| "echo_server_cpp#meta/echo_server_cpp.cmx"), |
| fidl::examples::echo::Echo::Name_)); |
| auto enclosing_environment = CreateNewEnclosingEnvironment(kRealm, std::move(env_services)); |
| WaitForEnclosingEnvToStart(enclosing_environment.get()); |
| |
| // make sure echo service is running. |
| fidl::examples::echo::EchoPtr echo; |
| enclosing_environment->ConnectToService(echo.NewRequest()); |
| const std::string message = "some_msg"; |
| fidl::StringPtr ret_msg; |
| echo->EchoString(message, [&](::fidl::StringPtr retval) { ret_msg = retval; }); |
| RunLoopUntil([&] { return ret_msg.has_value(); }); |
| |
| fuchsia::sys::JobProviderSyncPtr ptr; |
| files::Glob glob(std::string("/hub/r/") + kRealm + "/*/job"); |
| ASSERT_EQ(1u, glob.size()); |
| const std::string path = *glob.begin(); |
| fdio_service_connect(path.c_str(), ptr.NewRequest().TakeChannel().release()); |
| |
| zx::job job; |
| EXPECT_EQ(ZX_OK, ptr->GetJob(&job)); |
| ASSERT_EQ(ZX_OK, job.kill()); |
| |
| RunLoopUntil([&] { return !enclosing_environment->is_running(); }); |
| } |
| |
| TEST_F(RealmTest, EmptyRealmDiesWhenItsJobDies) { |
| auto env_services = CreateServices(); |
| |
| auto enclosing_environment = CreateNewEnclosingEnvironment(kRealm, std::move(env_services)); |
| WaitForEnclosingEnvToStart(enclosing_environment.get()); |
| |
| fuchsia::sys::JobProviderSyncPtr ptr; |
| files::Glob glob(std::string("/hub/r/") + kRealm + "/*/job"); |
| ASSERT_EQ(1u, glob.size()); |
| const std::string path = *glob.begin(); |
| fdio_service_connect(path.c_str(), ptr.NewRequest().TakeChannel().release()); |
| |
| zx::job job; |
| EXPECT_EQ(ZX_OK, ptr->GetJob(&job)); |
| ASSERT_EQ(ZX_OK, job.kill()); |
| |
| RunLoopUntil([&] { return !enclosing_environment->is_running(); }); |
| } |
| |
| TEST_F(RealmTest, KillWorks) { |
| auto env_services = CreateServices(); |
| |
| auto enclosing_environment = CreateNewEnclosingEnvironment(kRealm, std::move(env_services)); |
| WaitForEnclosingEnvToStart(enclosing_environment.get()); |
| |
| std::string hub_path = std::string("/hub/r/") + kRealm; |
| // make sure realm was created |
| files::Glob glob1(hub_path); |
| ASSERT_EQ(1u, glob1.size()); |
| |
| bool killed = false; |
| enclosing_environment->Kill([&] { killed = true; }); |
| RunLoopUntil([&] { return killed; }); |
| |
| // make sure realm was really killed |
| files::Glob glob2(hub_path); |
| ASSERT_EQ(0u, glob2.size()); |
| } |
| |
| /// this will remove hash from the url and return un-hashed version. |
| /// example: |
| /// `url`:"fuchsia-pkg://fuchsia.com/my-pkg?hash=3204f2f24920e55bfbcb9c3a058ec2869f229b18d00ef1049ec3f47e5b7e4351#meta/my-component.cmx" |
| /// returns "fuchsia-pkg://fuchsia.com/my-pkg#meta/my-component.cmx" |
| std::string GetUnhashedUrl(const std::string& url) { |
| component::FuchsiaPkgUrl furl; |
| furl.Parse(url); |
| return fxl::Substitute("fuchsia-pkg://$0/$1#$2", furl.host_name(), furl.package_name(), |
| furl.resource_path()); |
| } |
| |
| class RealmCrashIntrospectTest : public RealmTest { |
| public: |
| using CrashIntrospect_FindComponentByThreadKoid_Result = |
| fuchsia::sys::internal::CrashIntrospect_FindComponentByThreadKoid_Result; |
| |
| void SetUp() override { |
| RealmTest::SetUp(); |
| real_services()->Connect(introspect_.NewRequest()); |
| ASSERT_TRUE(files::ReadFileToString("hub/name", ¤t_realm_name_)); |
| } |
| |
| CrashIntrospect_FindComponentByThreadKoid_Result FindComponent(zx_koid_t thread_koid) { |
| CrashIntrospect_FindComponentByThreadKoid_Result result; |
| auto status = introspect_->FindComponentByThreadKoid(thread_koid, &result); |
| FX_CHECK(ZX_OK == status) << "status: " << status; |
| return result; |
| } |
| static zx_koid_t GetKoid(const zx::object_base& obj) { return fsl::GetKoid(obj.get()); } |
| |
| static zx_koid_t GetCurrentProcessKoid() { |
| auto koid = GetKoid(*zx::process::self()); |
| ZX_DEBUG_ASSERT(koid != ZX_KOID_INVALID); |
| return koid; |
| } |
| |
| const std::string& current_realm_name() const { return current_realm_name_; } |
| |
| private: |
| fuchsia::sys::internal::CrashIntrospectSyncPtr introspect_; |
| std::string current_realm_name_; |
| }; |
| |
| // This tests that the service is not available in environment which do not explicitly include it |
| // from parent environment. |
| // This test's cmx includes this service that so we are able to indirectly test that inheriting this |
| // service works. |
| TEST_F(RealmCrashIntrospectTest, CrashServiceNotAvailableInAllEnvironments) { |
| auto env_services = CreateServices(); |
| auto enclosing_environment = CreateNewEnclosingEnvironment(kRealm, std::move(env_services)); |
| WaitForEnclosingEnvToStart(enclosing_environment.get()); |
| |
| fuchsia::sys::internal::CrashIntrospectPtr introspect; |
| enclosing_environment->ConnectToService(introspect.NewRequest()); |
| bool done = false; |
| introspect.set_error_handler([&](zx_status_t status) { |
| done = true; |
| ASSERT_EQ(ZX_ERR_PEER_CLOSED, status); |
| }); |
| RunLoopUntil([&]() { return done; }); |
| } |
| |
| // pass job's koid so that we can test ZX_ERR_NOT_FOUND. |
| TEST_F(RealmCrashIntrospectTest, InvalidProcessId) { |
| auto job = zx::job::default_job(); |
| auto koid = GetKoid(*job); |
| auto result = FindComponent(koid); |
| ASSERT_TRUE(result.is_err()); |
| EXPECT_EQ(ZX_ERR_NOT_FOUND, result.err()); |
| } |
| |
| // TODO(fxbug.dev/57032): re-enable once we can intercept the exception after appmgr, but before the |
| // platform's exception handling. |
| TEST_F(RealmCrashIntrospectTest, DISABLED_ComponentUrlForNewCrashingProcess) { |
| const char* command_argv[] = {"/pkg/bin/crashing_process", nullptr}; |
| |
| auto job = zx::job::default_job(); |
| |
| zx::process process; |
| |
| ASSERT_EQ(ZX_OK, fdio_spawn(job->get(), FDIO_SPAWN_CLONE_ALL, |
| command_argv[0], // path |
| command_argv, process.reset_and_get_address())); |
| auto koid = GetKoid(process); |
| // TODO(fxbug.dev/51382): Remove these logs. Added for debugging incase process never crashes. |
| FX_LOGS(INFO) << "Waiting for process to die."; |
| process.wait_one(ZX_TASK_TERMINATED, zx::time::infinite(), nullptr); |
| FX_LOGS(INFO) << "Process died."; |
| auto result = FindComponent(koid); |
| ASSERT_FALSE(result.is_err()) << zx_status_get_string(result.err()); |
| auto& response = result.response().component_info; |
| |
| EXPECT_EQ(GetUnhashedUrl(response.component_url()), |
| "fuchsia-pkg://fuchsia.com/appmgr_integration_tests#meta/" |
| "appmgr_realm_integration_tests.cmx"); |
| std::vector<std::string> expected = {"app", "sys", current_realm_name()}; |
| EXPECT_EQ(response.realm_path(), expected); |
| EXPECT_EQ(response.instance_id(), std::to_string(GetCurrentProcessKoid())); |
| |
| // we should only be able to retrieve it once. |
| result = FindComponent(koid); |
| ASSERT_TRUE(result.is_err()); |
| EXPECT_EQ(ZX_ERR_NOT_FOUND, result.err()); |
| } |
| |
| // TODO(fxbug.dev/57032): re-enable once we can intercept the exception after appmgr, but before the |
| // platform's exception handling. |
| TEST_F(RealmCrashIntrospectTest, DISABLED_ComponentUrlForNewComponentInCurrentEnv) { |
| zx::channel request; |
| auto component_svc = sys::ServiceDirectory::CreateWithRequest(&request); |
| auto launch_info = CreateLaunchInfo( |
| "fuchsia-pkg://fuchsia.com/appmgr_integration_tests#meta/crashing_component.cmx", |
| std::move(request)); |
| fuchsia::sys::ComponentControllerPtr controller; |
| launcher_ptr()->CreateComponent(std::move(launch_info), controller.NewRequest()); |
| bool component_started = false; |
| controller.events().OnDirectoryReady = [&] { component_started = true; }; |
| |
| RunLoopUntil([&] { return component_started; }); |
| |
| files::Glob glob("/hub/c/crashing_component.cmx/*/process-id"); |
| ASSERT_EQ(1u, glob.size()); |
| |
| std::string process_koid; |
| ASSERT_TRUE(files::ReadFileToString(*glob.begin(), &process_koid)); |
| |
| bool component_died = false; |
| controller.events().OnTerminated = [&](int64_t err, TerminationReason r) { |
| component_died = true; |
| }; |
| fuchsia::testing::appmgr::CrashInducerPtr crash_srv; |
| component_svc->Connect(crash_srv.NewRequest()); |
| crash_srv->Crash(); |
| // TODO(fxbug.dev/51382): Remove these logs. Added for debugging incase process never crashes. |
| FX_LOGS(INFO) << "Waiting for component to die."; |
| RunLoopUntil([&] { return component_died; }); |
| FX_LOGS(INFO) << "Component died."; |
| |
| auto result = FindComponent(std::stoi(process_koid)); |
| ASSERT_FALSE(result.is_err()) << zx_status_get_string(result.err()); |
| auto& response = result.response().component_info; |
| |
| EXPECT_EQ(response.component_url(), |
| "fuchsia-pkg://fuchsia.com/appmgr_integration_tests#meta/crashing_component.cmx"); |
| std::vector<std::string> expected = {"app", "sys", current_realm_name()}; |
| EXPECT_EQ(response.realm_path(), expected); |
| EXPECT_EQ(response.instance_id(), process_koid); |
| } |
| |
| // TODO(fxbug.dev/57032): re-enable once we can intercept the exception after appmgr, but before the |
| // platform's exception handling. |
| TEST_F(RealmCrashIntrospectTest, DISABLED_ComponentUrlForNewComponentInEnclosingEnv) { |
| auto env_services = CreateServices(); |
| std::string realm_label = "RealmCrashIntrospectTest"; |
| auto enclosing_environment = CreateNewEnclosingEnvironment(realm_label, std::move(env_services)); |
| WaitForEnclosingEnvToStart(enclosing_environment.get()); |
| zx::channel request; |
| auto component_svc = sys::ServiceDirectory::CreateWithRequest(&request); |
| auto controller = |
| RunComponent(enclosing_environment.get(), |
| "fuchsia-pkg://fuchsia.com/appmgr_integration_tests#meta/crashing_component.cmx", |
| std::move(request)); |
| |
| bool component_started = false; |
| controller.events().OnDirectoryReady = [&] { component_started = true; }; |
| RunLoopUntil([&] { return component_started; }); |
| |
| files::Glob glob(std::string("/hub/r/") + realm_label + |
| "/*/c/crashing_component.cmx/*/process-id"); |
| ASSERT_EQ(1u, glob.size()); |
| |
| std::string process_koid; |
| ASSERT_TRUE(files::ReadFileToString(*glob.begin(), &process_koid)); |
| |
| bool component_died = false; |
| controller.events().OnTerminated = [&](int64_t err, TerminationReason r) { |
| component_died = true; |
| }; |
| fuchsia::testing::appmgr::CrashInducerPtr crash_srv; |
| component_svc->Connect(crash_srv.NewRequest()); |
| crash_srv->Crash(); |
| // TODO(fxbug.dev/51382): Remove these logs. Added for debugging incase process never crashes. |
| FX_LOGS(INFO) << "Waiting for component to die."; |
| RunLoopUntil([&] { return component_died; }); |
| FX_LOGS(INFO) << "Component died."; |
| |
| auto result = FindComponent(std::stoi(process_koid)); |
| ASSERT_FALSE(result.is_err()) << zx_status_get_string(result.err()); |
| auto& response = result.response().component_info; |
| |
| EXPECT_EQ(response.component_url(), |
| "fuchsia-pkg://fuchsia.com/appmgr_integration_tests#meta/crashing_component.cmx"); |
| std::vector<std::string> expected = {"app", "sys", current_realm_name(), std::move(realm_label)}; |
| EXPECT_EQ(response.realm_path(), expected); |
| EXPECT_EQ(response.instance_id(), process_koid); |
| EXPECT_EQ(response.component_name(), "crashing_component.cmx"); |
| } |
| |
| // Create a test environment that includes additional functions for |
| // reading/writing data. |
| // |
| // We use virtual inheritance here to work around the LLVM compiler bug |
| // https://bugs.llvm.org/show_bug.cgi?id=35902 which generates |
| // a false-positive alignment alert. |
| class EnvironmentOptionsTest : public virtual RealmTest, |
| public virtual component::testing::DataFileReaderWriterUtil {}; |
| |
| TEST_F(EnvironmentOptionsTest, DeleteStorageOnDeath) { |
| constexpr char kTestFileName[] = "some-test-file"; |
| constexpr char kTestFileContent[] = "the-best-file-content"; |
| |
| // Create an environment with 'delete_storage_on_death' option enabled. |
| zx::channel request; |
| auto services = sys::ServiceDirectory::CreateWithRequest(&request); |
| DataFileReaderWriterPtr util; |
| auto enclosing_environment = CreateNewEnclosingEnvironment( |
| kRealm, CreateServices(), fuchsia::sys::EnvironmentOptions{.delete_storage_on_death = true}); |
| auto ctrl = RunComponent(enclosing_environment.get(), |
| "fuchsia-pkg://fuchsia.com/persistent_storage_test_util#meta/util.cmx", |
| std::move(request)); |
| services->Connect(util.NewRequest()); |
| |
| // Write some arbitrary file content into the test util's "/data" dir, and |
| // verify that we can read it back. |
| ASSERT_EQ(WriteFileSync(util, kTestFileName, kTestFileContent), ZX_OK); |
| ASSERT_EQ(ReadFileSync(util, kTestFileName).value_or(""), kTestFileContent); |
| |
| // Kill the environment, which should automatically delete any persistent |
| // storage it owns. |
| bool killed = false; |
| enclosing_environment->Kill([&] { killed = true; }); |
| RunLoopUntil([&] { return killed; }); |
| |
| // Recreate the environment and component using the same environment label. |
| services = sys::ServiceDirectory::CreateWithRequest(&request); |
| enclosing_environment = CreateNewEnclosingEnvironment(kRealm, CreateServices()); |
| ctrl = RunComponent(enclosing_environment.get(), |
| "fuchsia-pkg://fuchsia.com/persistent_storage_test_util#meta/util.cmx", |
| std::move(request)); |
| services->Connect(util.NewRequest()); |
| |
| // Verify that the file no longer exists. |
| EXPECT_FALSE(ReadFileSync(util, kTestFileName).has_value()); |
| } |
| |
| using LabelAndValidity = std::tuple<std::string, bool>; |
| class EnvironmentLabelTest : public RealmTest, |
| public ::testing::WithParamInterface<LabelAndValidity> {}; |
| |
| TEST_P(EnvironmentLabelTest, CheckLabelValidity) { |
| // Can't use EnclosingEnvironment here since there's no way to discern between |
| // 'not yet created' and 'failed to create'. This also lets use check the |
| // specific status returned. |
| fuchsia::sys::EnvironmentPtr env; |
| fuchsia::sys::EnvironmentControllerPtr env_controller; |
| |
| zx_status_t env_status = ZX_OK; |
| zx_status_t env_controller_status = ZX_OK; |
| bool env_created = false; |
| env.set_error_handler([&](zx_status_t status) { env_status = status; }); |
| env_controller.set_error_handler([&](zx_status_t status) { env_controller_status = status; }); |
| env_controller.events().OnCreated = [&] { env_created = true; }; |
| |
| auto [label, label_valid] = GetParam(); |
| real_env()->CreateNestedEnvironment(env.NewRequest(), env_controller.NewRequest(), label, |
| /* additional_services = */ nullptr, |
| fuchsia::sys::EnvironmentOptions{}); |
| |
| if (label_valid) { |
| RunLoopUntil([&] { return env_created; }); |
| } else { |
| RunLoopUntil([&] { return env_status == ZX_ERR_INVALID_ARGS; }); |
| RunLoopUntil([&] { return env_controller_status == ZX_ERR_INVALID_ARGS; }); |
| EXPECT_FALSE(env_created); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| InvalidLabels, EnvironmentLabelTest, |
| ::testing::Combine(::testing::Values("", "a/b", "/", ".", "..", "../..", "\t", "\r", "ab\n", |
| std::string("123\0", 4), "\10", "\33", "\177", " ", |
| "my realm", "~", "`", "!", "@", "$", "%", "^", "&", "*", |
| "(", ")", "=", "+", "{", "}", "[", "]", "|", "?", ";", "'", |
| "\"", "<", ">", ",", |
| "fuchsia-pkg://fuchsia.com/abcd#meta/abcd.cmx"), |
| ::testing::Values(false))); |
| |
| INSTANTIATE_TEST_SUITE_P( |
| ValidLabels, EnvironmentLabelTest, |
| ::testing::Combine(::testing::Values("abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ", |
| "0123456789", "#-_:.", "my.realm", "my..realm", |
| "fuchsia-pkg:::fuchsia.com:abcd#meta:abcd.cmx"), |
| ::testing::Values(true))); |
| |
| class RealmFakeLoaderTest : public RealmTest, public fuchsia::sys::Loader { |
| protected: |
| RealmFakeLoaderTest() { |
| sys::testing::EnvironmentServices::ParentOverrides parent_overrides; |
| parent_overrides.loader_service_ = |
| std::make_shared<vfs::Service>([this](zx::channel channel, async_dispatcher_t* dispatcher) { |
| bindings_.AddBinding(this, |
| fidl::InterfaceRequest<fuchsia::sys::Loader>(std::move(channel))); |
| }); |
| enclosing_environment_ = CreateNewEnclosingEnvironment( |
| kRealm, CreateServicesWithParentOverrides(std::move(parent_overrides))); |
| } |
| |
| void LoadUrl(std::string url, LoadUrlCallback callback) override { |
| ASSERT_TRUE(component_url_.empty()); |
| component_url_ = url; |
| } |
| |
| void WaitForComponentLoad() { |
| RunLoopUntil([this] { return !component_url_.empty(); }); |
| } |
| |
| const std::string& component_url() const { return component_url_; } |
| |
| std::unique_ptr<EnclosingEnvironment> enclosing_environment_; |
| |
| private: |
| std::shared_ptr<vfs::Service> loader_service_; |
| fidl::BindingSet<fuchsia::sys::Loader> bindings_; |
| std::string component_url_; |
| }; |
| |
| TEST_F(RealmFakeLoaderTest, CreateInvalidComponent) { |
| TerminationReason reason = TerminationReason::UNKNOWN; |
| int64_t return_code = INT64_MAX; |
| auto controller = RunComponent(enclosing_environment_.get(), "garbage://test"); |
| controller.events().OnTerminated = [&](int64_t err, TerminationReason r) { |
| return_code = err; |
| reason = r; |
| }; |
| RunLoopUntil([&] { return return_code < INT64_MAX; }); |
| EXPECT_EQ(TerminationReason::URL_INVALID, reason); |
| EXPECT_EQ(-1, return_code); |
| } |
| |
| } // namespace |
| } // namespace component |