| #include "aos/testing/test_logging.h" |
| |
| #include <stdio.h> |
| |
| #include <vector> |
| |
| #include "gtest/gtest.h" |
| |
| #include "aos/logging/implementations.h" |
| #include "aos/mutex/mutex.h" |
| #include "absl/base/call_once.h" |
| |
| using ::aos::logging::LogMessage; |
| |
| namespace aos { |
| namespace testing { |
| namespace { |
| |
| class TestLogImplementation : public logging::HandleMessageLogImplementation { |
| public: |
| const ::std::vector<LogMessage> &messages() { return messages_; } |
| |
| // Sets the current thread's time to be monotonic_now for logging. |
| void MockTime(::aos::monotonic_clock::time_point monotonic_now) { |
| mock_time_ = true; |
| monotonic_now_ = monotonic_now; |
| } |
| |
| // Clears any mock time for the current thread. |
| void UnMockTime() { mock_time_ = false; } |
| |
| ::aos::monotonic_clock::time_point monotonic_now() const override { |
| if (mock_time_) { |
| return monotonic_now_; |
| } |
| return ::aos::monotonic_clock::now(); |
| } |
| |
| // This class has to be a singleton so that everybody can get access to the |
| // same instance to read out the messages etc. |
| static TestLogImplementation *GetInstance() { |
| static absl::once_flag once; |
| absl::call_once(once, CreateInstance); |
| return instance; |
| } |
| |
| // Clears out all of the messages already recorded. |
| void ClearMessages() { |
| ::aos::MutexLocker locker(&messages_mutex_); |
| messages_.clear(); |
| } |
| |
| // Prints out all of the messages (like when a test fails). |
| void PrintAllMessages() { |
| ::aos::MutexLocker locker(&messages_mutex_); |
| for (auto it = messages_.begin(); it != messages_.end(); ++it) { |
| logging::internal::PrintMessage(stdout, *it); |
| } |
| } |
| |
| void SetOutputFile(const char *filename) { |
| if (strcmp("-", filename) != 0) { |
| FILE *newfile = fopen(filename, "w"); |
| |
| if (newfile) { |
| output_file_ = newfile; |
| } |
| } |
| } |
| |
| bool fill_type_cache() override { return false; } |
| |
| void PrintMessagesAsTheyComeIn() { print_as_messages_come_in_ = true; } |
| |
| private: |
| static TestLogImplementation *instance; |
| TestLogImplementation() {} |
| ~TestLogImplementation() { |
| if (output_file_ != stdout) { |
| fclose(output_file_); |
| } |
| } |
| |
| static void CreateInstance() { |
| instance = new TestLogImplementation(); |
| } |
| |
| virtual void HandleMessage(const LogMessage &message) override { |
| ::aos::MutexLocker locker(&messages_mutex_); |
| if (message.level == FATAL || print_as_messages_come_in_) { |
| logging::internal::PrintMessage(output_file_, message); |
| } |
| |
| messages_.push_back(message); |
| } |
| |
| ::std::vector<LogMessage> messages_; |
| bool print_as_messages_come_in_ = false; |
| FILE *output_file_ = stdout; |
| ::aos::Mutex messages_mutex_; |
| |
| // Thread local storage for mock time. This is thread local because if |
| // someone spawns a thread and goes to town in parallel with a simulated event |
| // loop, we want to just print the actual monotonic clock out. |
| static thread_local bool mock_time_; |
| static thread_local ::aos::monotonic_clock::time_point monotonic_now_; |
| }; |
| |
| TestLogImplementation *TestLogImplementation::instance; |
| |
| thread_local bool TestLogImplementation::mock_time_ = false; |
| thread_local ::aos::monotonic_clock::time_point |
| TestLogImplementation::monotonic_now_ = ::aos::monotonic_clock::min_time; |
| |
| class MyTestEventListener : public ::testing::EmptyTestEventListener { |
| virtual void OnTestStart(const ::testing::TestInfo & /*test_info*/) { |
| TestLogImplementation::GetInstance()->ClearMessages(); |
| } |
| virtual void OnTestEnd(const ::testing::TestInfo &test_info) { |
| if (test_info.result()->Failed()) { |
| printf("Test %s failed. Use '--print-logs' to see all log messages.\n", |
| test_info.name()); |
| } |
| } |
| |
| virtual void OnTestPartResult( const ::testing::TestPartResult &result) { |
| if (result.failed()) { |
| const char *failure_type = "unknown"; |
| switch (result.type()) { |
| case ::testing::TestPartResult::Type::kNonFatalFailure: |
| failure_type = "EXPECT"; |
| break; |
| case ::testing::TestPartResult::Type::kFatalFailure: |
| failure_type = "ASSERT"; |
| break; |
| case ::testing::TestPartResult::Type::kSuccess: |
| break; |
| } |
| log_do(ERROR, "%s: %d: gtest %s failure\n%s\n", |
| result.file_name(), |
| result.line_number(), |
| failure_type, |
| result.message()); |
| } |
| } |
| }; |
| |
| void *DoEnableTestLogging() { |
| logging::Init(); |
| logging::SetImplementation(TestLogImplementation::GetInstance()); |
| |
| ::testing::UnitTest::GetInstance()->listeners().Append( |
| new MyTestEventListener()); |
| |
| return nullptr; |
| } |
| |
| static absl::once_flag enable_test_logging_once; |
| |
| } // namespace |
| |
| void EnableTestLogging() { |
| absl::call_once(enable_test_logging_once, DoEnableTestLogging); |
| } |
| |
| void SetLogFileName(const char* filename) { |
| TestLogImplementation::GetInstance()->SetOutputFile(filename); |
| } |
| |
| void ForcePrintLogsDuringTests() { |
| TestLogImplementation::GetInstance()->PrintMessagesAsTheyComeIn(); |
| } |
| |
| void MockTime(::aos::monotonic_clock::time_point monotonic_now) { |
| TestLogImplementation::GetInstance()->MockTime(monotonic_now); |
| } |
| void UnMockTime() { |
| TestLogImplementation::GetInstance()->UnMockTime(); |
| } |
| |
| } // namespace testing |
| } // namespace aos |