rupprecht created this revision.
Herald added a subscriber: mgorny.
Herald added a project: All.
rupprecht requested review of this revision.
Herald added a project: LLDB.
Herald added a subscriber: lldb-commits.

This helper class can be useful to see what `LLDB_LOG` messages are happening 
when debugging the LLDB C++ unit tests. It isn't pretty, but on the other hand, 
it's meant for temporary debugging and not something one would want to check in 
anyway, so maybe that's OK.

This can be used like so:

  TEST(FooTest, HasABug) {
    Func1(); // LLDB_LOG statements this makes won't go anywhere
    {
      auto logger = TestStderrLogger::Scoped(LLDBLog::AST | 
LLDBLog::Breakpoints);
      Func2(); // Now they'll go to stderr.
    }
    Func3(); // Now they go nowhere again.
  }

It can be created in a way that mirrors `GetLog()` calls, i.e. deducing from 
the enum, or by directly using the strings that are registered.

Right now this only works for the LLDBLog enum, but it should be trivial to add 
more. I don't know of a good generic way to handle arbitrary enums.

I was able to use this to debug a flaky unit test that was failing ~1/100 
times, so my approach was to enable logs and compare good vs bad log methods. I 
didn't find an easier way than this helper class.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D133628

Files:
  lldb/unittests/TestingSupport/CMakeLists.txt
  lldb/unittests/TestingSupport/TestStderrLogger.cpp
  lldb/unittests/TestingSupport/TestStderrLogger.h
  lldb/unittests/TestingSupport/tests/CMakeLists.txt
  lldb/unittests/TestingSupport/tests/TestStderrLoggerTest.cpp

