blob: fcfaf65cb8f56b490bd888d50d9f601620cc8103 [file] [log] [blame]
// Copyright 2019 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 <fidl/examples/echo/cpp/fidl.h>
#include <fuchsia/debugdata/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/cpp/wait.h>
#include <lib/async/dispatcher.h>
#include <lib/fidl/cpp/binding_set.h>
#include <lib/sys/cpp/testing/enclosing_environment.h>
#include <lib/sys/cpp/testing/test_with_environment.h>
#include <lib/zx/vmo.h>
#include <zircon/processargs.h>
#include <memory>
#include "src/lib/fxl/strings/string_printf.h"
using namespace fuchsia::sys;
namespace echo = ::fidl::examples::echo;
namespace sys::testing::test {
constexpr char kHelperProc[] =
"fuchsia-pkg://fuchsia.com/component_cpp_tests#meta/helper_proc.cmx";
constexpr int kNumberOfTries = 3;
// helper class that creates and listens on
// a socket while appending to a std::stringstream
class SocketReader {
public:
SocketReader() : wait_(this) {}
zx::handle OpenSocket() {
ZX_ASSERT(!socket_.is_valid());
zx::socket ret;
zx::socket::create(0, &ret, &socket_);
wait_.set_object(socket_.get());
wait_.set_trigger(ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED);
wait_.Begin(async_get_default_dispatcher());
return zx::handle(std::move(ret));
}
std::string GetString() { return stream_.str(); }
void OnData(async_dispatcher_t* dispatcher, async::WaitBase* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
if (status != ZX_OK) {
return;
}
if (signal->observed & ZX_SOCKET_READABLE) {
char buff[1024];
size_t actual;
status = socket_.read(0, buff, sizeof(buff) - 1, &actual);
ASSERT_EQ(status, ZX_OK);
buff[actual] = '\0';
stream_ << buff;
}
if (!(signal->observed & ZX_SOCKET_PEER_CLOSED)) {
wait_.Begin(dispatcher);
}
}
private:
zx::socket socket_;
std::stringstream stream_;
async::WaitMethod<SocketReader, &SocketReader::OnData> wait_;
};
class EnclosingEnvTest : public TestWithEnvironment {
public:
// Tries to connect and communicate with echo service
bool TryEchoService(const std::unique_ptr<EnclosingEnvironment>& env) {
// We give this part of the test 3 shots to complete
// this is the safest way to do this and prevent flakiness.
// Because EnvironmentServices is listening on a ComponentController channel
// to restart the service, we can't control that it'll actually recreate the
// service in 100% deterministic order.
echo::EchoPtr echo;
for (int tries = 0; tries < kNumberOfTries; tries++) {
// reset flag again and communicate with the service,
// it must be spun back up
bool req_done = false;
// dismiss old channel
// connect again
env->ConnectToService(echo.NewRequest());
bool channel_closed = false;
echo.set_error_handler(
[&](zx_status_t status) { channel_closed = true; });
// talk with the service once and assert it's ok
echo->EchoString("hello", [&req_done](::fidl::StringPtr rsp) {
EXPECT_EQ(rsp, "hello");
req_done = true;
});
RunLoopUntil([&]() { return req_done || channel_closed; });
if (req_done) {
return true;
} else {
std::cerr << "Didn't receive echo response in attempt number "
<< (tries + 1) << std::endl;
}
}
return false;
}
};
TEST_F(EnclosingEnvTest, RespawnService) {
auto svc = CreateServices();
LaunchInfo linfo;
linfo.url = kHelperProc;
linfo.arguments.reset({"--echo", "--kill=die"});
svc->AddServiceWithLaunchInfo(std::move(linfo), echo::Echo::Name_);
auto env = CreateNewEnclosingEnvironment("test-env", std::move(svc));
WaitForEnclosingEnvToStart(env.get());
// attempt to connect to service:
bool req_done = false;
bool got_error = false;
echo::EchoPtr echo;
echo.set_error_handler(
[&got_error](zx_status_t status) { got_error = true; });
env->ConnectToService(echo.NewRequest());
// talk with the service once and assert it's done
echo->EchoString("hello", [&req_done](::fidl::StringPtr rsp) {
ASSERT_EQ(rsp, "hello");
req_done = true;
});
RunLoopUntil([&req_done]() { return req_done; });
// reset flag, and send the kill string
req_done = false;
// talk with the service once and assert it's done
echo->EchoString("die", [&req_done](::fidl::StringPtr rsp) {
ASSERT_EQ(rsp, "die");
req_done = true;
});
// wait until we see the response AND the channel closing
RunLoopUntil([&req_done, &got_error]() { return req_done && got_error; });
// Try to communicate with server again, we expect
// it to be spun up once more
ASSERT_TRUE(TryEchoService(env));
}
TEST_F(EnclosingEnvTest, EnclosingEnvOnASeperateThread) {
std::unique_ptr<sys::testing::EnclosingEnvironment> env = nullptr;
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
loop.StartThread("vfs test thread");
auto svc =
sys::testing::EnvironmentServices::Create(real_env(), loop.dispatcher());
LaunchInfo linfo;
linfo.url = kHelperProc;
linfo.arguments.reset({"--echo", "--kill=die"});
svc->AddServiceWithLaunchInfo(std::move(linfo), echo::Echo::Name_);
env = CreateNewEnclosingEnvironment("test-env", std::move(svc));
WaitForEnclosingEnvToStart(env.get());
echo::EchoSyncPtr echo_ptr;
env->ConnectToService(echo_ptr.NewRequest());
fidl::StringPtr response;
echo_ptr->EchoString("hello1", &response);
ASSERT_EQ(response, "hello1");
}
TEST_F(EnclosingEnvTest, RespawnServiceWithHandler) {
auto svc = CreateServices();
int call_counter = 0;
svc->AddServiceWithLaunchInfo(
kHelperProc,
[&call_counter]() {
LaunchInfo linfo;
linfo.url = kHelperProc;
linfo.arguments.reset({"--echo", "--kill=die"});
call_counter++;
return linfo;
},
echo::Echo::Name_);
auto env = CreateNewEnclosingEnvironment("test-env", std::move(svc));
WaitForEnclosingEnvToStart(env.get());
// attempt to connect to service:
bool req_done = false;
bool got_error = false;
echo::EchoPtr echo;
echo.set_error_handler(
[&got_error](zx_status_t status) { got_error = true; });
env->ConnectToService(echo.NewRequest());
// talk with the service once and assert it's done
echo->EchoString("hello", [&req_done](::fidl::StringPtr rsp) {
ASSERT_EQ(rsp, "hello");
req_done = true;
});
RunLoopUntil([&req_done]() { return req_done; });
// check that the launch info factory function was called only once
EXPECT_EQ(call_counter, 1);
// reset flag, and send the kill string
req_done = false;
// talk with the service once and assert it's done
echo->EchoString("die", [&req_done](::fidl::StringPtr rsp) {
ASSERT_EQ(rsp, "die");
req_done = true;
});
// wait until we see the response AND the channel closing
RunLoopUntil([&req_done, &got_error]() { return req_done && got_error; });
// Try to communicate with server again, we expect
// it to be spun up once more
ASSERT_TRUE(TryEchoService(env));
// check that the launch info factory function was called only TWICE
EXPECT_EQ(call_counter, 2);
}
TEST_F(EnclosingEnvTest, OutErrPassing) {
auto svc = CreateServices();
SocketReader cout_reader;
SocketReader cerr_reader;
svc->AddServiceWithLaunchInfo(
kHelperProc,
[&cout_reader, &cerr_reader]() {
LaunchInfo linfo;
linfo.url = kHelperProc;
linfo.arguments.reset({"--echo", "--cout=potato", "--cerr=tomato"});
linfo.out = FileDescriptor::New();
linfo.out->type0 = PA_FD;
linfo.out->handle0 = cout_reader.OpenSocket();
linfo.err = FileDescriptor::New();
linfo.err->type0 = PA_FD;
linfo.err->handle0 = cerr_reader.OpenSocket();
return linfo;
},
echo::Echo::Name_);
auto env = CreateNewEnclosingEnvironment("test-env", std::move(svc));
WaitForEnclosingEnvToStart(env.get());
// attempt to connect to service:
echo::EchoPtr echo;
// this should trigger hello_proc to start and
// print "potato" to cout and "tomato" to err
env->ConnectToService(echo.NewRequest());
// now it's just a matter of waiting for the socket readers to
// have seen those strings:
RunLoopUntil([&cout_reader, &cerr_reader]() {
return cout_reader.GetString().find("potato") != std::string::npos &&
cerr_reader.GetString().find("tomato") != std::string::npos;
});
}
class FakeLoader : public fuchsia::sys::Loader {
public:
FakeLoader() {
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)),
dispatcher);
});
}
void LoadUrl(std::string url, LoadUrlCallback callback) override {
ASSERT_TRUE(!url.empty());
component_urls_.push_back(url);
}
std::vector<std::string>& component_urls() { return component_urls_; };
std::shared_ptr<vfs::Service> loader_service() { return loader_service_; }
private:
std::shared_ptr<vfs::Service> loader_service_;
fidl::BindingSet<fuchsia::sys::Loader> bindings_;
std::vector<std::string> component_urls_;
};
TEST_F(EnclosingEnvTest, CanLaunchMoreThanOneService) {
FakeLoader loader;
EnvironmentServices::ParentOverrides parent_overrides;
parent_overrides.loader_service_ = loader.loader_service();
auto svc = CreateServicesWithParentOverrides(std::move(parent_overrides));
std::vector<std::string> urls;
std::vector<std::string> svc_names;
for (int i = 0; i < 3; i++) {
auto url = fxl::StringPrintf(
"fuchsia-pkg://fuchsia.com/dummy%d#meta/dummy%d.cmx", i, i);
auto svc_name = fxl::StringPrintf("service%d", i);
LaunchInfo linfo;
linfo.url = url;
svc->AddServiceWithLaunchInfo(std::move(linfo), svc_name);
urls.push_back(url);
svc_names.push_back(svc_name);
}
auto env = CreateNewEnclosingEnvironment("test-env", std::move(svc));
WaitForEnclosingEnvToStart(env.get());
for (int i = 0; i < 3; i++) {
echo::EchoPtr echo;
env->ConnectToService(echo.NewRequest(), svc_names[i]);
}
RunLoopUntil([&loader]() { return loader.component_urls().size() == 3; });
ASSERT_EQ(loader.component_urls(), urls);
}
class FakeDebugData : public fuchsia::debugdata::DebugData {
public:
void Publish(std::string data_sink, ::zx::vmo data) override {
call_count_++;
}
void LoadConfig(std::string config_name,
LoadConfigCallback callback) override {
// not implemented
}
fidl::InterfaceRequestHandler<fuchsia::debugdata::DebugData> GetHandler(
async_dispatcher_t* dispatcher = nullptr) {
return bindings_.GetHandler(this, dispatcher);
}
uint64_t call_count() const { return call_count_; }
private:
fidl::BindingSet<fuchsia::debugdata::DebugData> bindings_;
uint64_t call_count_ = 0;
};
TEST_F(EnclosingEnvTest, DebugDataServicePlumbedCorrectly) {
zx::vmo data;
fuchsia::debugdata::DebugDataPtr ptr;
FakeDebugData debug_data;
// Add to first enclosing env and override plumbing
EnvironmentServices::ParentOverrides parent_overrides;
parent_overrides.debug_data_service_ =
std::make_shared<vfs::Service>(debug_data.GetHandler());
auto svc = CreateServicesWithParentOverrides(std::move(parent_overrides));
auto env = CreateNewEnclosingEnvironment("test-env1", std::move(svc));
WaitForEnclosingEnvToStart(env.get());
// make sure count was 0
ASSERT_EQ(0u, debug_data.call_count());
// make sure out fake servcie can be called.
env->ConnectToService(ptr.NewRequest());
ASSERT_EQ(ZX_OK, zx::vmo::create(8, 0, &data));
ptr->Publish("data_sink", std::move(data));
RunLoopUntil([&debug_data]() { return debug_data.call_count() == 1; });
// make sure service is automatically plumbed to sub environments
auto sub_env = env->CreateNestedEnclosingEnvironment("test-env2");
ptr.Unbind();
sub_env->ConnectToService(ptr.NewRequest());
ASSERT_EQ(ZX_OK, zx::vmo::create(8, 0, &data));
ptr->Publish("data_sink", std::move(data));
RunLoopUntil([&debug_data]() { return debug_data.call_count() == 2; });
}
} // namespace sys::testing::test