diff --git a/lldb/unittests/TestingSupport/CMakeLists.txt b/lldb/unittests/TestingSupport/CMakeLists.txt --- a/lldb/unittests/TestingSupport/CMakeLists.txt +++ b/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) diff --git a/lldb/unittests/TestingSupport/TestStderrLogger.h b/lldb/unittests/TestingSupport/TestStderrLogger.h new file mode 100644 --- /dev/null +++ b/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 llvm::StringRef ChannelName() = delete; +template <> llvm::StringRef ChannelName(); + +/// 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"}); +/// ... +/// +/// ... +/// } +/// TEST(Foo, OtherThingWorks) { +/// ... +/// +/// ... +/// } +/// +/// 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 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 categories, + uint32_t log_options, llvm::raw_ostream &os); + ~TestStderrLogCloser(); + }; + + static void Init(); + + static std::vector 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 categories, + uint32_t log_options = 0, + llvm::raw_ostream &os = llvm::errs()); + + /// Example usage: + /// auto log = TestStderrLogger::Scoped(LLDBLog::AST | LLDBLog::Breakpoints); + template + static TestStderrLogCloser Scoped(Cat mask, uint32_t log_options = 0, + llvm::raw_ostream &os = llvm::errs()) { + static_assert( + std::is_same>::value); + Init(); + return Scoped(ChannelName(), + CategoriesForMask(LogChannelFor(), Log::MaskType(mask)), + log_options, os); + } +}; + +} // namespace lldb_private + +#endif diff --git a/lldb/unittests/TestingSupport/TestStderrLogger.cpp b/lldb/unittests/TestingSupport/TestStderrLogger.cpp new file mode 100644 --- /dev/null +++ b/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() { + return "lldb"; +} + +TestStderrLogger::TestStderrLogCloser::TestStderrLogCloser( + llvm::StringRef channel, llvm::ArrayRef categories, + uint32_t log_options, llvm::raw_ostream &os) { + auto log_stream_sp = std::make_shared(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 categories, + uint32_t log_options, llvm::raw_ostream &os) { + Init(); + return TestStderrLogger::TestStderrLogCloser(channel, categories, log_options, + os); +} +std::vector +TestStderrLogger::CategoriesForMask(const Log::Channel &chan, + Log::MaskType mask) { + std::vector categories; + for (const auto &c : chan.categories) + if (mask & c.flag) + categories.push_back(c.name.data()); + return categories; +} diff --git a/lldb/unittests/TestingSupport/tests/CMakeLists.txt b/lldb/unittests/TestingSupport/tests/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/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 diff --git a/lldb/unittests/TestingSupport/tests/TestStderrLoggerTest.cpp b/lldb/unittests/TestingSupport/tests/TestStderrLoggerTest.cpp new file mode 100644 --- /dev/null +++ b/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"); +}