[modular][testing] Test harness component & gtest fixture
This CL introduces two features:
* A component that provides the TestHarness as a service.
(`modular_test_harness.cmx`)
* A gtest fixture that exposes TestHarness using
`modular_test_harness.cmx`.
modular_test_harness.cmx is intended to be used by test fixtures in
other languages such as Dart and C++.
Also in this CL:
* TestHarnessImpl will accept and invoke an exit callback if the test
harness becomes unavailable.
Test: `modular_test_harness_test` exercises the test fixture, which also
exercises modular_test_harness.cmx
Change-Id: I58a4d0f80e218fca6bf896d0a1a1843f39e21764
diff --git a/peridot/bin/modular_test_harness/BUILD.gn b/peridot/bin/modular_test_harness/BUILD.gn
index 8104455..352ac25 100644
--- a/peridot/bin/modular_test_harness/BUILD.gn
+++ b/peridot/bin/modular_test_harness/BUILD.gn
@@ -4,6 +4,22 @@
import("//build/package.gni")
+executable("modular_test_harness_bin") {
+ testonly = true
+
+ sources = [
+ "modular_test_harness.cc",
+ ]
+
+ deps = [
+ "//garnet/public/lib/fxl",
+ "//sdk/lib/sys/cpp",
+ "//sdk/fidl/fuchsia.modular.testing",
+ "//peridot/public/lib/modular_test_harness/cpp:test_harness_impl",
+ "//zircon/public/lib/async-loop-cpp",
+ ]
+}
+
executable("test_base_shell_bin") {
testonly = true
@@ -59,6 +75,20 @@
]
}
+executable("modular_test_harness_test") {
+ testonly = true
+ output_name = "modular_test_harness_test"
+ sources = ["modular_test_harness_test.cc"]
+ deps = [
+ "//sdk/lib/sys/cpp",
+ "//sdk/lib/sys/cpp/testing:integration",
+ "//sdk/fidl/fuchsia.sys",
+ "//sdk/fidl/fuchsia.modular.testing",
+ "//peridot/public/lib/modular_test_harness/cpp",
+ "//third_party/googletest:gtest_main",
+ ]
+}
+
package("modular_test_harness") {
testonly = true
@@ -72,10 +102,28 @@
{
name = "test_session_shell_bin"
},
+ {
+ name = "modular_test_harness_bin"
+ },
+ ]
+
+ tests = [
+ {
+ name = "modular_test_harness_test"
+ environments = basic_envs
+ },
]
meta = [
{
+ path = "meta/modular_test_harness.cmx"
+ dest = "modular_test_harness.cmx"
+ },
+ {
+ path = "meta/modular_test_harness_test.cmx"
+ dest = "modular_test_harness_test.cmx"
+ },
+ {
path = "meta/test_base_shell.cmx"
dest = "test_base_shell.cmx"
},
@@ -90,8 +138,10 @@
]
deps = [
+ ":modular_test_harness_bin",
+ ":modular_test_harness_test",
":test_base_shell_bin",
- ":test_session_shell_bin",
":test_story_shell_bin",
+ ":test_session_shell_bin",
]
}
diff --git a/peridot/bin/modular_test_harness/meta/modular_test_harness.cmx b/peridot/bin/modular_test_harness/meta/modular_test_harness.cmx
new file mode 100644
index 0000000..2735eeb
--- /dev/null
+++ b/peridot/bin/modular_test_harness/meta/modular_test_harness.cmx
@@ -0,0 +1,11 @@
+{
+ "program": {
+ "binary": "bin/modular_test_harness_bin"
+ },
+ "sandbox": {
+ "services": [
+ "fuchsia.sys.Environment",
+ "fuchsia.sys.Loader"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/peridot/bin/modular_test_harness/meta/modular_test_harness_test.cmx b/peridot/bin/modular_test_harness/meta/modular_test_harness_test.cmx
new file mode 100644
index 0000000..b66c96f7
--- /dev/null
+++ b/peridot/bin/modular_test_harness/meta/modular_test_harness_test.cmx
@@ -0,0 +1,11 @@
+{
+ "program": {
+ "binary": "test/modular_test_harness_test"
+ },
+ "sandbox": {
+ "services": [
+ "fuchsia.sys.Environment",
+ "fuchsia.sys.Launcher"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/peridot/bin/modular_test_harness/modular_test_harness.cc b/peridot/bin/modular_test_harness/modular_test_harness.cc
new file mode 100644
index 0000000..6b3772c
--- /dev/null
+++ b/peridot/bin/modular_test_harness/modular_test_harness.cc
@@ -0,0 +1,32 @@
+// 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.
+
+// This component provides the |fuchsia.modular.testing.TestHarness| fidl
+// service. This component will exit if the test harness becomes unavailable.
+
+#include <lib/async-loop/cpp/loop.h>
+#include <lib/modular_test_harness/cpp/test_harness_impl.h>
+#include <sdk/lib/sys/cpp/component_context.h>
+#include <src/lib/fxl/logging.h>
+
+#include <memory>
+
+int main(int argc, const char** argv) {
+ async::Loop loop(&kAsyncLoopConfigAttachToThread);
+
+ std::unique_ptr<modular::testing::TestHarnessImpl> test_harness_impl;
+
+ auto context = sys::ComponentContext::Create();
+ auto env = context->svc()->Connect<fuchsia::sys::Environment>();
+ context->outgoing()->AddPublicService<fuchsia::modular::testing::TestHarness>(
+ [&loop, &context, &env, &test_harness_impl](
+ fidl::InterfaceRequest<fuchsia::modular::testing::TestHarness>
+ request) {
+ test_harness_impl = std::make_unique<modular::testing::TestHarnessImpl>(
+ env, std::move(request), [&loop] { loop.Quit(); });
+ });
+
+ loop.Run();
+ return 0;
+}
diff --git a/peridot/bin/modular_test_harness/modular_test_harness_test.cc b/peridot/bin/modular_test_harness/modular_test_harness_test.cc
new file mode 100644
index 0000000..646308b
--- /dev/null
+++ b/peridot/bin/modular_test_harness/modular_test_harness_test.cc
@@ -0,0 +1,36 @@
+// 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 <fuchsia/modular/testing/cpp/fidl.h>
+#include <lib/modular_test_harness/cpp/test_harness_fixture.h>
+#include <sdk/lib/sys/cpp/service_directory.h>
+#include <sdk/lib/sys/cpp/testing/test_with_environment.h>
+
+class TestHarnessFixtureTest : public modular::testing::TestHarnessFixture {};
+
+// Ensure that the TestHarnessFixture is able to launch the modular runtime.
+TEST_F(TestHarnessFixtureTest, SimpleSuccess) {
+ constexpr char kFakeBaseShellUrl[] =
+ "fuchsia-pkg://example.com/FAKE_BASE_SHELL_PKG/fake_base_shell.cmx";
+
+ fuchsia::modular::testing::InterceptSpec shell_intercept_spec;
+ shell_intercept_spec.set_component_url(kFakeBaseShellUrl);
+ fuchsia::modular::testing::TestHarnessSpec spec;
+ spec.mutable_base_shell()->set_intercept_spec(
+ std::move(shell_intercept_spec));
+
+ // Listen for base shell interception.
+ bool intercepted = false;
+
+ test_harness().events().OnNewBaseShell =
+ [&intercepted](
+ fuchsia::sys::StartupInfo startup_info,
+ fidl::InterfaceHandle<fuchsia::modular::testing::InterceptedComponent>
+ component) { intercepted = true; };
+
+ test_harness()->Run(std::move(spec));
+
+ ASSERT_TRUE(
+ RunLoopWithTimeoutOrUntil([&] { return intercepted; }, zx::sec(5)));
+}
\ No newline at end of file
diff --git a/peridot/public/lib/modular_test_harness/cpp/BUILD.gn b/peridot/public/lib/modular_test_harness/cpp/BUILD.gn
index 865ac6b..c86de66 100644
--- a/peridot/public/lib/modular_test_harness/cpp/BUILD.gn
+++ b/peridot/public/lib/modular_test_harness/cpp/BUILD.gn
@@ -6,7 +6,7 @@
testonly = true
public_deps = [
- ":test_harness_impl",
+ ":test_harness_fixture",
]
}
@@ -18,6 +18,22 @@
]
}
+# This library has a run-time dependency on the `modular_test_harness` package.
+source_set("test_harness_fixture") {
+ testonly = true
+
+ sources = [
+ "test_harness_fixture.h",
+ "test_harness_fixture.cc"
+ ]
+
+ deps = [
+ "//sdk/fidl/fuchsia.modular.testing",
+ "//sdk/lib/sys/cpp",
+ "//sdk/lib/sys/cpp/testing:integration",
+ ]
+}
+
source_set("test_harness_impl") {
testonly = true
@@ -49,6 +65,8 @@
deps = [
":test_harness_impl",
+ "//sdk/fidl/fuchsia.modular",
+ "//sdk/fidl/fuchsia.modular.testing",
"//sdk/lib/sys/cpp/testing:integration",
"//sdk/lib/sys/cpp/testing:unit",
"//third_party/googletest:gtest_main",
diff --git a/peridot/public/lib/modular_test_harness/cpp/test_harness_fixture.cc b/peridot/public/lib/modular_test_harness/cpp/test_harness_fixture.cc
new file mode 100644
index 0000000..01dd236
--- /dev/null
+++ b/peridot/public/lib/modular_test_harness/cpp/test_harness_fixture.cc
@@ -0,0 +1,31 @@
+// 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 "lib/modular_test_harness/cpp/test_harness_fixture.h"
+
+namespace modular {
+namespace testing {
+namespace {
+
+const char kTestHarnessUrl[] =
+ "fuchsia-pkg://fuchsia.com/modular_test_harness#meta/"
+ "modular_test_harness.cmx";
+
+} // namespace
+
+TestHarnessFixture::TestHarnessFixture() {
+ fuchsia::sys::LaunchInfo launch_info;
+ launch_info.url = kTestHarnessUrl;
+ svc_ =
+ sys::ServiceDirectory::CreateWithRequest(&launch_info.directory_request);
+ launcher_ptr()->CreateComponent(std::move(launch_info),
+ test_harness_ctrl_.NewRequest());
+
+ test_harness_ = svc_->Connect<fuchsia::modular::testing::TestHarness>();
+}
+
+TestHarnessFixture::~TestHarnessFixture() = default;
+
+} // namespace testing
+} // namespace modular
diff --git a/peridot/public/lib/modular_test_harness/cpp/test_harness_fixture.h b/peridot/public/lib/modular_test_harness/cpp/test_harness_fixture.h
new file mode 100644
index 0000000..c86b577
--- /dev/null
+++ b/peridot/public/lib/modular_test_harness/cpp/test_harness_fixture.h
@@ -0,0 +1,35 @@
+// 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.
+
+#ifndef LIB_MODULAR_TEST_HARNESS_CPP_TEST_HARNESS_FIXTURE_H_
+#define LIB_MODULAR_TEST_HARNESS_CPP_TEST_HARNESS_FIXTURE_H_
+
+#include <fuchsia/modular/testing/cpp/fidl.h>
+#include <sdk/lib/sys/cpp/service_directory.h>
+#include <sdk/lib/sys/cpp/testing/test_with_environment.h>
+
+namespace modular {
+namespace testing {
+
+// A gtest fixture for tests that require an instance of the modular runtime.
+// This fixture requires the `modular_test_harness` package to be available.
+class TestHarnessFixture : public sys::testing::TestWithEnvironment {
+ protected:
+ TestHarnessFixture();
+ virtual ~TestHarnessFixture();
+
+ fuchsia::modular::testing::TestHarnessPtr& test_harness() {
+ return test_harness_;
+ }
+
+ private:
+ std::shared_ptr<sys::ServiceDirectory> svc_;
+ fuchsia::modular::testing::TestHarnessPtr test_harness_;
+ fuchsia::sys::ComponentControllerPtr test_harness_ctrl_;
+};
+
+} // namespace testing
+} // namespace modular
+
+#endif // LIB_MODULAR_TEST_HARNESS_CPP_TEST_HARNESS_FIXTURE_H_
diff --git a/peridot/public/lib/modular_test_harness/cpp/test_harness_impl.cc b/peridot/public/lib/modular_test_harness/cpp/test_harness_impl.cc
index 2329b3f..4cd1f2f 100644
--- a/peridot/public/lib/modular_test_harness/cpp/test_harness_impl.cc
+++ b/peridot/public/lib/modular_test_harness/cpp/test_harness_impl.cc
@@ -96,12 +96,17 @@
TestHarnessImpl::TestHarnessImpl(
const fuchsia::sys::EnvironmentPtr& parent_env,
- fidl::InterfaceRequest<fuchsia::modular::testing::TestHarness> request)
+ fidl::InterfaceRequest<fuchsia::modular::testing::TestHarness> request,
+ fit::function<void()> on_disconnected)
: parent_env_(parent_env),
binding_(this, std::move(request)),
+ on_disconnected_(std::move(on_disconnected)),
interceptor_(
sys::testing::ComponentInterceptor::CreateWithEnvironmentLoader(
- parent_env_)) {}
+ parent_env_)) {
+ binding_.set_error_handler(
+ [this](zx_status_t status) { CloseBindingIfError(status); });
+}
TestHarnessImpl::~TestHarnessImpl() = default;
@@ -136,6 +141,7 @@
binding_.Close(status);
// destory |enclosing_env_| should kill all processes.
enclosing_env_.reset();
+ on_disconnected_();
return true;
}
return false;
diff --git a/peridot/public/lib/modular_test_harness/cpp/test_harness_impl.h b/peridot/public/lib/modular_test_harness/cpp/test_harness_impl.h
index 937e39f..4406227 100644
--- a/peridot/public/lib/modular_test_harness/cpp/test_harness_impl.h
+++ b/peridot/public/lib/modular_test_harness/cpp/test_harness_impl.h
@@ -26,8 +26,15 @@
//
// |test_harness_request| is implemented by this class. The TestHarness
// FIDL interface is the way to interact with the TestHarness API.
+ //
+ // |on_exit| is called when the TestHarness interface is closed.
+ // This can happen if the TestHarness client drops their side of the
+ // connection, or this class closes it due to an error; In this case, the
+ // error is sent as an epitaph. See the |TestHarness| protocol documentation
+ // for more details.
TestHarnessImpl(const fuchsia::sys::EnvironmentPtr& parent_env,
- fidl::InterfaceRequest<TestHarness> test_harness_request);
+ fidl::InterfaceRequest<TestHarness> test_harness_request,
+ fit::function<void()> on_disconnected);
virtual ~TestHarnessImpl() override;
@@ -133,6 +140,8 @@
fidl::Binding<fuchsia::modular::testing::TestHarness> binding_;
fuchsia::modular::testing::TestHarnessSpec spec_;
+ fit::function<void()> on_disconnected_;
+
// This map manages InterceptedComponent bindings (and their implementations).
// When a |InterceptedComponent| connection is closed, it is automatically
// removed from this map (and its impl is deleted as well).
diff --git a/peridot/public/lib/modular_test_harness/cpp/test_harness_impl_unittest.cc b/peridot/public/lib/modular_test_harness/cpp/test_harness_impl_unittest.cc
index a928615..9c36700 100644
--- a/peridot/public/lib/modular_test_harness/cpp/test_harness_impl_unittest.cc
+++ b/peridot/public/lib/modular_test_harness/cpp/test_harness_impl_unittest.cc
@@ -23,24 +23,35 @@
class TestHarnessImplTest : public sys::testing::TestWithEnvironment {
public:
- TestHarnessImplTest() : harness_impl_(real_env(), harness_.NewRequest()) {}
+ TestHarnessImplTest()
+ : harness_impl_(real_env(), harness_.NewRequest(),
+ [this] { did_exit_ = true; }) {}
- const fuchsia::modular::testing::TestHarnessPtr& test_harness() {
+ fuchsia::modular::testing::TestHarnessPtr& test_harness() {
return harness_;
};
+ bool did_exit() { return did_exit_; }
+
std::vector<std::string> MakeBasemgrArgs(
fuchsia::modular::testing::TestHarnessSpec spec) {
return TestHarnessImpl::MakeBasemgrArgs(std::move(spec));
}
private:
+ bool did_exit_ = false;
fuchsia::modular::testing::TestHarnessPtr harness_;
::modular::testing::TestHarnessImpl harness_impl_;
};
namespace {
+TEST_F(TestHarnessImplTest, ExitCallback) {
+ test_harness().Unbind();
+ ASSERT_TRUE(
+ RunLoopWithTimeoutOrUntil([&] { return did_exit(); }, zx::sec(5)));
+}
+
TEST_F(TestHarnessImplTest, DefaultMakeBasemgrArgs) {
std::vector<std::string> expected = {
"--test",
@@ -123,20 +134,16 @@
std::move(intercept_spec));
}
- fuchsia::modular::testing::TestHarnessPtr harness;
- ::modular::testing::TestHarnessImpl harness_impl(real_env(),
- harness.NewRequest());
-
// Listen for story shell interception.
bool story_shell_intercepted = false;
- harness.events().OnNewStoryShell =
+ test_harness().events().OnNewStoryShell =
[&](fuchsia::sys::StartupInfo startup_info,
fidl::InterfaceHandle<fuchsia::modular::testing::InterceptedComponent>
component) { story_shell_intercepted = true; };
// Listen for module interception.
bool fake_module_intercepted = false;
- harness.events().OnNewComponent =
+ test_harness().events().OnNewComponent =
[&](fuchsia::sys::StartupInfo startup_info,
fidl::InterfaceHandle<fuchsia::modular::testing::InterceptedComponent>
component) {
@@ -144,7 +151,7 @@
fake_module_intercepted = true;
}
};
- harness->Run(std::move(spec));
+ test_harness()->Run(std::move(spec));
// Create a new story -- this should auto-start the story (because of
// test_session_shell's behaviour), and launch a new story shell.
@@ -153,7 +160,7 @@
fuchsia::modular::testing::TestHarnessService svc;
svc.set_puppet_master(puppet_master.NewRequest());
- harness->GetService(std::move(svc));
+ test_harness()->GetService(std::move(svc));
puppet_master->ControlStory("my_story", story_master.NewRequest());