| // Copyright 2019 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 <algorithm> |
| #include <cstring> |
| |
| #include "light_public_overrides/pw_unit_test/framework_backend.h" |
| #include "pw_assert/check.h" |
| |
| namespace pw { |
| namespace unit_test { |
| |
| void RegisterEventHandler(EventHandler* event_handler) { |
| internal::Framework::Get().RegisterEventHandler(event_handler); |
| } |
| |
| namespace internal { |
| |
| // Singleton instance of the unit test framework class. |
| Framework Framework::framework_; |
| |
| // Linked list of all test cases in the test executable. This is static as it is |
| // populated using static initialization. |
| TestInfo* Framework::tests_ = nullptr; |
| |
| void Framework::RegisterTest(TestInfo* new_test) const { |
| // If the test list is empty, set new_test as the first test. |
| if (tests_ == nullptr) { |
| tests_ = new_test; |
| return; |
| } |
| |
| // Find the right place in the test list to insert new test case. |
| TestInfo* info = tests_; |
| for (; info->next() != nullptr; info = info->next()) { |
| // Stop if this is the last test case from new test's suite. |
| if (strcmp(info->test_case().suite_name, |
| new_test->test_case().suite_name) == 0 && |
| strcmp(info->next()->test_case().suite_name, |
| new_test->test_case().suite_name) != 0) { |
| break; |
| } |
| } |
| |
| new_test->set_next(info->next()); |
| info->set_next(new_test); |
| } |
| |
| int Framework::RunAllTests() { |
| exit_status_ = 0; |
| run_tests_summary_.passed_tests = 0; |
| run_tests_summary_.failed_tests = 0; |
| run_tests_summary_.skipped_tests = 0; |
| run_tests_summary_.disabled_tests = 0; |
| |
| if (event_handler_ != nullptr) { |
| event_handler_->RunAllTestsStart(); |
| } |
| for (const TestInfo* test = tests_; test != nullptr; test = test->next()) { |
| if (ShouldRunTest(*test)) { |
| test->run(); |
| } else if (!test->enabled()) { |
| run_tests_summary_.disabled_tests++; |
| |
| if (event_handler_ != nullptr) { |
| event_handler_->TestCaseDisabled(test->test_case()); |
| } |
| } else { |
| run_tests_summary_.skipped_tests++; |
| } |
| } |
| if (event_handler_ != nullptr) { |
| event_handler_->RunAllTestsEnd(run_tests_summary_); |
| } |
| return exit_status_; |
| } |
| |
| void Framework::SetUpTestSuiteIfNeeded(SetUpTestSuiteFunc set_up_ts) const { |
| if (set_up_ts == Test::SetUpTestSuite) { |
| return; |
| } |
| |
| for (TestInfo* info = tests_; info != current_test_; info = info->next()) { |
| if (info->test_case().suite_name == current_test_->test_case().suite_name) { |
| return; |
| } |
| } |
| |
| set_up_ts(); |
| } |
| |
| void Framework::TearDownTestSuiteIfNeeded( |
| TearDownTestSuiteFunc tear_down_ts) const { |
| if (tear_down_ts == Test::TearDownTestSuite) { |
| return; |
| } |
| |
| for (TestInfo* info = current_test_->next(); info != nullptr; |
| info = info->next()) { |
| if (info->test_case().suite_name == current_test_->test_case().suite_name) { |
| return; |
| } |
| } |
| |
| tear_down_ts(); |
| } |
| |
| void Framework::StartTest(const TestInfo& test) { |
| current_test_ = &test; |
| current_result_ = TestResult::kSuccess; |
| |
| if (event_handler_ != nullptr) { |
| event_handler_->TestCaseStart(test.test_case()); |
| } |
| } |
| |
| void Framework::EndCurrentTest() { |
| switch (current_result_) { |
| case TestResult::kSuccess: |
| run_tests_summary_.passed_tests++; |
| break; |
| case TestResult::kFailure: |
| run_tests_summary_.failed_tests++; |
| break; |
| case TestResult::kSkipped: |
| run_tests_summary_.skipped_tests++; |
| break; |
| } |
| |
| if (event_handler_ != nullptr) { |
| event_handler_->TestCaseEnd(current_test_->test_case(), current_result_); |
| } |
| |
| current_test_ = nullptr; |
| } |
| |
| void Framework::CurrentTestSkip(int line) { |
| if (current_result_ == TestResult::kSuccess) { |
| current_result_ = TestResult::kSkipped; |
| } |
| return CurrentTestExpectSimple( |
| "(test skipped)", "(test skipped)", line, true); |
| } |
| |
| void Framework::CurrentTestExpectSimple(const char* expression, |
| const char* evaluated_expression, |
| int line, |
| bool success) { |
| PW_CHECK_NOTNULL( |
| current_test_, |
| "EXPECT/ASSERT was called when no test was running! EXPECT/ASSERT cannot " |
| "be used from static constructors/destructors or before or after " |
| "RUN_ALL_TESTS()."); |
| |
| if (!success) { |
| current_result_ = TestResult::kFailure; |
| exit_status_ = 1; |
| } |
| |
| if (event_handler_ == nullptr) { |
| return; |
| } |
| |
| TestExpectation expectation = { |
| .expression = expression, |
| .evaluated_expression = evaluated_expression, |
| .line_number = line, |
| .success = success, |
| }; |
| |
| event_handler_->TestCaseExpect(current_test_->test_case(), expectation); |
| } |
| |
| bool Framework::ShouldRunTest(const TestInfo& test_info) const { |
| #if PW_CXX_STANDARD_IS_SUPPORTED(17) |
| // Test suite filtering is only supported if using C++17. |
| if (!test_suites_to_run_.empty()) { |
| std::string_view test_suite(test_info.test_case().suite_name); |
| |
| bool suite_matches = |
| std::any_of(test_suites_to_run_.begin(), |
| test_suites_to_run_.end(), |
| [&](auto& name) { return test_suite == name; }); |
| |
| if (!suite_matches) { |
| return false; |
| } |
| } |
| #endif // PW_CXX_STANDARD_IS_SUPPORTED(17) |
| |
| return test_info.enabled(); |
| } |
| |
| 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 unit_test |
| } // namespace pw |