pw_unit_test: Support ASSERT/EXPECT in test ctor
Make current_test_ a pointer to the TestInfo, and set it before
constructing the test instance. This allows EXPECT/ASSERT statements to
be used in fixture constructors and destructors.
Change-Id: Ib84db91acc080ba103cdb543b4d764b192b398f7
diff --git a/pw_unit_test/framework.cc b/pw_unit_test/framework.cc
index 5c2ba05..1a27eda 100644
--- a/pw_unit_test/framework.cc
+++ b/pw_unit_test/framework.cc
@@ -23,16 +23,6 @@
}
namespace internal {
-namespace {
-
-bool TestIsEnabled(const TestInfo& test) {
- constexpr size_t kStringSize = sizeof("DISABLED_") - 1;
- return std::strncmp("DISABLED_", test.test_case.test_name, kStringSize) !=
- 0 &&
- std::strncmp("DISABLED_", test.test_case.suite_name, kStringSize) != 0;
-}
-
-} // namespace
// Singleton instance of the unit test framework class.
Framework Framework::framework_;
@@ -41,12 +31,18 @@
// populated using static initialization.
TestInfo* Framework::tests_ = nullptr;
-void Framework::RegisterTest(TestInfo* test) {
- // Append the test case to the end of the test list.
- TestInfo** pos = &tests_;
- for (; *pos != nullptr; pos = &(*pos)->next) {
+void Framework::RegisterTest(TestInfo* new_test) {
+ // If the test list is empty, set new_test as the first test.
+ if (tests_ == nullptr) {
+ tests_ = new_test;
+ return;
}
- *pos = test;
+
+ // Append the test case to the end of the test list.
+ TestInfo* info = tests_;
+ for (; info->next() != nullptr; info = info->next()) {
+ }
+ info->set_next(new_test);
}
int Framework::RunAllTests() {
@@ -56,11 +52,11 @@
if (event_handler_ != nullptr) {
event_handler_->RunAllTestsStart();
}
- for (TestInfo* test = tests_; test != nullptr; test = test->next) {
- if (TestIsEnabled(*test)) {
+ for (const TestInfo* test = tests_; test != nullptr; test = test->next()) {
+ if (test->enabled()) {
test->run();
} else {
- event_handler_->TestCaseDisabled(test->test_case);
+ event_handler_->TestCaseDisabled(test->test_case());
}
}
if (event_handler_ != nullptr) {
@@ -69,18 +65,16 @@
return exit_status_;
}
-void Framework::StartTest(Test* test) {
- current_test_ = test;
+void Framework::StartTest(const TestInfo& test) {
+ current_test_ = &test;
current_result_ = TestResult::kSuccess;
- if (event_handler_ == nullptr) {
- return;
+ if (event_handler_ != nullptr) {
+ event_handler_->TestCaseStart(test.test_case());
}
- event_handler_->TestCaseStart(test->pigweed_test_info_->test_case);
}
-void Framework::EndTest(Test* test) {
- current_test_ = nullptr;
+void Framework::EndCurrentTest() {
switch (current_result_) {
case TestResult::kSuccess:
run_tests_summary_.passed_tests++;
@@ -90,12 +84,11 @@
break;
}
- if (event_handler_ == nullptr) {
- return;
+ if (event_handler_ != nullptr) {
+ event_handler_->TestCaseEnd(current_test_->test_case(), current_result_);
}
- event_handler_->TestCaseEnd(test->pigweed_test_info_->test_case,
- current_result_);
+ current_test_ = nullptr;
}
void Framework::ExpectationResult(const char* expression,
@@ -118,10 +111,14 @@
.success = success,
};
- event_handler_->TestCaseExpect(current_test_->pigweed_test_info_->test_case,
- expectation);
+ event_handler_->TestCaseExpect(current_test_->test_case(), expectation);
+}
+
+bool TestInfo::enabled() const {
+ constexpr size_t kStringSize = sizeof("DISABLED_") - 1;
+ return std::strncmp("DISABLED_", test_case().test_name, kStringSize) != 0 &&
+ std::strncmp("DISABLED_", test_case().suite_name, kStringSize) != 0;
}
} // namespace internal
-
} // namespace pw::unit_test
diff --git a/pw_unit_test/framework_test.cc b/pw_unit_test/framework_test.cc
index 2d570ee..10a0ada 100644
--- a/pw_unit_test/framework_test.cc
+++ b/pw_unit_test/framework_test.cc
@@ -162,5 +162,16 @@
EXPECT_EQ(cool_number_, 3500);
}
+class Expectations : public ::testing::Test {
+ protected:
+ Expectations() : cool_number_(3) { ASSERT_EQ(cool_number_, 3); }
+
+ ~Expectations() { ASSERT_EQ(cool_number_, 14159); }
+
+ int cool_number_;
+};
+
+TEST_F(Expectations, SetCoolNumber) { cool_number_ = 14159; }
+
} // namespace
} // namespace pw
diff --git a/pw_unit_test/public/pw_unit_test/framework.h b/pw_unit_test/public/pw_unit_test/framework.h
index a74304b..6588cc5 100644
--- a/pw_unit_test/public/pw_unit_test/framework.h
+++ b/pw_unit_test/public/pw_unit_test/framework.h
@@ -143,7 +143,7 @@
namespace internal {
-struct TestInfo;
+class TestInfo;
// Singleton test framework class responsible for managing and running test
// cases. This implementation is internal to Pigweed test; free functions
@@ -184,7 +184,7 @@
// statically allocated per test case, with a run() function that references
// this method instantiated for its test class.
template <typename TestInstance>
- static void CreateAndRunTest() {
+ static void CreateAndRunTest(const TestInfo& test_info) {
// TODO(frolv): Update the assert message with the name of the config option
// for memory pool size once it is configurable.
static_assert(
@@ -193,17 +193,18 @@
"kTestMemoryPoolSizeBytes or decrease the size of your test fixture.");
Framework& framework = Get();
+ framework.StartTest(test_info);
- // Construct the test object within the static memory pool.
+ // Construct the test object within the static memory pool. The StartTest
+ // function has already been called by the TestInfo at this point.
TestInstance* test_instance = new (&framework.memory_pool_) TestInstance;
-
- framework.StartTest(test_instance);
test_instance->PigweedTestRun();
- framework.EndTest(test_instance);
// Manually call the destructor as it is not called automatically for
// objects constructed using placement new.
test_instance->~TestInstance();
+
+ framework.EndCurrentTest();
}
// Runs an expectation function for the currently active test case.
@@ -245,11 +246,11 @@
bool success);
private:
- // Dispatches an event indicating that a test started running.
- void StartTest(Test* test);
+ // Sets current_test_ and dispatches an event indicating that a test started.
+ void StartTest(const TestInfo& test);
- // Dispatches an event indicating that a test finished running.
- void EndTest(Test* test);
+ // Dispatches event indicating that a test finished and clears current_test_.
+ void EndCurrentTest();
// Singleton instance of the framework class.
static Framework framework_;
@@ -259,7 +260,7 @@
static TestInfo* tests_;
// The current test case which is running.
- Test* current_test_;
+ const TestInfo* current_test_;
// Overall result of the current test case (pass/fail).
TestResult current_result_;
@@ -283,30 +284,41 @@
// Information about a single test case, including a pointer to a function which
// constructs and runs the test class. These are statically allocated instead of
// the test classes, as test classes can be very large.
-struct TestInfo {
+class TestInfo {
+ public:
TestInfo(const char* const test_suite_name,
const char* const test_name,
const char* const file_name,
- void (*run)())
- : test_case{
+ void (*run)(const TestInfo&))
+ : test_case_{
.suite_name = test_suite_name,
.test_name = test_name,
.file_name = file_name,
- }, run(run) {
+ }, run_(run) {
Framework::Get().RegisterTest(this);
}
// The name of the suite to which the test case belongs, the name of the test
// case itself, and the path to the file in which the test case is located.
- TestCase test_case;
+ const TestCase& test_case() const { return test_case_; }
+
+ bool enabled() const;
+
+ void run() const { run_(*this); }
+
+ TestInfo* next() const { return next_; }
+ void set_next(TestInfo* next) { next_ = next; }
+
+ private:
+ TestCase test_case_;
// Function which runs the test case. Refers to Framework::CreateAndRunTest
// instantiated for the test case's class.
- void (*run)();
+ void (*run_)(const TestInfo&);
// TestInfo structs are registered with the test framework and stored as a
// linked list.
- TestInfo* next = nullptr;
+ TestInfo* next_ = nullptr;
};
} // namespace internal
@@ -332,18 +344,9 @@
virtual ~Test() = default;
- protected:
- // Called by subclasses' constructors with their TestInfo instances.
- void PigweedSetTestInfo(const internal::TestInfo* test_info) {
- pigweed_test_info_ = test_info;
- }
-
private:
friend class internal::Framework;
- // Pointer to the TestInfo struct statically allocated for the test case.
- const internal::TestInfo* pigweed_test_info_;
-
// The user-provided body of the test case. Populated by the TEST macro.
virtual void PigweedTestBody() = 0;
};
@@ -354,32 +357,26 @@
#define _PW_TEST_CLASS_NAME(test_suite_name, test_name) \
PW_CONCAT(test_suite_name, _, test_name, _Test)
-#define _PW_TEST(test_suite_name, test_name, parent_class) \
- static_assert(sizeof(PW_STRINGIFY(test_suite_name)) > 1, \
- "test_suite_name must not be empty"); \
- static_assert(sizeof(PW_STRINGIFY(test_name)) > 1, \
- "test_name must not be empty"); \
- \
- class _PW_TEST_CLASS_NAME(test_suite_name, test_name) final \
- : public parent_class { \
- public: \
- _PW_TEST_CLASS_NAME(test_suite_name, test_name)() { \
- PigweedSetTestInfo(&test_info_); \
- } \
- \
- private: \
- void PigweedTestBody() override; \
- static ::pw::unit_test::internal::TestInfo test_info_; \
- }; \
- \
- ::pw::unit_test::internal::TestInfo \
- _PW_TEST_CLASS_NAME(test_suite_name, test_name)::test_info_( \
- PW_STRINGIFY(test_suite_name), \
- PW_STRINGIFY(test_name), \
- __FILE__, \
- ::pw::unit_test::internal::Framework::CreateAndRunTest< \
- _PW_TEST_CLASS_NAME(test_suite_name, test_name)>); \
- \
+#define _PW_TEST(test_suite_name, test_name, parent_class) \
+ static_assert(sizeof(#test_suite_name) > 1, \
+ "test_suite_name must not be empty"); \
+ static_assert(sizeof(#test_name) > 1, "test_name must not be empty"); \
+ \
+ class _PW_TEST_CLASS_NAME(test_suite_name, test_name) final \
+ : public parent_class { \
+ private: \
+ void PigweedTestBody() override; \
+ static ::pw::unit_test::internal::TestInfo test_info_; \
+ }; \
+ \
+ ::pw::unit_test::internal::TestInfo \
+ _PW_TEST_CLASS_NAME(test_suite_name, test_name)::test_info_( \
+ #test_suite_name, \
+ #test_name, \
+ __FILE__, \
+ ::pw::unit_test::internal::Framework::CreateAndRunTest< \
+ _PW_TEST_CLASS_NAME(test_suite_name, test_name)>); \
+ \
void _PW_TEST_CLASS_NAME(test_suite_name, test_name)::PigweedTestBody()
#define _PW_TEST_EXPECT(lhs, rhs, expectation, expectation_string) \