| #include "quiche/common/bug_utils.h" |
| |
| #include <string.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <string> |
| |
| #include "absl/base/log_severity.h" |
| #include "absl/strings/string_view.h" |
| #include "quiche/common/bug_utils_test_helper.h" |
| #include "quiche/common/platform/api/quiche_test.h" |
| |
| namespace quiche { |
| namespace internal { |
| namespace { |
| |
| using ::testing::_; |
| using ::testing::AnyNumber; |
| using ::testing::EndsWith; |
| using ::testing::InSequence; |
| |
| class BugHandler { |
| public: |
| virtual ~BugHandler() = default; |
| virtual void OnBug(absl::string_view file, int line, |
| absl::string_view message) = 0; |
| }; |
| |
| // This class provides a convenient way to write expectations for the bug |
| // override function. |
| class MockBugHandler : public BugHandler { |
| public: |
| MockBugHandler() = default; |
| |
| MOCK_METHOD(void, OnBug, |
| (absl::string_view file, int line, absl::string_view message), |
| (override)); |
| }; |
| |
| MockBugHandler* mock_bug_handler = nullptr; |
| |
| MockBugHandler* GetInstance() { |
| if (mock_bug_handler == nullptr) { |
| mock_bug_handler = new MockBugHandler; |
| } |
| return mock_bug_handler; |
| } |
| |
| void ResetInstance() { |
| delete mock_bug_handler; |
| mock_bug_handler = nullptr; |
| } |
| |
| class BugUtilsTest : public ::quiche::test::QuicheTest { |
| public: |
| void SetUp() override { |
| fn_ = GenericBugStreamHandler::GetOverrideFunction(); |
| GenericBugStreamHandler::SetOverrideFunction( |
| [](absl::LogSeverity /*severity*/, const char* file, int line, |
| absl::string_view log_message) { |
| GetInstance()->OnBug(absl::string_view(file, strlen(file)), line, |
| log_message); |
| }); |
| } |
| |
| void TearDown() override { |
| GenericBugStreamHandler::SetOverrideFunction(fn_); |
| ResetInstance(); |
| } |
| |
| inline static GenericBugStreamHandler::OverrideFunction fn_ = nullptr; |
| }; |
| |
| // Tests several permutations. |
| TEST_F(BugUtilsTest, TestsEverythingUsing23And26) { |
| InSequence seq; |
| EXPECT_CALL(*GetInstance(), OnBug(EndsWith("bug_utils_test_helper.h"), 23, |
| EndsWith("Here on line 23"))); |
| LogBugLine23(); |
| |
| EXPECT_CALL(*GetInstance(), OnBug(EndsWith("bug_utils_test_helper.h"), 23, |
| EndsWith("Here on line 23"))); |
| LogBugLine23(); |
| |
| EXPECT_CALL(*GetInstance(), OnBug(EndsWith("bug_utils_test_helper.h"), 26, |
| EndsWith("Here on line 26"))); |
| EXPECT_CALL(*GetInstance(), OnBug(EndsWith("bug_utils_test_helper.h"), 27, |
| EndsWith("And 27!"))); |
| LogBugLine26(); |
| } |
| |
| TEST_F(BugUtilsTest, TestBugIf) { |
| InSequence seq; |
| |
| // Verify that we don't invoke the function for a false condition. |
| LogIfBugLine31(false); |
| |
| // The first true should trigger an invocation. |
| EXPECT_CALL(*GetInstance(), OnBug(EndsWith("bug_utils_test_helper.h"), 31, |
| EndsWith("Here on line 31"))); |
| LogIfBugLine31(true); |
| |
| // It's always a no-op if the condition is false. |
| LogIfBugLine31(false); // no-op |
| LogIfBugLine31(false); // no-op |
| } |
| |
| TEST_F(BugUtilsTest, TestBugIfMessage) { |
| int i; |
| |
| // Check success |
| LogIfBugNullCheckLine35(&i); |
| |
| // Check failure |
| EXPECT_CALL( |
| *GetInstance(), |
| OnBug( |
| EndsWith("bug_utils_test_helper.h"), 35, |
| EndsWith( |
| "QUICHE_TEST_BUG_IF(Bug 35, ptr == nullptr): Here on line 35"))); |
| LogIfBugNullCheckLine35(nullptr); |
| } |
| |
| // Don't actually need to crash, just cause a side effect the test can assert |
| // on. |
| int num_times_called = 0; |
| bool BadCondition() { |
| ++num_times_called; |
| return true; |
| } |
| |
| TEST_F(BugUtilsTest, BadCondition) { |
| InSequence seq; |
| |
| EXPECT_EQ(num_times_called, 0); |
| |
| EXPECT_CALL(*GetInstance(), OnBug(_, _, EndsWith("Called BadCondition"))); |
| QUICHE_TEST_BUG_IF(id, BadCondition()) << "Called BadCondition"; |
| EXPECT_EQ(num_times_called, 1); |
| } |
| |
| TEST_F(BugUtilsTest, NoDanglingElse) { |
| auto unexpected_bug_message = [] { |
| ADD_FAILURE() << "This should not be called"; |
| return "bad"; |
| }; |
| |
| if (false) QUICHE_TEST_BUG(dangling_else) << unexpected_bug_message(); |
| |
| bool expected_else_reached = false; |
| if (false) |
| QUICHE_TEST_BUG(dangling_else_2) << unexpected_bug_message(); |
| else |
| expected_else_reached = true; |
| |
| EXPECT_TRUE(expected_else_reached); |
| } |
| |
| TEST_F(BugUtilsTest, BugListener) { |
| class TestListener : public GenericBugListener { |
| public: |
| explicit TestListener(bool expect_log_message) |
| : expect_log_message_(expect_log_message) {} |
| |
| ~TestListener() override { EXPECT_EQ(hit_count_, 1); } |
| |
| void OnBug(const char* bug_id, const char* file, int line, |
| absl::string_view bug_message) override { |
| ++hit_count_; |
| EXPECT_EQ(bug_id, "bug_listener_test"); |
| EXPECT_EQ(file, __FILE__); |
| EXPECT_GT(line, 0); |
| if (expect_log_message_) { |
| EXPECT_EQ(bug_message, "TEST_BUG(bug_listener_test): Bug listener msg"); |
| } else { |
| EXPECT_EQ(bug_message, ""); |
| } |
| } |
| |
| TestListener* self() { return this; } |
| |
| private: |
| int hit_count_ = 0; |
| const bool expect_log_message_; |
| }; |
| |
| GENERIC_BUG_IMPL("TEST_BUG", bug_listener_test, /*skip_log_condition=*/false, |
| QUICHE_TEST_BUG_OPTIONS().SetBugListener( |
| TestListener(/*expect_log_message=*/true).self())) |
| << "Bug listener msg"; |
| |
| GENERIC_BUG_IMPL("TEST_BUG", bug_listener_test, /*skip_log_condition=*/true, |
| QUICHE_TEST_BUG_OPTIONS().SetBugListener( |
| TestListener(/*expect_log_message=*/false).self())) |
| << "Bug listener msg"; |
| } |
| |
| } // namespace |
| } // namespace internal |
| } // namespace quiche |