Skip to content

Commit

Permalink
pw_unit_test: Support GTest backend in RPC server
Browse files Browse the repository at this point in the history
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 <[email protected]>
Commit-Queue: Eli Lipsitz <[email protected]>
  • Loading branch information
elipsitz authored and CQ Bot Account committed Jul 9, 2022
1 parent 0c7cba1 commit b313459
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 42 deletions.
15 changes: 13 additions & 2 deletions pw_unit_test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
],
)
51 changes: 27 additions & 24 deletions pw_unit_test/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand All @@ -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") {
Expand Down
7 changes: 5 additions & 2 deletions pw_unit_test/docs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions pw_unit_test/public/pw_unit_test/unit_test_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
106 changes: 106 additions & 0 deletions pw_unit_test/rpc_gtest_event_handler.cc
Original file line number Diff line number Diff line change
@@ -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 <tuple>

#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<std::string_view> 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
Original file line number Diff line number Diff line change
@@ -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<std::string_view> 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
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string_view> suites_to_run) {
RegisterEventHandler(this);
SetTestSuitesToRun(suites_to_run);

PW_LOG_DEBUG("%u test suite filters applied",
static_cast<unsigned>(suites_to_run.size()));

RUN_ALL_TESTS();

RegisterEventHandler(nullptr);
SetTestSuitesToRun({});
}

void RpcEventHandler::RunAllTestsStart() { service_.WriteTestRunStart(); }

void RpcEventHandler::RunAllTestsEnd(const RunTestsSummary& run_tests_summary) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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<std::string_view> suites_to_run);

void RunAllTestsStart() override;
void RunAllTestsEnd(const RunTestsSummary& run_tests_summary) override;
Expand Down
12 changes: 1 addition & 11 deletions pw_unit_test/unit_test_service.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<unsigned>(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
Expand Down

0 comments on commit b313459

Please sign in to comment.