From b313459aa92f03fd2010609e9869f6128b084ab3 Mon Sep 17 00:00:00 2001 From: Eli Lipsitz Date: Sat, 9 Jul 2022 00:59:46 +0000 Subject: [PATCH] pw_unit_test: Support GTest backend in RPC server Previously, the pw_unit_test RPC server didn't support GoogleTest at all, only the default ':light' backend. This adds initial support for GoogleTest. The core functionality (running tests) works, but there's still some functionality missing. In particular, we cannot run a subset of the test suites (filtering), because GoogleTest does not programmatically support this functionality. Additionally, there are a few differences in outputs -- GoogleTest does not support reporting all expectations (only failures), and the failure output of GoogleTest does not necessarily conform to the constrained "expected expression / actual expression" format that pw_unit_test requires. Change-Id: I8403c83fbdf90d3cb43bb67e0b6fbf0ba89e035d Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/100660 Reviewed-by: Wyatt Hepler Commit-Queue: Eli Lipsitz --- pw_unit_test/BUILD.bazel | 15 ++- pw_unit_test/BUILD.gn | 51 +++++---- pw_unit_test/docs.rst | 7 +- .../public/pw_unit_test/unit_test_service.h | 1 + pw_unit_test/rpc_gtest_event_handler.cc | 106 ++++++++++++++++++ .../pw_unit_test/internal/rpc_event_handler.h | 44 ++++++++ ..._handler.cc => rpc_light_event_handler.cc} | 17 ++- .../pw_unit_test/internal/rpc_event_handler.h | 6 +- pw_unit_test/unit_test_service.cc | 12 +- 9 files changed, 217 insertions(+), 42 deletions(-) create mode 100644 pw_unit_test/rpc_gtest_event_handler.cc create mode 100644 pw_unit_test/rpc_gtest_public/pw_unit_test/internal/rpc_event_handler.h rename pw_unit_test/{rpc_event_handler.cc => rpc_light_event_handler.cc} (78%) rename pw_unit_test/{public => rpc_light_public}/pw_unit_test/internal/rpc_event_handler.h (89%) diff --git a/pw_unit_test/BUILD.bazel b/pw_unit_test/BUILD.bazel index 266480980c..14bb6bcb25 100644 --- a/pw_unit_test/BUILD.bazel +++ b/pw_unit_test/BUILD.bazel @@ -164,13 +164,14 @@ pw_proto_library( pw_cc_library( name = "rpc_service", srcs = [ - "rpc_event_handler.cc", + "rpc_light_event_handler.cc", "unit_test_service.cc", ], hdrs = [ - "public/pw_unit_test/internal/rpc_event_handler.h", "public/pw_unit_test/unit_test_service.h", + "rpc_light_public/pw_unit_test/internal/rpc_event_handler.h", ], + includes = ["rpc_light_public"], deps = [ ":pw_unit_test", ":unit_test_cc.pwpb", @@ -243,3 +244,13 @@ filegroup( # "//pw_rpc/system_server", # ], ) + +# GTest is not yet supported in the Bazel build. This filegroup silences +# warnings about these files not being included in the Bazel build. +filegroup( + name = "gtest_rpc_support", + srcs = [ + "rpc_gtest_event_handler.cc", + "rpc_gtest_public/pw_unit_test/internal/rpc_event_handler.h", + ], +) diff --git a/pw_unit_test/BUILD.gn b/pw_unit_test/BUILD.gn index 9b4be0fa5d..45e0f59bf4 100644 --- a/pw_unit_test/BUILD.gn +++ b/pw_unit_test/BUILD.gn @@ -169,18 +169,12 @@ pw_source_set("logging_main") { sources = [ "logging_main.cc" ] } -pw_build_assert("require_light_test_backend") { - # TODO(b/233073669): Simplify once pw_unit_test_PUBLIC_DEPS is removed. - condition = (pw_unit_test_PUBLIC_DEPS == [] && - pw_unit_test_GOOGLETEST_BACKEND == "$dir_pw_unit_test:light") || - filter_include(pw_unit_test_PUBLIC_DEPS, - [ "$dir_pw_unit_test:light" ]) != [] - - message = "The pw_unit_test RPC service only works with the 'light' test " + - "backend.\n(pw_unit_test_GOOGLETEST_BACKEND = " + - "$pw_unit_test_GOOGLETEST_BACKEND)\n" + - "(pw_unit_test_PUBLIC_DEPS = $pw_unit_test_PUBLIC_DEPS)" - visibility = [ ":*" ] +config("rpc_service_backend_light") { + include_dirs = [ "rpc_light_public" ] +} + +config("rpc_service_backend_gtest") { + include_dirs = [ "rpc_gtest_public" ] } pw_source_set("rpc_service") { @@ -191,18 +185,27 @@ pw_source_set("rpc_service") { ":unit_test_proto.raw_rpc", "$dir_pw_containers:vector", ] - deps = [ - ":require_light_test_backend", - dir_pw_log, - ] - public = [ - "public/pw_unit_test/internal/rpc_event_handler.h", - "public/pw_unit_test/unit_test_service.h", - ] - sources = [ - "rpc_event_handler.cc", - "unit_test_service.cc", - ] + deps = [ dir_pw_log ] + public = [ "public/pw_unit_test/unit_test_service.h" ] + sources = [ "unit_test_service.cc" ] + defines = [] + + # TODO(b/233073669): Simplify once pw_unit_test_PUBLIC_DEPS is removed. + using_light_backend = + (pw_unit_test_PUBLIC_DEPS == [] && + pw_unit_test_GOOGLETEST_BACKEND == "$dir_pw_unit_test:light") || + filter_include(pw_unit_test_PUBLIC_DEPS, [ "$dir_pw_unit_test:light" ]) != + [] + + if (using_light_backend) { + public_configs += [ ":rpc_service_backend_light" ] + sources += [ "rpc_light_event_handler.cc" ] + public += [ "rpc_light_public/pw_unit_test/internal/rpc_event_handler.h" ] + } else { + public_configs += [ ":rpc_service_backend_gtest" ] + sources += [ "rpc_gtest_event_handler.cc" ] + public += [ "rpc_gtest_public/pw_unit_test/internal/rpc_event_handler.h" ] + } } pw_source_set("rpc_main") { diff --git a/pw_unit_test/docs.rst b/pw_unit_test/docs.rst index 880a3fa1ed..88c2b33136 100644 --- a/pw_unit_test/docs.rst +++ b/pw_unit_test/docs.rst @@ -378,8 +378,11 @@ RPC service ``pw_unit_test`` provides an RPC service which runs unit tests on demand and streams the results back to the client. The service is defined in ``pw_unit_test_proto/unit_test.proto``, and implemented by the GN target -``$dir_pw_unit_test:rpc_service``. The RPC service is only compatible with the -default ``pw_unit_test:light`` backend. +``$dir_pw_unit_test:rpc_service``. + +The RPC service is primarily intended for use with the default +``pw_unit_test:light`` backend. It has some support for the GoogleTest backend, +however some features (such as test suite filtering) are missing. To set up RPC-based unit tests in your application, instantiate a ``pw::unit_test::UnitTestService`` and register it with your RPC server. diff --git a/pw_unit_test/public/pw_unit_test/unit_test_service.h b/pw_unit_test/public/pw_unit_test/unit_test_service.h index a7269eeccb..80be9f319c 100644 --- a/pw_unit_test/public/pw_unit_test/unit_test_service.h +++ b/pw_unit_test/public/pw_unit_test/unit_test_service.h @@ -15,6 +15,7 @@ #include "pw_log/log.h" #include "pw_unit_test/config.h" +#include "pw_unit_test/event_handler.h" #include "pw_unit_test/internal/rpc_event_handler.h" #include "pw_unit_test_proto/unit_test.pwpb.h" #include "pw_unit_test_proto/unit_test.raw_rpc.pb.h" diff --git a/pw_unit_test/rpc_gtest_event_handler.cc b/pw_unit_test/rpc_gtest_event_handler.cc new file mode 100644 index 0000000000..9428c4c928 --- /dev/null +++ b/pw_unit_test/rpc_gtest_event_handler.cc @@ -0,0 +1,106 @@ +// Copyright 2022 The Pigweed Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +#include + +#include "gtest/gtest.h" +#include "pw_unit_test/internal/rpc_event_handler.h" +#include "pw_unit_test/unit_test_service.h" + +namespace pw::unit_test::internal { + +RpcEventHandler::RpcEventHandler(UnitTestService& service) : service_(service) { + // Initialize GoogleTest and disable the default result printer. + testing::InitGoogleTest(); + auto unit_test = testing::UnitTest::GetInstance(); + auto default_listener = unit_test->listeners().default_result_printer(); + unit_test->listeners().Release(default_listener); + delete default_listener; +} + +void RpcEventHandler::ExecuteTests(span suites_to_run) { + if (!suites_to_run.empty()) { + PW_LOG_WARN( + "GoogleTest backend does not support test suite filtering. Running all " + "suites."); + } + if (service_.verbose_) { + PW_LOG_WARN( + "GoogleTest backend does not support reporting passed expectations."); + } + + auto unit_test = testing::UnitTest::GetInstance(); + unit_test->listeners().Append(this); + + std::ignore = RUN_ALL_TESTS(); + + unit_test->listeners().Release(this); +} + +void RpcEventHandler::OnTestProgramStart(const testing::UnitTest&) { + service_.WriteTestRunStart(); +} + +void RpcEventHandler::OnTestProgramEnd(const testing::UnitTest& unit_test) { + RunTestsSummary run_tests_summary{ + .passed_tests = unit_test.successful_test_count(), + .failed_tests = unit_test.failed_test_count(), + .skipped_tests = unit_test.skipped_test_count(), + .disabled_tests = unit_test.disabled_test_count(), + }; + service_.WriteTestRunEnd(run_tests_summary); +} + +void RpcEventHandler::OnTestStart(const testing::TestInfo& test_info) { + TestCase test_case{ + .suite_name = test_info.test_suite_name(), + .test_name = test_info.name(), + .file_name = test_info.file(), + }; + service_.WriteTestCaseStart(test_case); +} + +void RpcEventHandler::OnTestEnd(const testing::TestInfo& test_info) { + TestResult result; + if (test_info.result()->Passed()) { + result = TestResult::kSuccess; + } else if (test_info.result()->Skipped()) { + result = TestResult::kSkipped; + } else { + result = TestResult::kFailure; + } + + service_.WriteTestCaseEnd(result); +} + +void RpcEventHandler::OnTestPartResult(const testing::TestPartResult& result) { + TestExpectation expectation{ + .expression = "", + .evaluated_expression = result.summary(), + .line_number = result.line_number(), + .success = result.passed(), + }; + service_.WriteTestCaseExpectation(expectation); +} + +void RpcEventHandler::OnTestDisabled(const testing::TestInfo& test_info) { + TestCase test_case{ + .suite_name = test_info.test_suite_name(), + .test_name = test_info.name(), + .file_name = test_info.file(), + }; + service_.WriteTestCaseDisabled(test_case); +} + +} // namespace pw::unit_test::internal diff --git a/pw_unit_test/rpc_gtest_public/pw_unit_test/internal/rpc_event_handler.h b/pw_unit_test/rpc_gtest_public/pw_unit_test/internal/rpc_event_handler.h new file mode 100644 index 0000000000..1a9a7547af --- /dev/null +++ b/pw_unit_test/rpc_gtest_public/pw_unit_test/internal/rpc_event_handler.h @@ -0,0 +1,44 @@ +// Copyright 2022 The Pigweed Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +#pragma once + +#include "gtest/gtest.h" +#include "pw_span/span.h" + +namespace pw::unit_test { + +class UnitTestService; + +namespace internal { + +// GoogleTest event handler that streams test events through an RPC service. +class RpcEventHandler : public testing::EmptyTestEventListener { + public: + RpcEventHandler(UnitTestService& service); + void ExecuteTests(span suites_to_run); + + void OnTestProgramStart(const testing::UnitTest& unit_test) override; + void OnTestProgramEnd(const testing::UnitTest& unit_test) override; + void OnTestStart(const testing::TestInfo& test_info) override; + void OnTestEnd(const testing::TestInfo& test_info) override; + void OnTestPartResult( + const testing::TestPartResult& test_part_result) override; + void OnTestDisabled(const testing::TestInfo& test_info) override; + + private: + UnitTestService& service_; +}; + +} // namespace internal +} // namespace pw::unit_test diff --git a/pw_unit_test/rpc_event_handler.cc b/pw_unit_test/rpc_light_event_handler.cc similarity index 78% rename from pw_unit_test/rpc_event_handler.cc rename to pw_unit_test/rpc_light_event_handler.cc index 291dec39ac..29028ece11 100644 --- a/pw_unit_test/rpc_event_handler.cc +++ b/pw_unit_test/rpc_light_event_handler.cc @@ -13,11 +13,26 @@ // the License. #include "pw_unit_test/internal/rpc_event_handler.h" - #include "pw_unit_test/unit_test_service.h" namespace pw::unit_test::internal { +RpcEventHandler::RpcEventHandler(UnitTestService& service) + : service_(service) {} + +void RpcEventHandler::ExecuteTests(span suites_to_run) { + RegisterEventHandler(this); + SetTestSuitesToRun(suites_to_run); + + PW_LOG_DEBUG("%u test suite filters applied", + static_cast(suites_to_run.size())); + + RUN_ALL_TESTS(); + + RegisterEventHandler(nullptr); + SetTestSuitesToRun({}); +} + void RpcEventHandler::RunAllTestsStart() { service_.WriteTestRunStart(); } void RpcEventHandler::RunAllTestsEnd(const RunTestsSummary& run_tests_summary) { diff --git a/pw_unit_test/public/pw_unit_test/internal/rpc_event_handler.h b/pw_unit_test/rpc_light_public/pw_unit_test/internal/rpc_event_handler.h similarity index 89% rename from pw_unit_test/public/pw_unit_test/internal/rpc_event_handler.h rename to pw_unit_test/rpc_light_public/pw_unit_test/internal/rpc_event_handler.h index ad27fc0bbe..1cf0ef39bf 100644 --- a/pw_unit_test/public/pw_unit_test/internal/rpc_event_handler.h +++ b/pw_unit_test/rpc_light_public/pw_unit_test/internal/rpc_event_handler.h @@ -13,7 +13,8 @@ // the License. #pragma once -#include "pw_unit_test/event_handler.h" +#include "gtest/gtest.h" +#include "pw_span/span.h" namespace pw::unit_test { @@ -24,7 +25,8 @@ namespace internal { // Unit test event handler that streams test events through an RPC service. class RpcEventHandler : public EventHandler { public: - RpcEventHandler(UnitTestService& service) : service_(service) {} + RpcEventHandler(UnitTestService& service); + void ExecuteTests(span suites_to_run); void RunAllTestsStart() override; void RunAllTestsEnd(const RunTestsSummary& run_tests_summary) override; diff --git a/pw_unit_test/unit_test_service.cc b/pw_unit_test/unit_test_service.cc index 0a6ef6e5cd..ef51cde52f 100644 --- a/pw_unit_test/unit_test_service.cc +++ b/pw_unit_test/unit_test_service.cc @@ -68,17 +68,7 @@ void UnitTestService::Run(ConstByteSpan request, RawServerWriter& writer) { } PW_LOG_INFO("Starting unit test run"); - - RegisterEventHandler(&handler_); - SetTestSuitesToRun(suites_to_run); - PW_LOG_DEBUG("%u test suite filters applied", - static_cast(suites_to_run.size())); - - RUN_ALL_TESTS(); - - RegisterEventHandler(nullptr); - SetTestSuitesToRun({}); - + handler_.ExecuteTests(suites_to_run); PW_LOG_INFO("Unit test run complete"); writer_.Finish().IgnoreError(); // TODO(pwbug/387): Handle Status properly