diff --git a/pw_unit_test/BUILD.bazel b/pw_unit_test/BUILD.bazel index cb20a71e0d..853dc96f7c 100644 --- a/pw_unit_test/BUILD.bazel +++ b/pw_unit_test/BUILD.bazel @@ -249,6 +249,28 @@ pw_cc_binary( ], ) +cc_library( + name = "multi_event_handler", + hdrs = [ + "public/pw_unit_test/multi_event_handler.h", + ], + includes = [ + "public", + ], + deps = [ + ":event_handler", + ], +) + +pw_cc_test( + name = "multi_event_handler_test", + srcs = ["multi_event_handler_test.cc"], + deps = [ + ":multi_event_handler", + ":pw_unit_test", + ], +) + proto_library( name = "unit_test_proto", srcs = ["pw_unit_test_proto/unit_test.proto"], diff --git a/pw_unit_test/BUILD.gn b/pw_unit_test/BUILD.gn index 7ee27f1261..95617d39bb 100644 --- a/pw_unit_test/BUILD.gn +++ b/pw_unit_test/BUILD.gn @@ -237,6 +237,21 @@ pw_source_set("logging_main") { sources = [ "logging_main.cc" ] } +# Library providing an event handler adapter that allows for multiple +# event handlers to be registered for a given test run +pw_source_set("multi_event_handler") { + public_deps = [ ":event_handler" ] + public = [ "public/pw_unit_test/multi_event_handler.h" ] +} + +pw_test("multi_event_handler_test") { + sources = [ "multi_event_handler_test.cc" ] + deps = [ + ":multi_event_handler", + ":pw_unit_test", + ] +} + config("rpc_service_backend_light") { include_dirs = [ "rpc_light_public" ] } @@ -379,6 +394,7 @@ pw_test_group("tests") { ":framework_test", ":framework_light_test", ":static_library_support_test", + ":multi_event_handler_test", ] if (dir_pw_third_party_googletest != "") { tests += [ ":googletest_test_matchers_test" ] diff --git a/pw_unit_test/CMakeLists.txt b/pw_unit_test/CMakeLists.txt index 89bc5c2a00..5bf93ee901 100644 --- a/pw_unit_test/CMakeLists.txt +++ b/pw_unit_test/CMakeLists.txt @@ -175,3 +175,21 @@ pw_add_library(pw_unit_test.logging_main STATIC pw_unit_test.logging_event_handler pw_unit_test ) + +pw_add_library(pw_unit_test.multi_event_handler INTERFACE + HEADERS + public/pw_unit_test/multi_event_handler.h + PUBLIC_INCLUDES + public + PUBLIC_DEPS + pw_unit_test.event_handler +) + +pw_add_test(pw_unit_test.multi_event_handler_test + SOURCES + multi_event_handler_test.cc + PRIVATE_DEPS + pw_unit_test.multi_event_handler + GROUPS + pw_unit_test +) \ No newline at end of file diff --git a/pw_unit_test/docs.rst b/pw_unit_test/docs.rst index 6f84f88f19..a3940660af 100644 --- a/pw_unit_test/docs.rst +++ b/pw_unit_test/docs.rst @@ -203,6 +203,13 @@ GoogleTest-style output using the shared Event handler that uses ``std::printf`` to output test results. +.. cpp:class:: MultiEventHandler : public GoogleTestStyleEventHandler + + An event handler adapter that allows you to register multiple event handlers + for a test run. To use it, create a MultiEventHandler object by passing + in the event handlers you want to use into the constructor. Then, register + the MultiEventHandler object as the test run's event handler. + .. cpp:namespace-pop:: Test filtering diff --git a/pw_unit_test/multi_event_handler_test.cc b/pw_unit_test/multi_event_handler_test.cc new file mode 100644 index 0000000000..c0ca89786d --- /dev/null +++ b/pw_unit_test/multi_event_handler_test.cc @@ -0,0 +1,155 @@ +// Copyright 2024 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 "pw_unit_test/multi_event_handler.h" + +#include "pw_unit_test/framework.h" + +namespace pw::unit_test { +namespace { + +// Fake event handler that keeps track of how many times its functions were +// invoked +class FakeEventHandler : public EventHandler { + public: + struct { + int TestProgramStart = 0; + int EnvironmentsSetUpEnd = 0; + int TestSuiteStart = 0; + int TestSuiteEnd = 0; + int EnvironmentsTearDownEnd = 0; + int TestProgramEnd = 0; + int RunAllTestsStart = 0; + int RunAllTestsEnd = 0; + int TestCaseStart = 0; + int TestCaseEnd = 0; + int TestCaseExpect = 0; + int TestCaseDisabled = 0; + } function_invocation_counts; + + void TestProgramStart(const ProgramSummary&) override { + function_invocation_counts.TestProgramStart++; + } + void EnvironmentsSetUpEnd() override { + function_invocation_counts.EnvironmentsSetUpEnd++; + } + void TestSuiteStart(const TestSuite&) override { + function_invocation_counts.TestSuiteStart++; + } + void TestSuiteEnd(const TestSuite&) override { + function_invocation_counts.TestSuiteEnd++; + } + void EnvironmentsTearDownEnd() override { + function_invocation_counts.EnvironmentsTearDownEnd++; + } + void TestProgramEnd(const ProgramSummary&) override { + function_invocation_counts.TestProgramEnd++; + } + void RunAllTestsStart() override { + function_invocation_counts.RunAllTestsStart++; + } + void RunAllTestsEnd(const RunTestsSummary&) override { + function_invocation_counts.RunAllTestsEnd++; + } + void TestCaseStart(const TestCase&) override { + function_invocation_counts.TestCaseStart++; + } + void TestCaseEnd(const TestCase&, TestResult) override { + function_invocation_counts.TestCaseEnd++; + } + void TestCaseExpect(const TestCase&, const TestExpectation&) override { + function_invocation_counts.TestCaseExpect++; + } + void TestCaseDisabled(const TestCase&) override { + function_invocation_counts.TestCaseDisabled++; + } +}; + +// Helper method for ensuring all methods of an event handler were called x +// number of times +void AssertFunctionInvocationCounts(FakeEventHandler handler, + int num_invocations) { + ASSERT_EQ(handler.function_invocation_counts.TestProgramStart, + num_invocations); + ASSERT_EQ(handler.function_invocation_counts.EnvironmentsSetUpEnd, + num_invocations); + ASSERT_EQ(handler.function_invocation_counts.TestSuiteStart, num_invocations); + ASSERT_EQ(handler.function_invocation_counts.TestSuiteEnd, num_invocations); + ASSERT_EQ(handler.function_invocation_counts.EnvironmentsTearDownEnd, + num_invocations); + ASSERT_EQ(handler.function_invocation_counts.TestProgramEnd, num_invocations); + ASSERT_EQ(handler.function_invocation_counts.RunAllTestsStart, + num_invocations); + ASSERT_EQ(handler.function_invocation_counts.RunAllTestsEnd, num_invocations); + ASSERT_EQ(handler.function_invocation_counts.TestCaseStart, num_invocations); + ASSERT_EQ(handler.function_invocation_counts.TestCaseEnd, num_invocations); + ASSERT_EQ(handler.function_invocation_counts.TestCaseExpect, num_invocations); + ASSERT_EQ(handler.function_invocation_counts.TestCaseDisabled, + num_invocations); +} + +TEST(AllEventHandlerMethodsCalled, InvokeMethodMultipleTimes) { + FakeEventHandler h1; + FakeEventHandler h2; + MultiEventHandler<2> multi_handler(h1, h2); + + ASSERT_EQ(h1.function_invocation_counts.TestCaseStart, 0); + ASSERT_EQ(h2.function_invocation_counts.TestCaseEnd, 0); + + TestCase test_case{}; + TestResult test_result = TestResult::kSuccess; + multi_handler.TestCaseStart(test_case); + multi_handler.TestCaseStart(test_case); + multi_handler.TestCaseStart(test_case); + multi_handler.TestCaseEnd(test_case, test_result); + multi_handler.TestCaseEnd(test_case, test_result); + multi_handler.TestCaseEnd(test_case, test_result); + + ASSERT_EQ(h1.function_invocation_counts.TestCaseStart, 3); + ASSERT_EQ(h2.function_invocation_counts.TestCaseEnd, 3); +} + +TEST(AllEventHandlerMethodsCalled, InvokeAllEventHandlerMethods) { + FakeEventHandler h1; + FakeEventHandler h2; + MultiEventHandler<2> multi_handler(h1, h2); + + AssertFunctionInvocationCounts(h1, 0); + AssertFunctionInvocationCounts(h2, 0); + + ProgramSummary program_summary{}; + TestSuite test_suite{}; + TestCase test_case{}; + RunTestsSummary run_test_summary{}; + TestExpectation expectation{}; + TestResult test_result = TestResult::kSuccess; + multi_handler.TestProgramStart(program_summary); + multi_handler.EnvironmentsSetUpEnd(); + multi_handler.TestSuiteStart(test_suite); + multi_handler.TestSuiteEnd(test_suite); + multi_handler.EnvironmentsTearDownEnd(); + multi_handler.TestProgramEnd(program_summary); + multi_handler.RunAllTestsStart(); + multi_handler.RunAllTestsEnd(run_test_summary); + multi_handler.TestCaseStart(test_case); + multi_handler.TestCaseEnd(test_case, test_result); + multi_handler.TestCaseExpect(test_case, expectation); + multi_handler.TestCaseDisabled(test_case); + + AssertFunctionInvocationCounts(h1, 1); + AssertFunctionInvocationCounts(h2, 1); +} + +} // namespace +} // namespace pw::unit_test \ No newline at end of file diff --git a/pw_unit_test/public/pw_unit_test/multi_event_handler.h b/pw_unit_test/public/pw_unit_test/multi_event_handler.h new file mode 100644 index 0000000000..f130ccd741 --- /dev/null +++ b/pw_unit_test/public/pw_unit_test/multi_event_handler.h @@ -0,0 +1,101 @@ +// Copyright 2024 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 +#include + +#include "pw_unit_test/event_handler.h" + +namespace pw::unit_test { + +// An event handler adapter that allows for multiple event handlers to be +// registered and used during test runs +template +class MultiEventHandler : public EventHandler { + public: + template < + typename... EventHandlers, + typename = std::enable_if_t> + constexpr MultiEventHandler(EventHandlers&... event_handlers) + : event_handlers_{&event_handlers...} {} + + void TestProgramStart(const ProgramSummary& program_summary) override { + for (EventHandler* event_handler : event_handlers_) { + event_handler->TestProgramStart(program_summary); + } + } + void EnvironmentsSetUpEnd() override { + for (EventHandler* event_handler : event_handlers_) { + event_handler->EnvironmentsSetUpEnd(); + } + } + void TestSuiteStart(const TestSuite& test_suite) override { + for (EventHandler* event_handler : event_handlers_) { + event_handler->TestSuiteStart(test_suite); + } + } + void TestSuiteEnd(const TestSuite& test_suite) override { + for (EventHandler* event_handler : event_handlers_) { + event_handler->TestSuiteEnd(test_suite); + } + } + void EnvironmentsTearDownEnd() override { + for (EventHandler* event_handler : event_handlers_) { + event_handler->EnvironmentsTearDownEnd(); + } + } + void TestProgramEnd(const ProgramSummary& program_summary) override { + for (EventHandler* event_handler : event_handlers_) { + event_handler->TestProgramEnd(program_summary); + } + } + void RunAllTestsStart() override { + for (EventHandler* event_handler : event_handlers_) { + event_handler->RunAllTestsStart(); + } + } + void RunAllTestsEnd(const RunTestsSummary& run_tests_summary) override { + for (EventHandler* event_handler : event_handlers_) { + event_handler->RunAllTestsEnd(run_tests_summary); + } + } + void TestCaseStart(const TestCase& test_case) override { + for (EventHandler* event_handler : event_handlers_) { + event_handler->TestCaseStart(test_case); + } + } + void TestCaseEnd(const TestCase& test_case, TestResult result) override { + for (EventHandler* event_handler : event_handlers_) { + event_handler->TestCaseEnd(test_case, result); + } + } + void TestCaseExpect(const TestCase& test_case, + const TestExpectation& expectation) override { + for (EventHandler* event_handler : event_handlers_) { + event_handler->TestCaseExpect(test_case, expectation); + } + } + void TestCaseDisabled(const TestCase& test_case) override { + for (EventHandler* event_handler : event_handlers_) { + event_handler->TestCaseDisabled(test_case); + } + } + + private: + static_assert(kNumHandlers > 0); + std::array event_handlers_; +}; + +} // namespace pw::unit_test