Index: lldb/unittests/TestingSupport/tests/TestStderrLoggerTest.cpp
===================================================================
--- /dev/null
+++ lldb/unittests/TestingSupport/tests/TestStderrLoggerTest.cpp
@@ -0,0 +1,59 @@
+//===-- TestStderrLoggerTest.cpp ------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "TestingSupport/TestStderrLogger.h"
+#include "gtest/gtest.h"
+
+using namespace lldb_private;
+using namespace lldb;
+
+TEST(TestStderrLoggerTest, Strings) {
+  LLDB_LOG(GetLog(LLDBLog::AST), "This won't be logged");
+  {
+    auto logger = TestStderrLogger::Scoped("lldb", {"ast"});
+    LLDB_LOG(GetLog(LLDBLog::AST), "This will be logged");
+  }
+  LLDB_LOG(GetLog(LLDBLog::AST), "This won't be logged either");
+}
+
+TEST(TestStderrLoggerTest, Special) {
+  EXPECT_EQ(1, 2);
+  {
+    auto logger = TestStderrLogger::Scoped("lldb", {"all"});
+    LLDB_LOG(GetLog(LLDBLog::Breakpoints), "break is part of all");
+  }
+  {
+    auto logger = TestStderrLogger::Scoped("lldb", {"default"});
+    LLDB_LOG(GetLog(LLDBLog::AST), "ast is not part of default");
+    LLDB_LOG(GetLog(LLDBLog::Breakpoints), "break is part of default");
+  }
+}
+
+TEST(TestStderrLoggerTest, Enums) {
+  auto logger = TestStderrLogger::Scoped(LLDBLog::AST | LLDBLog::Breakpoints);
+  LLDB_LOG(GetLog(LLDBLog::AST), "AST lines are logged");
+  LLDB_LOG(GetLog(LLDBLog::Breakpoints), "Breakpoint lines are logged");
+  LLDB_LOG(GetLog(LLDBLog::Unwind), "Unwind lines are not logged");
+}
+
+TEST(TestStderrLoggerTest, SwitchSource) {
+  std::string log_dest;
+  llvm::raw_string_ostream ostream_dest(log_dest);
+
+  {
+    auto logger = TestStderrLogger::Scoped("lldb", {"ast"}, /*log_options=*/0,
+                                           ostream_dest);
+    LLDB_LOG(GetLog(LLDBLog::AST), "This goes to a stream");
+  }
+
+  {
+    auto logger = TestStderrLogger::Scoped("lldb", {"ast"});
+    LLDB_LOG(GetLog(LLDBLog::AST), "This goes to stderr");
+  }
+  EXPECT_EQ(log_dest, "This goes to a stream\n");
+}
Index: lldb/unittests/TestingSupport/tests/CMakeLists.txt
===================================================================
--- /dev/null
+++ lldb/unittests/TestingSupport/tests/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Note: "TestingSupportTests" already exists in LLVM. Prefix with "LLDB" to
+# make it unique.
+add_lldb_unittest(LLDBTestingSupportTests
+  TestStderrLoggerTest.cpp
+
+  LINK_LIBS
+    lldbUtilityHelpers
+  )
+  
\ No newline at end of file
Index: lldb/unittests/TestingSupport/TestStderrLogger.h
===================================================================
--- /dev/null
+++ lldb/unittests/TestingSupport/TestStderrLogger.h
@@ -0,0 +1,89 @@
+//===- TestStderrLogger.h ---------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_UNITTESTS_TESTINGSUPPORT_TESTUTILITIES_H
+#define LLDB_UNITTESTS_TESTINGSUPPORT_TESTUTILITIES_H
+
+#include "lldb/Utility/LLDBLog.h"
+#include "llvm/Support/Threading.h"
+
+namespace lldb_private {
+
+// Map categories to their name. This is not exposed at the moment so we hard
+// code it this way.
+template <typename Cat> llvm::StringRef ChannelName() = delete;
+template <> llvm::StringRef ChannelName<LLDBLog>();
+
+/// Scoped logger to dump everything logged through LLDB's logging macros, such
+/// as LLDB_LOGF, to stderr.
+///
+/// This is meant as a local debugging tool to see what is logged. You almost
+/// certainly want to remove use of this class before submitting your change.
+///
+/// Usage:
+///
+/// TEST(Foo, ThingWorks) {
+///   auto logger = ScopedLogger("lldb", {"all"});
+///   ...
+///   <calls to LLDB_LOGF(...) will go to stderr>
+///   ...
+/// }
+/// TEST(Foo, OtherThingWorks) {
+///   ...
+///   <calls to LLDB_LOGF(...) will not be logged anywhere>
+///   ...
+/// }
+///
+/// This has some limitations because logging is not uniform, so some things
+/// don't work well or are hard coded in an unfortunate way.
+///   - There isn't a mapping from the channel enum to the string used to enable
+///   it. See ChannelName<T> above.
+///   - There isn't a uniform way to Register/Unregister log types, and
+///   registering it twice is fatal. We have to be careful to only register it
+///   once, and even then, make sure you aren't calling code that registers it.
+///   - This does not remember what logging might have been enabled when it
+///   started, so it completely disables logging when the scope goes out.
+class TestStderrLogger {
+  class TestStderrLogCloser {
+  public:
+    TestStderrLogCloser(llvm::StringRef channel,
+                        llvm::ArrayRef<const char *> categories,
+                        uint32_t log_options, llvm::raw_ostream &os);
+    ~TestStderrLogCloser();
+  };
+
+  static void Init();
+
+  static std::vector<const char *> CategoriesForMask(const Log::Channel &chan,
+                                                     Log::MaskType mask);
+
+public:
+  /// Example usage:
+  /// auto log = TestStderrLogger::Scoped("lldb", {"ast", "break"});
+  static TestStderrLogCloser Scoped(llvm::StringRef channel,
+                                    llvm::ArrayRef<const char *> categories,
+                                    uint32_t log_options = 0,
+                                    llvm::raw_ostream &os = llvm::errs());
+
+  /// Example usage:
+  /// auto log = TestStderrLogger::Scoped(LLDBLog::AST | LLDBLog::Breakpoints);
+  template <typename Cat>
+  static TestStderrLogCloser Scoped(Cat mask, uint32_t log_options = 0,
+                                    llvm::raw_ostream &os = llvm::errs()) {
+    static_assert(
+        std::is_same<Log::MaskType, std::underlying_type_t<Cat>>::value);
+    Init();
+    return Scoped(ChannelName<Cat>(),
+                  CategoriesForMask(LogChannelFor<Cat>(), Log::MaskType(mask)),
+                  log_options, os);
+  }
+};
+
+} // namespace lldb_private
+
+#endif
Index: lldb/unittests/TestingSupport/TestStderrLogger.cpp
===================================================================
--- /dev/null
+++ lldb/unittests/TestingSupport/TestStderrLogger.cpp
@@ -0,0 +1,61 @@
+//===-- TestStderrLogger.cpp ----------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "TestStderrLogger.h"
+
+using namespace lldb_private;
+
+class OstreamLogHandler : public LogHandler {
+public:
+  OstreamLogHandler(llvm::raw_ostream &os) : os(os) {}
+  void Emit(llvm::StringRef message) override {
+    os << std::string_view(message);
+  }
+
+private:
+  llvm::raw_ostream &os;
+};
+
+template <> llvm::StringRef lldb_private::ChannelName<LLDBLog>() {
+  return "lldb";
+}
+
+TestStderrLogger::TestStderrLogCloser::TestStderrLogCloser(
+    llvm::StringRef channel, llvm::ArrayRef<const char *> categories,
+    uint32_t log_options, llvm::raw_ostream &os) {
+  auto log_stream_sp = std::make_shared<OstreamLogHandler>(os);
+  Log::EnableLogChannel(log_stream_sp, /*log_options=*/0, channel, categories,
+                        llvm::errs());
+}
+
+TestStderrLogger::TestStderrLogCloser::~TestStderrLogCloser() {
+  Log::DisableAllLogChannels();
+}
+
+void TestStderrLogger::Init() {
+  static llvm::once_flag g_once_flag;
+  llvm::call_once(g_once_flag, []() { lldb_private::InitializeLldbChannel(); });
+}
+
+TestStderrLogger::TestStderrLogCloser
+TestStderrLogger::Scoped(llvm::StringRef channel,
+                         llvm::ArrayRef<const char *> categories,
+                         uint32_t log_options, llvm::raw_ostream &os) {
+  Init();
+  return TestStderrLogger::TestStderrLogCloser(channel, categories, log_options,
+                                               os);
+}
+std::vector<const char *>
+TestStderrLogger::CategoriesForMask(const Log::Channel &chan,
+                                    Log::MaskType mask) {
+  std::vector<const char *> categories;
+  for (const auto &c : chan.categories)
+    if (mask & c.flag)
+      categories.push_back(c.name.data());
+  return categories;
+}
Index: lldb/unittests/TestingSupport/CMakeLists.txt
===================================================================
--- lldb/unittests/TestingSupport/CMakeLists.txt
+++ lldb/unittests/TestingSupport/CMakeLists.txt
@@ -2,6 +2,7 @@
 add_lldb_library(lldbUtilityHelpers
   MockTildeExpressionResolver.cpp
   TestUtilities.cpp
+  TestStderrLogger.cpp
 
   LINK_LIBS
     lldbUtility
@@ -14,3 +15,4 @@
 
 add_subdirectory(Host)
 add_subdirectory(Symbol)
+add_subdirectory(tests)
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to