| // 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 "sandbox.h" |
| |
| #include <fuchsia/logger/cpp/fidl.h> |
| #include <lib/fit/sequencer.h> |
| #include <lib/fit/single_threaded_executor.h> |
| #include <lib/sys/cpp/termination_reason.h> |
| |
| #include <iostream> |
| #include <unordered_set> |
| |
| #include "lib/gtest/real_loop_fixture.h" |
| #include "log_listener_test_helpers.h" |
| #include "src/lib/fxl/strings/join_strings.h" |
| #include "src/lib/testing/predicates/status.h" |
| |
| static const char* kBusName = "test-bus"; |
| static const char* kBusClientName = "sandbox_unittest"; |
| |
| namespace netemul { |
| namespace testing { |
| |
| enum EventType { |
| Event, |
| OnClientAttached, |
| OnClientDetached, |
| }; |
| |
| using namespace fuchsia::netemul; |
| |
| class SandboxTest : public ::gtest::RealLoopFixture { |
| protected: |
| using TerminationReason = Sandbox::TerminationReason; |
| |
| void RunSandbox(SandboxResult::Status expect) { |
| Sandbox sandbox(std::move(sandbox_args_)); |
| bool done = false; |
| SandboxResult o_result; |
| |
| sandbox.SetRootEnvironmentCreatedCallback([this](ManagedEnvironment* env) { |
| if (log_event_) { |
| fidl::InterfaceHandle<fuchsia::logger::LogListenerSafe> listener; |
| log_listener_ = std::make_unique<TestListener>(listener.NewRequest()); |
| log_listener_->SetObserver( |
| [this](const fuchsia::logger::LogMessage& msg) { log_event_(msg); }); |
| fidl::InterfacePtr<fuchsia::logger::Log> log; |
| env->ConnectToService(fuchsia::logger::Log::Name_, log.NewRequest().TakeChannel()); |
| log->ListenSafe(std::move(listener), nullptr); |
| } |
| }); |
| |
| sandbox.SetServicesCreatedCallback([this, &sandbox]() { |
| if (connect_to_network_) { |
| ConnectToNetwork(&sandbox); |
| } |
| if (collect_events_) { |
| InstallEventCollection(&sandbox); |
| } |
| }); |
| |
| sandbox.SetTerminationCallback([&done, &o_result](SandboxResult result) { |
| FX_LOGS(INFO) << "Sandbox terminated with status: " << result; |
| o_result = std::move(result); |
| done = true; |
| }); |
| |
| sandbox.Start(dispatcher()); |
| |
| RunLoopUntil([&done]() { return done; }); |
| |
| // We quit the loop when sandbox terminates, |
| // but because some of the tests will look at services in the sandbox when |
| // we exit, we run the loop until idle to make sure the sandbox will have a |
| // last chance to read any events pending. |
| RunLoopUntilIdle(); |
| |
| // If we're expecting unspecified status, we will just check for expected |
| // failure. That's for failure cases that can have races on different |
| // failure points. |
| if (expect == SandboxResult::Status::UNSPECIFIED) { |
| EXPECT_FALSE(o_result.is_success()); |
| } else { |
| EXPECT_EQ(o_result.status(), expect); |
| } |
| } |
| |
| void RunSandboxSuccess() { RunSandbox(SandboxResult::SUCCESS); } |
| |
| void RunSandboxFailure() { RunSandbox(SandboxResult::TEST_FAILED); } |
| |
| void SetCmx(const std::string& cmx, bool disable_logging = true) { |
| ASSERT_TRUE(sandbox_args_.ParseFromString(cmx)); |
| if (disable_logging) { |
| // disable all syslog logging for unit tests. |
| sandbox_args_.config.environment().DisableLogging(true); |
| } |
| } |
| |
| void EnableEventCollection() { collect_events_ = true; } |
| void EnableNetworkService() { connect_to_network_ = true; } |
| void EnableLogCapture(fit::function<void(const fuchsia::logger::LogMessage&)> callback) { |
| log_event_ = std::move(callback); |
| } |
| |
| void CheckEvents(const std::vector<int32_t>& check) { |
| for (const auto& v : check) { |
| auto f = collected_codes_.find(v); |
| EXPECT_FALSE(f == collected_codes_.end()) << "Couldn't find event code " << v; |
| } |
| } |
| |
| bool PeekEvents(const std::unordered_set<int32_t>& check) { |
| for (const auto& v : check) { |
| auto f = collected_codes_.find(v); |
| if (f == collected_codes_.end()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool ObservedClient(const std::string& client) { |
| return observed_clients_.find(client) != observed_clients_.end(); |
| } |
| |
| bool ClientDetached(const std::string& client) { |
| return detached_clients_.find(client) != detached_clients_.end(); |
| } |
| |
| void SetOnEvent(fit::function<void(EventType)> on_event) { on_event_ = std::move(on_event); } |
| |
| const std::unordered_set<int32_t>& events() { return collected_codes_; } |
| |
| sync::BusPtr& bus() { return bus_; } |
| |
| network::NetworkManagerPtr& network_manager() { return net_manager_; } |
| |
| network::EndpointManagerPtr& endpoint_manager() { return endp_manager_; } |
| |
| SandboxArgs TakeArgs() { return std::move(sandbox_args_); } |
| |
| private: |
| void ConnectToNetwork(Sandbox* sandbox) { |
| std::cout << "Connected to network" << std::endl; |
| sandbox->sandbox_environment()->network_context().GetHandler()(net_ctx_.NewRequest()); |
| net_ctx_->GetNetworkManager(net_manager_.NewRequest()); |
| net_ctx_->GetEndpointManager(endp_manager_.NewRequest()); |
| } |
| |
| void InstallEventCollection(Sandbox* sandbox) { |
| // connect to bus manager: |
| sync::SyncManagerPtr syncManager; |
| sandbox->sandbox_environment()->sync_manager().GetHandler()(syncManager.NewRequest()); |
| syncManager->BusSubscribe(kBusName, kBusClientName, bus_.NewRequest()); |
| bus_.events().OnBusData = [this](sync::Event event) { |
| if (!event.has_code()) { |
| return; |
| } |
| auto code = event.code(); |
| std::cout << "Observed event " << code << std::endl; |
| // assert that code hasn't happened yet. |
| // given we're putting codes in a set, it's an invalid test setup |
| // to have child procs publish the same code multiple times |
| ASSERT_TRUE(collected_codes_.find(code) == collected_codes_.end()); |
| collected_codes_.insert(code); |
| if (on_event_) { |
| on_event_(EventType::Event); |
| } |
| }; |
| bus_.events().OnClientAttached = [this](fidl::StringPtr client) { |
| // Ensure no two clients with the same name get attached to bus, |
| // doing so may result in flaky tests due to timing |
| // this is here mostly to catch bad test setups |
| std::cout << "Observed client " << client << std::endl; |
| ASSERT_TRUE(client.has_value()); |
| ASSERT_TRUE(observed_clients_.find(client.value()) == observed_clients_.end()); |
| observed_clients_.insert(client.value()); |
| if (on_event_) { |
| on_event_(EventType::OnClientAttached); |
| } |
| }; |
| bus_.events().OnClientDetached = [this](fidl::StringPtr client) { |
| // just keep a record of detached clients |
| ASSERT_TRUE(client.has_value()); |
| detached_clients_.insert(client.value()); |
| if (on_event_) { |
| on_event_(EventType::OnClientDetached); |
| } |
| }; |
| } |
| |
| fit::function<void(EventType)> on_event_; |
| bool collect_events_ = false; |
| bool connect_to_network_ = false; |
| fit::function<void(const fuchsia::logger::LogMessage&)> log_event_; |
| SandboxArgs sandbox_args_; |
| std::unordered_set<int32_t> collected_codes_; |
| std::unordered_set<std::string> observed_clients_; |
| std::unordered_set<std::string> detached_clients_; |
| std::unique_ptr<TestListener> log_listener_; |
| sync::BusPtr bus_; |
| network::NetworkContextPtr net_ctx_; |
| network::NetworkManagerPtr net_manager_; |
| network::EndpointManagerPtr endp_manager_; |
| }; |
| |
| TEST_F(SandboxTest, SimpleSuccess) { |
| SetCmx(R"( |
| { |
| "environment" : { |
| "test" : [ "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx" ] |
| } |
| })"); |
| RunSandboxSuccess(); |
| } |
| |
| TEST_F(SandboxTest, MalformedFacet) { |
| SandboxArgs args; |
| ASSERT_FALSE(args.ParseFromString(R"( {bad, json} )")); |
| } |
| |
| TEST_F(SandboxTest, SimpleFailure) { |
| SetCmx(R"( |
| { |
| "default_url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "environment" : { |
| "test" : [ { "arguments": ["-f"] } ] |
| } |
| } |
| )"); |
| RunSandboxFailure(); |
| } |
| |
| TEST_F(SandboxTest, ConfirmOnBus) { |
| SetCmx(R"( |
| { |
| "default_url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "environment" : { |
| "test" : [ { "arguments": ["-p", "3"] } ] |
| } |
| } |
| )"); |
| EnableEventCollection(); |
| RunSandboxSuccess(); |
| CheckEvents({3}); |
| } |
| |
| TEST_F(SandboxTest, FastChildren) { |
| // Make root test wait so children exits first |
| SetCmx(R"( |
| { |
| "default_url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "environment" : { |
| "name" : "root", |
| "test" : [ { "arguments": ["-p", "1", "-w", "30"] } ], |
| "children" : [ |
| { |
| "name" : "child", |
| "test" : [{ |
| "arguments" : ["-p", "2", "-n", "child"] |
| }] |
| } |
| ] |
| } |
| } |
| )"); |
| EnableEventCollection(); |
| RunSandboxSuccess(); |
| CheckEvents({1, 2}); |
| } |
| |
| TEST_F(SandboxTest, FastRoot) { |
| // Make child test wait so root exits first |
| SetCmx(R"( |
| { |
| "default_url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "environment" : { |
| "name" : "root", |
| "test" : [ { "arguments": ["-p", "1"] } ], |
| "children" : [ |
| { |
| "name" : "child", |
| "test" : [{ |
| "arguments" : ["-p", "2", "-n", "child", "-w", "30"] |
| }] |
| } |
| ] |
| } |
| } |
| )"); |
| EnableEventCollection(); |
| RunSandboxSuccess(); |
| CheckEvents({1, 2}); |
| } |
| |
| TEST_F(SandboxTest, FailedSetupCausesFailure) { |
| SetCmx(R"( |
| { |
| "default_url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "environment" : { |
| "test" : [ { "arguments": ["-p", "1"] } ], |
| "setup" : [{ |
| "arguments" : ["-f"] |
| }] |
| } |
| } |
| )"); |
| EnableEventCollection(); |
| RunSandbox(SandboxResult::Status::SETUP_FAILED); |
| // root proc should not have run, so events should be empty |
| EXPECT_TRUE(events().empty()); |
| } |
| |
| TEST_F(SandboxTest, AppsAreLaunched) { |
| // Launch root waiting for event 100, responds with event 4 |
| // Launch 3 apps and observe that they ran |
| // then Signal root with event 100 |
| SetCmx(R"( |
| { |
| "default_url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "environment" : { |
| "test" : [ { "arguments": ["-e", "100", "-p", "4"] } ], |
| "apps" : [ |
| { |
| "arguments" : ["-n", "app1", "-p", "1"] |
| }, |
| { |
| "arguments" : ["-n", "app2", "-p", "2"] |
| }, |
| { |
| "arguments" : ["-n", "app3", "-p", "3"] |
| } |
| ] |
| } |
| } |
| )"); |
| SetOnEvent([this](EventType type) { |
| if (type == EventType::OnClientDetached) { |
| return; |
| } |
| // if all app events are seen and root is waiting for us |
| // unlock root with event code 100 |
| if (PeekEvents({1, 2, 3}) && ObservedClient("root")) { |
| sync::Event evt; |
| evt.set_code(100); |
| bus()->Publish(std::move(evt)); |
| } |
| }); |
| EnableEventCollection(); |
| RunSandboxSuccess(); |
| // all events must be there at the end |
| CheckEvents({1, 2, 3, 4}); |
| } |
| |
| TEST_F(SandboxTest, AppExitCodesAreIgnored) { |
| // Launch root waiting for event 100, responds with event 2 |
| // Launch app that published event 1 and will fail |
| // sandbox should ignore "app" exit codes |
| SetCmx(R"( |
| { |
| "default_url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "environment" : { |
| "test" : [ { "arguments": ["-e", "100", "-p", "2"] } ], |
| "apps" : [ |
| { |
| "arguments" : ["-n", "app1", "-p", "1", "-f"] |
| } |
| ] |
| } |
| } |
| )"); |
| SetOnEvent([this](EventType type) { |
| if (type == EventType::OnClientDetached) { |
| return; |
| } |
| // if all app events are seen and root is waiting for us |
| // unlock root with event code 100 |
| if (PeekEvents({1}) && ObservedClient("root")) { |
| sync::Event evt; |
| evt.set_code(100); |
| bus()->Publish(std::move(evt)); |
| } |
| }); |
| EnableEventCollection(); |
| RunSandboxSuccess(); |
| // all events must be there at the end |
| CheckEvents({1, 2}); |
| } |
| |
| TEST_F(SandboxTest, SetupProcsAreOperatedSequentially) { |
| SetCmx(R"( |
| { |
| "default_url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "environment" : { |
| "test" : [ { "arguments": ["-p", "4"] } ], |
| "setup" : [ |
| { |
| "arguments" : ["-p", "1", "-n", "setup1", "-w", "10"] |
| }, |
| { |
| "arguments" : ["-p", "2", "-n", "setup2", "-w", "5"] |
| }, |
| { |
| "arguments" : ["-p", "3", "-n", "setup3"] |
| } |
| ] |
| } |
| } |
| )"); |
| int counter = 0; |
| SetOnEvent([this, &counter](EventType type) { |
| if (type != EventType::Event) { |
| return; |
| } |
| counter++; |
| switch (counter) { |
| case 1: |
| EXPECT_TRUE(ObservedClient("setup1")); |
| CheckEvents({1}); |
| break; |
| case 2: |
| EXPECT_TRUE(ObservedClient("setup2")); |
| EXPECT_TRUE(ClientDetached("setup1")); |
| CheckEvents({1, 2}); |
| break; |
| case 3: |
| EXPECT_TRUE(ObservedClient("setup3")); |
| EXPECT_TRUE(ClientDetached("setup2")); |
| CheckEvents({1, 2, 3}); |
| break; |
| case 4: |
| EXPECT_TRUE(ObservedClient("root")); |
| EXPECT_TRUE(ClientDetached("setup3")); |
| CheckEvents({1, 2, 3}); |
| break; |
| default: |
| FAIL() << "counter should not have this value"; |
| } |
| }); |
| EnableEventCollection(); |
| RunSandboxSuccess(); |
| CheckEvents({1, 2, 3, 4}); |
| } |
| |
| TEST_F(SandboxTest, SetupRunsBeforeTest) { |
| SetCmx(R"( |
| { |
| "default_url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "environment" : { |
| "setup" : [ |
| {"arguments" : ["-p", "1", "-n", "setup1", "-w", "2"]} |
| ], |
| "test" : [ |
| {"arguments" : ["-p", "3", "-n", "test1"]}, |
| {"arguments" : ["-p", "2"]} |
| ] |
| } |
| } |
| )"); |
| int counter = 0; |
| SetOnEvent([this, &counter](EventType type) { |
| if (type != EventType::Event) { |
| return; |
| } |
| counter++; |
| switch (counter) { |
| case 1: |
| EXPECT_TRUE(ObservedClient("setup1")); |
| CheckEvents({1}); |
| EXPECT_FALSE(ObservedClient("test1")); |
| EXPECT_FALSE(ObservedClient("root")); |
| break; |
| default: |
| EXPECT_TRUE(ClientDetached("setup1")); |
| break; |
| } |
| }); |
| EnableEventCollection(); |
| RunSandboxSuccess(); |
| CheckEvents({1, 2, 3}); |
| } |
| |
| TEST_F(SandboxTest, SetupRunsBeforeTestOnChildren) { |
| // Tests that empty root environments with children that requires |
| // setup and tests will succeed. This is basically the same test setup |
| // as "SetupRunsBeforeTest" but with an empty root environment. |
| SetCmx(R"( |
| { |
| "default_url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "environment" : { |
| "children": [{ |
| "setup" : [ |
| {"arguments" : ["-p", "1", "-n", "setup1", "-w", "2"]} |
| ], |
| "test" : [ |
| {"arguments" : ["-p", "3", "-n", "test1"]}, |
| {"arguments" : ["-p", "2"]} |
| ] |
| }] |
| } |
| } |
| )"); |
| int counter = 0; |
| SetOnEvent([this, &counter](EventType type) { |
| if (type != EventType::Event) { |
| return; |
| } |
| counter++; |
| switch (counter) { |
| case 1: |
| EXPECT_TRUE(ObservedClient("setup1")); |
| CheckEvents({1}); |
| EXPECT_FALSE(ObservedClient("test1")); |
| EXPECT_FALSE(ObservedClient("root")); |
| break; |
| default: |
| EXPECT_TRUE(ClientDetached("setup1")); |
| break; |
| } |
| }); |
| EnableEventCollection(); |
| RunSandboxSuccess(); |
| CheckEvents({1, 2, 3}); |
| } |
| |
| TEST_F(SandboxTest, DuplicateNetworkNameFails) { |
| SetCmx(R"( |
| { |
| "networks" : [ |
| { |
| "name" : "net" |
| }, |
| { |
| "name" : "net" |
| } |
| ] |
| } |
| )"); |
| RunSandbox(SandboxResult::Status::NETWORK_CONFIG_FAILED); |
| } |
| |
| TEST_F(SandboxTest, DuplicateEndpointNameFails) { |
| SetCmx(R"( |
| { |
| "networks" : [ |
| { |
| "name" : "net1", |
| "endpoints" : [{ |
| "name" : "ep" |
| }] |
| }, |
| { |
| "name" : "net2", |
| "endpoints" : [{ |
| "name" : "ep" |
| }] |
| } |
| ] |
| } |
| )"); |
| RunSandbox(SandboxResult::Status::NETWORK_CONFIG_FAILED); |
| } |
| |
| TEST_F(SandboxTest, ValidNetworkSetup) { |
| // - Configures 2 networks with 2 endpoints each |
| // - waits for root process to start and then |
| // connects to network FIDL service to check |
| // that the networks and endpoints were |
| // created correctly |
| // - finally, tries to attach endpoints to network again |
| // to asses that they were correctly put in place |
| SetCmx(R"( |
| { |
| "default_url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "environment" : { |
| "test" : [ { "arguments": ["-e", "100", "-p", "1"] } ] |
| }, |
| "networks" : [ |
| { |
| "name" : "net1", |
| "endpoints" : [ |
| { "name" : "ep1" }, |
| { "name" : "ep2" } |
| ] |
| }, |
| { |
| "name" : "net2", |
| "endpoints" : [ |
| { "name" : "ep3" }, |
| { "name" : "ep4" } |
| ] |
| } |
| ] |
| } |
| )"); |
| EnableNetworkService(); |
| EnableEventCollection(); |
| std::vector<std::string> networks({"net1", "net2"}); |
| std::vector<std::string> endpoints({"ep1", "ep2", "ep3", "ep4"}); |
| std::vector<std::pair<int, std::string>> attachments({ |
| std::make_pair<int, std::string>(0, "ep1"), |
| std::make_pair<int, std::string>(0, "ep2"), |
| std::make_pair<int, std::string>(1, "ep3"), |
| std::make_pair<int, std::string>(1, "ep4"), |
| }); |
| std::vector<network::NetworkPtr> found_nets; |
| |
| auto nets = networks.begin(); |
| auto eps = endpoints.begin(); |
| auto attach = attachments.begin(); |
| |
| fit::function<void()> check; |
| // check will keep a reference to itself so |
| // it can recur. |
| // Plus, it'll keep a reference to the iterators |
| // so it runs all the checks over network, endpoint, and attachment |
| check = [&nets, &eps, &networks, &endpoints, &attach, &attachments, &check, &found_nets, this]() { |
| if (nets != networks.end()) { |
| // iterate over networks and check they're there |
| auto lookup = *nets++; |
| std::cout << "checking network " << lookup << std::endl; |
| network_manager()->GetNetwork( |
| lookup, [&check, &found_nets](fidl::InterfaceHandle<network::Network> net) { |
| ASSERT_TRUE(net.is_valid()); |
| // keep network for attachments check |
| found_nets.emplace_back(net.Bind()); |
| check(); |
| }); |
| } else if (eps != endpoints.end()) { |
| // iterate over endpoints and check they're there |
| auto lookup = *eps++; |
| std::cout << "checking endpoint " << lookup << std::endl; |
| endpoint_manager()->GetEndpoint(lookup, |
| [&check](fidl::InterfaceHandle<network::Endpoint> ep) { |
| ASSERT_TRUE(ep.is_valid()); |
| check(); |
| }); |
| } else if (attach != attachments.end()) { |
| // iterate over attachments and check they're there |
| auto& a = *attach++; |
| std::cout << "checking endpoint " << a.second << " is in network" << std::endl; |
| found_nets[a.first]->AttachEndpoint(a.second, [&check](zx_status_t status) { |
| ASSERT_STATUS(status, ZX_ERR_ALREADY_BOUND); |
| check(); |
| }); |
| } else { |
| sync::Event event; |
| event.set_code(100); |
| bus()->Publish(std::move(event)); |
| } |
| }; |
| |
| // when we get the client attached event for root, |
| // we call the check closure to run the tests. |
| // at the end of the check closure, it'll |
| // signal the root test with event code 100 |
| // and finish the test. |
| SetOnEvent([&check](EventType type) { |
| if (type != EventType::OnClientAttached) { |
| return; |
| } |
| check(); |
| }); |
| RunSandboxSuccess(); |
| CheckEvents({1}); |
| } |
| |
| TEST_F(SandboxTest, ManyTests) { |
| std::stringstream ss; |
| ss << R"({ "default_url" : "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "environment" : { "test" : [)"; |
| const int test_count = 10; |
| std::vector<int32_t> expect; |
| expect.reserve(test_count); |
| for (int i = 0; i < test_count; i++) { |
| if (i != 0) { |
| ss << ","; |
| } |
| ss << R"({"arguments":["-p",")" << i << R"(", "-n", "t)" << i << R"("]})"; |
| expect.push_back(i); |
| } |
| ss << "]}}"; |
| SetCmx(ss.str()); |
| EnableEventCollection(); |
| RunSandboxSuccess(); |
| CheckEvents(expect); |
| } |
| |
| TEST_F(SandboxTest, NoTestsIsFailedtest) { |
| // Even if we run setup stuff, |
| // if no |tests| are defined in any environments, we consider it a failure. |
| SetCmx(R"( |
| { |
| "default_url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "environment" : { |
| "setup" : [ |
| {"arguments" : ["-n", "setup1"]} |
| ], |
| "test" : [] |
| } |
| } |
| )"); |
| RunSandbox(SandboxResult::Status::EMPTY_TEST_SET); |
| } |
| |
| TEST_F(SandboxTest, DisabledTestSucceeds) { |
| // Start with a component that is instructed to fail, |
| // but mark the test as disabled. |
| // expect sandbox to exit with success. |
| SetCmx(R"( |
| { |
| "disabled" : true, |
| "default_url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "environment" : { |
| "test" : [ { "arguments": ["-f"] } ] |
| } |
| } |
| )"); |
| RunSandboxSuccess(); |
| } |
| |
| TEST_F(SandboxTest, NonexistentPackageUrl) { |
| SetCmx(R"( |
| { |
| "environment" : { |
| "test" : ["fuchsia-pkg://fuchsia.com/netemul_nonexistent_test#meta/something.cmx"] |
| } |
| } |
| )"); |
| RunSandbox(SandboxResult::Status::COMPONENT_FAILURE); |
| } |
| |
| TEST_F(SandboxTest, TimeoutFires) { |
| SetCmx(R"( |
| { |
| "default_url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "timeout" : 1, |
| "environment" : { |
| "test" : [ { "arguments": ["-e", "100"] } ] |
| } |
| } |
| )"); |
| // Expect that we'll fail due to the timeout of 1s while dummy-proc is locked waiting on the |
| // event. |
| RunSandbox(SandboxResult::Status::TIMEOUT); |
| } |
| |
| TEST_F(SandboxTest, ProcessSucceedsBeforeTimeoutFires) { |
| SetCmx(R"( |
| { |
| "timeout" : 60, |
| "environment" : { |
| "test" : [ "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx" ] |
| } |
| } |
| )"); |
| // if a test succeeds, even though we have a timeout, we should succeed |
| // normally. We're using a large timeout here to prevent stalls in |
| // CQ bots to cause a false negative. |
| RunSandboxSuccess(); |
| } |
| |
| TEST_F(SandboxTest, BadServiceCausesFailure) { |
| SetCmx(R"( |
| { |
| "environment" : { |
| "services": { |
| "fuchsia.dummy.service" : "fuchsia-pkg://fuchsia.com/bad_package#meta/bad_service.cmx" |
| }, |
| "test": [{ |
| "url" : "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "arguments" : ["-e", "100", "-s", "fuchsia.dummy.service"] |
| }] |
| } |
| } |
| )"); |
| RunSandbox(SandboxResult::Status::SERVICE_EXITED); |
| } |
| |
| TEST_F(SandboxTest, ServiceExittingCausesFailure) { |
| SetCmx(R"( |
| { |
| "environment" : { |
| "services": { |
| "fuchsia.dummy.service" : { |
| "url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "arguments" : ["-f"] |
| } |
| }, |
| "test": [{ |
| "url" : "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "arguments" : ["-e", "100", "-s", "fuchsia.dummy.service"] |
| }] |
| } |
| } |
| )"); |
| RunSandbox(SandboxResult::Status::SERVICE_EXITED); |
| } |
| |
| TEST_F(SandboxTest, DestructorRunsCleanly) { |
| // This test verifies that if the sandbox is destroyed while tests are |
| // running inside it, it'll shutdown cleanly. |
| // |
| // Dummy proc is launched with an event wait that will never be fulfilled to ensure that we |
| // destroy the sandbox while it is still running. |
| SetCmx(R"( |
| { |
| "default_url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "environment" : { |
| "test" : [ { "arguments": ["-e", "100"] } ] |
| } |
| } |
| )"); |
| auto sandbox = std::make_unique<Sandbox>(TakeArgs()); |
| sandbox->SetTerminationCallback([](SandboxResult result) { FAIL() << "Shouldn't exit"; }); |
| sandbox->Start(dispatcher()); |
| // Give enough time for the process to actually open the file: |
| RunLoopWithTimeout(zx::msec(15)); |
| // force the descrutor to run: |
| sandbox = nullptr; |
| } |
| |
| TEST_F(SandboxTest, EnvironmentsWithSameNameFail) { |
| SetCmx(R"( |
| { |
| "environment" : { |
| "children" : [ |
| { |
| "name" : "my-env", |
| "test" : [ "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx" ] |
| }, |
| { |
| "name" : "my-env", |
| "test" : [ "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx" ] |
| } |
| ] |
| } |
| })"); |
| // Failures for environment with same name can come |
| // from different sources and is racy, to prevent |
| // test flakiness we just check that it'll fail cleanly, |
| // and not expect a specific return code. |
| RunSandbox(SandboxResult::Status::UNSPECIFIED); |
| } |
| |
| constexpr char expected_tag[] = "dummy_proc"; |
| |
| TEST_F(SandboxTest, SyslogWithNoKlog) { |
| SetCmx(R"( |
| { |
| "environment" : { |
| "test" : [{ |
| "url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "arguments" : ["-l", "hello", "-e", "100"] |
| }], |
| "logger_options": { |
| "enabled": false, |
| "klogs_enabled": false |
| } |
| } |
| })", |
| false); |
| EnableEventCollection(); |
| EnableLogCapture([this](const fuchsia::logger::LogMessage& msg) { |
| if (std::find(msg.tags.begin(), msg.tags.end(), expected_tag) != msg.tags.end()) { |
| FX_LOGS(INFO) << "Got log tagged with '" << expected_tag << "'! Sending event..."; |
| sync::Event evt; |
| evt.set_code(100); |
| bus()->Publish(std::move(evt)); |
| FX_LOGS(INFO) << "Published event!"; |
| } else { |
| for (const auto& tag : msg.tags) { |
| FX_LOGS(INFO) << "Got non-matching tag " << tag << "; waiting..."; |
| } |
| } |
| }); |
| RunSandboxSuccess(); |
| } |
| |
| TEST_F(SandboxTest, SyslogWithKlog) { |
| SetCmx(R"( |
| { |
| "environment" : { |
| "test" : [{ |
| "url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "arguments" : ["-l", "hello", "-e", "100"] |
| }], |
| "logger_options": { |
| "enabled": false, |
| "klogs_enabled": true |
| } |
| } |
| })", |
| false); |
| EnableEventCollection(); |
| int klog_count = 0; |
| bool got_dummy_log = false; |
| EnableLogCapture([this, &klog_count, &got_dummy_log](const fuchsia::logger::LogMessage& msg) { |
| if (std::find(msg.tags.begin(), msg.tags.end(), expected_tag) != msg.tags.end()) { |
| got_dummy_log = true; |
| } else if (std::find(msg.tags.begin(), msg.tags.end(), "klog") != msg.tags.end()) { |
| klog_count++; |
| } else { |
| for (const auto& tag : msg.tags) { |
| FX_LOGS(INFO) << "Got non-matching tag " << tag << "; waiting..."; |
| } |
| } |
| if (got_dummy_log && klog_count != 0) { |
| sync::Event evt; |
| evt.set_code(100); |
| bus()->Publish(std::move(evt)); |
| } |
| }); |
| RunSandboxSuccess(); |
| ASSERT_NE(klog_count, 0); |
| } |
| |
| TEST_F(SandboxTest, InvalidEnvironmentDevice) { |
| // Check that sandbox will fail with environment configuration failure |
| // if environment.devices contains an endpoint name that doesn't exist: |
| SetCmx(R"( |
| { |
| "environment" : { |
| "test" : [ "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx" ], |
| "devices": ["bad-ep"] |
| }, |
| "networks": [{ |
| "name": "net", |
| "endpoints": [{"name":"ep"}] |
| }] |
| })"); |
| RunSandbox(SandboxResult::Status::ENVIRONMENT_CONFIG_FAILED); |
| } |
| |
| TEST_F(SandboxTest, ServicesHaveVdev) { |
| // Check that /vdev is passed to services that have "dev" in their sandbox. |
| // We run one dummy_proc as a service fuchsia.dummy.service that will check for a path |
| // "/dev/class/ethernet/ep" and then publish event 1. |
| // The test will hit "fuchsia.dummy.service" (to make the service proc launch) and then |
| // publish event 1. |
| // Then we can check that the test suite runs to completion. (If /vdev was not |
| // available, we'd fail due to the dummy service "crashing") |
| SetCmx(R"( |
| { |
| "default_url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "environment": { |
| "test" : [{"arguments":["-s", "fuchsia.dummy.service", "-e", "1", "-n", "test"]}], |
| "services" : { |
| "fuchsia.dummy.service": { |
| "url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc-with-dev.cmx", |
| "arguments": ["-P", "/vdev/class/ethernet/ep", "-p", "1", "-n", "svc"] |
| } |
| }, |
| "devices": ["ep"] |
| }, |
| "networks": [{ |
| "name": "net", |
| "endpoints": [{"name":"ep"}] |
| }] |
| } |
| )"); |
| RunSandboxSuccess(); |
| } |
| |
| TEST_F(SandboxTest, NotAllServicesHaveVdev) { |
| // This is the same test as ServicesHaveVdev, but it'll fail because we're launching a version of |
| // dummy_proc that does not have "dev" in its sandbox. This asserts that we're using the "dev" |
| // field in the service's component manifest to decide whether it receives /vdev in its namespace |
| SetCmx(R"( |
| { |
| "default_url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc.cmx", |
| "environment": { |
| "test" : [{"arguments":["-s", "fuchsia.dummy.service", "-e", "1", "-n", "test"]}], |
| "services" : { |
| "fuchsia.dummy.service": {"arguments": ["-P", "/vdev/class/ethernet/ep", "-p", "1", "-n", "svc"]} |
| }, |
| "devices": ["ep"] |
| }, |
| "networks": [{ |
| "name": "net", |
| "endpoints": [{"name":"ep"}] |
| }] |
| } |
| )"); |
| // dummy service will just exit with an error because it won't be able to find /vdev: |
| RunSandbox(SandboxResult::Status::SERVICE_EXITED); |
| } |
| |
| TEST_F(SandboxTest, ExposesNetworkDevice) { |
| // Create an endpoint with NETWORK_DEVICE backing and stat its file from the dummy_proc to see |
| // that it got mounted in /vdev |
| SetCmx(R"( |
| { |
| "default_url": "fuchsia-pkg://fuchsia.com/netemul-sandbox-test#meta/dummy-proc-with-dev.cmx", |
| "environment": { |
| "test" : [{"arguments":["-P", "/vdev/class/network/ep"]}], |
| "devices": ["ep"] |
| }, |
| "networks": [{ |
| "name": "net", |
| "endpoints": [{"name":"ep", "backing": "NETWORK_DEVICE"}] |
| }] |
| } |
| )"); |
| RunSandboxSuccess(); |
| } |
| |
| } // namespace testing |
| } // namespace netemul |