Skip to content

[lldb-dap] In DAP unit tests, add helpers for loading a CoreFile. #140738

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 22, 2025

Conversation

ashgti
Copy link
Contributor

@ashgti ashgti commented May 20, 2025

This allows us to have a SBTarget and SBProcess for creating unit tests.

This allows us to have a SBTarget and SBProcess for creating unit tests.
@llvmbot
Copy link
Member

llvmbot commented May 20, 2025

@llvm/pr-subscribers-lldb

Author: John Harrison (ashgti)

Changes

This allows us to have a SBTarget and SBProcess for creating unit tests.


Full diff: https://github.com/llvm/llvm-project/pull/140738.diff

7 Files Affected:

  • (modified) lldb/tools/lldb-dap/Protocol/ProtocolBase.h (+5)
  • (modified) lldb/unittests/DAP/CMakeLists.txt (+8)
  • (modified) lldb/unittests/DAP/Handler/DisconnectTest.cpp (+30-2)
  • (added) lldb/unittests/DAP/Inputs/linux-x86_64.core ()
  • (added) lldb/unittests/DAP/Inputs/linux-x86_64.out ()
  • (modified) lldb/unittests/DAP/TestBase.cpp (+44)
  • (modified) lldb/unittests/DAP/TestBase.h (+19)
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolBase.h b/lldb/tools/lldb-dap/Protocol/ProtocolBase.h
index 1cb9cb13dd0da..724da59b50cd2 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolBase.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolBase.h
@@ -141,6 +141,11 @@ using Message = std::variant<Request, Response, Event>;
 bool fromJSON(const llvm::json::Value &, Message &, llvm::json::Path);
 llvm::json::Value toJSON(const Message &);
 
+inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Message &V) {
+  OS << toJSON(V);
+  return OS;
+}
+
 /// On error (whenever `success` is false), the body can provide more details.
 struct ErrorResponseBody {
   /// A structured error message.
diff --git a/lldb/unittests/DAP/CMakeLists.txt b/lldb/unittests/DAP/CMakeLists.txt
index cd421401f167b..16a3c2b736ee6 100644
--- a/lldb/unittests/DAP/CMakeLists.txt
+++ b/lldb/unittests/DAP/CMakeLists.txt
@@ -10,8 +10,16 @@ add_lldb_unittest(DAPTests
   VariablesTest.cpp
 
   LINK_LIBS
+    liblldb
     lldbDAP
+    lldbUtilityHelpers
     LLVMTestingSupport
   LINK_COMPONENTS
     Support
   )
+
+set(test_inputs
+  linux-x86_64.out
+  linux-x86_64.core
+  )
+add_unittest_inputs(DAPTests "${test_inputs}")
diff --git a/lldb/unittests/DAP/Handler/DisconnectTest.cpp b/lldb/unittests/DAP/Handler/DisconnectTest.cpp
index 6f3470239e974..73e0f84f93af3 100644
--- a/lldb/unittests/DAP/Handler/DisconnectTest.cpp
+++ b/lldb/unittests/DAP/Handler/DisconnectTest.cpp
@@ -10,7 +10,10 @@
 #include "Handler/RequestHandler.h"
 #include "Protocol/ProtocolBase.h"
 #include "TestBase.h"
+#include "lldb/API/SBDefines.h"
+#include "lldb/lldb-enumerations.h"
 #include "llvm/Testing/Support/Error.h"
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include <memory>
 #include <optional>
@@ -23,7 +26,7 @@ using namespace lldb_dap::protocol;
 
 class DisconnectRequestHandlerTest : public DAPTestBase {};
 
-TEST_F(DisconnectRequestHandlerTest, DisconnectingTriggersTerminated) {
+TEST_F(DisconnectRequestHandlerTest, DisconnectTriggersTerminated) {
   DisconnectRequestHandler handler(*dap);
   EXPECT_FALSE(dap->disconnecting);
   ASSERT_THAT_ERROR(handler.Run(std::nullopt), Succeeded());
@@ -31,5 +34,30 @@ TEST_F(DisconnectRequestHandlerTest, DisconnectingTriggersTerminated) {
   std::vector<Message> messages = DrainOutput();
   EXPECT_THAT(messages,
               testing::Contains(testing::VariantWith<Event>(testing::FieldsAre(
-                  /*event=*/"terminated", /*body=*/std::nullopt))));
+                  /*event=*/"terminated", /*body=*/testing::_))));
+}
+
+TEST_F(DisconnectRequestHandlerTest, DisconnectTriggersTerminateCommands) {
+  CreateDebugger();
+
+  if (!GetDebuggerSupportsTarget("X86"))
+    GTEST_SKIP() << "Unsupported platform";
+
+  LoadCore(DAPTestBase::k_linux_binary, DAPTestBase::k_linux_core);
+
+  DisconnectRequestHandler handler(*dap);
+
+  EXPECT_FALSE(dap->disconnecting);
+  dap->configuration.terminateCommands = {"?script print(1)",
+                                          "script print(2)"};
+  EXPECT_EQ(dap->target.GetProcess().GetState(), lldb::eStateStopped);
+  ASSERT_THAT_ERROR(handler.Run(std::nullopt), Succeeded());
+  EXPECT_TRUE(dap->disconnecting);
+  std::vector<Message> messages = DrainOutput();
+  EXPECT_THAT(messages, testing::ElementsAre(
+                            OutputMatcher("Running terminateCommands:\n"),
+                            OutputMatcher("(lldb) script print(2)\n"),
+                            OutputMatcher("2\n"),
+                            testing::VariantWith<Event>(testing::FieldsAre(
+                                /*event=*/"terminated", /*body=*/testing::_))));
 }
diff --git a/lldb/unittests/DAP/Inputs/linux-x86_64.core b/lldb/unittests/DAP/Inputs/linux-x86_64.core
new file mode 100644
index 0000000000000..2675eadd6a7f6
Binary files /dev/null and b/lldb/unittests/DAP/Inputs/linux-x86_64.core differ
diff --git a/lldb/unittests/DAP/Inputs/linux-x86_64.out b/lldb/unittests/DAP/Inputs/linux-x86_64.out
new file mode 100755
index 0000000000000..842402fd519d2
Binary files /dev/null and b/lldb/unittests/DAP/Inputs/linux-x86_64.out differ
diff --git a/lldb/unittests/DAP/TestBase.cpp b/lldb/unittests/DAP/TestBase.cpp
index eb146cb2fa9f4..1c58c351776ec 100644
--- a/lldb/unittests/DAP/TestBase.cpp
+++ b/lldb/unittests/DAP/TestBase.cpp
@@ -8,9 +8,16 @@
 
 #include "TestBase.h"
 #include "Protocol/ProtocolBase.h"
+#include "TestingSupport/TestUtilities.h"
+#include "lldb/API/SBDefines.h"
+#include "lldb/API/SBStructuredData.h"
 #include "lldb/Host/File.h"
 #include "lldb/Host/Pipe.h"
+#include "lldb/lldb-forward.h"
+#include "llvm/ADT/StringRef.h"
 #include "llvm/Testing/Support/Error.h"
+#include "gtest/gtest.h"
+#include <memory>
 
 using namespace llvm;
 using namespace lldb;
@@ -55,6 +62,43 @@ void DAPTestBase::SetUp() {
       /*transport=*/*to_dap);
 }
 
+void DAPTestBase::SetUpTestSuite() {
+  lldb::SBError error = SBDebugger::InitializeWithErrorHandling();
+  EXPECT_TRUE(error.Success());
+}
+void DAPTestBase::TeatUpTestSuite() { SBDebugger::Terminate(); }
+
+bool DAPTestBase::GetDebuggerSupportsTarget(llvm::StringRef platform) {
+  EXPECT_TRUE(dap->debugger);
+
+  lldb::SBStructuredData data = dap->debugger.GetBuildConfiguration()
+                                    .GetValueForKey("targets")
+                                    .GetValueForKey("value");
+  for (size_t i = 0; i < data.GetSize(); i++) {
+    char buf[100] = {0};
+    size_t size = data.GetItemAtIndex(i).GetStringValue(buf, sizeof(buf));
+    if (llvm::StringRef(buf, size) == platform)
+      return true;
+  }
+
+  return false;
+}
+
+void DAPTestBase::CreateDebugger() {
+  dap->debugger = lldb::SBDebugger::Create();
+  ASSERT_TRUE(dap->debugger);
+}
+
+void DAPTestBase::LoadCore(llvm::StringRef binary, llvm::StringRef core) {
+  ASSERT_TRUE(dap->debugger);
+  dap->target =
+      dap->debugger.CreateTarget(lldb_private::GetInputFilePath(binary).data());
+  ASSERT_TRUE(dap->target);
+  SBProcess process =
+      dap->target.LoadCore(lldb_private::GetInputFilePath(core).data());
+  ASSERT_TRUE(process);
+}
+
 std::vector<Message> DAPTestBase::DrainOutput() {
   std::vector<Message> msgs;
   output.CloseWriteFileDescriptor();
diff --git a/lldb/unittests/DAP/TestBase.h b/lldb/unittests/DAP/TestBase.h
index c789adf53c225..c6c261fa2b64c 100644
--- a/lldb/unittests/DAP/TestBase.h
+++ b/lldb/unittests/DAP/TestBase.h
@@ -10,6 +10,8 @@
 #include "Protocol/ProtocolBase.h"
 #include "Transport.h"
 #include "lldb/Host/Pipe.h"
+#include "llvm/ADT/StringRef.h"
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
 namespace lldb_dap_tests {
@@ -33,13 +35,30 @@ class TransportBase : public PipeBase {
   void SetUp() override;
 };
 
+/// Matches an "output" event.
+inline auto OutputMatcher(const llvm::StringRef output,
+                          const llvm::StringRef category = "console") {
+  return testing::VariantWith<lldb_dap::protocol::Event>(testing::FieldsAre(
+      /*event=*/"output", /*body=*/testing::Optional<llvm::json::Value>(
+          llvm::json::Object{{"category", category}, {"output", output}})));
+}
+
 /// A base class for tests that interact with a `lldb_dap::DAP` instance.
 class DAPTestBase : public TransportBase {
 protected:
   std::unique_ptr<lldb_dap::DAP> dap;
 
+  static constexpr llvm::StringLiteral k_linux_binary = "linux-x86_64.out";
+  static constexpr llvm::StringLiteral k_linux_core = "linux-x86_64.core";
+
+  static void SetUpTestSuite();
+  static void TeatUpTestSuite();
   void SetUp() override;
 
+  bool GetDebuggerSupportsTarget(llvm::StringRef platform);
+  void CreateDebugger();
+  void LoadCore(llvm::StringRef binary, llvm::StringRef core);
+
   /// Closes the DAP output pipe and returns the remaining protocol messages in
   /// the buffer.
   std::vector<lldb_dap::protocol::Message> DrainOutput();

@ashgti
Copy link
Contributor Author

ashgti commented May 20, 2025

I copied the TestDAP_coreFile.py's linux-x86_64.core and linux-x86_64.out for the inputs.

Comment on lines 22 to 23
linux-x86_64.out
linux-x86_64.core
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we obj2yaml these and programmatically yaml2obj them again?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yaml2obj on core files doesn't really work. It should be possible to do that with minidump files though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created yaml dumps of both the core and out files.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume Pavel's talking about the memory just being bytes. I'd say the YAML file is marginally better than the binaries. A minidump would be even better but I don't feel like we should block this PR on that.

Copy link
Contributor Author

@ashgti ashgti May 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used minidump to make the linux-x86_64.core.yaml file (it starts with --- !minidump). I just needed to load the existing target + core and use (lldb) process save-core --plugin-name=minidump <path> to get the file.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume Pavel's talking about the memory just being bytes.

No, that's actually the best you can get (without inventing something completely new), even with minidumps. With core files you just get nothing because they don't fit yaml2obj's model of elf files. elf2yaml's main use case is (unlinked) object files produced by the compiler, which have only section headers (no program headers). OTOH, core files consist of only program headers, and the tool just doesn't handle that. These days I think it's possible to generate a somewhat realistic core file with yaml2obj, but you have to write the yaml by hand. obj2yaml won't generate it for you.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL, thanks for the explanation.

Copy link

github-actions bot commented May 21, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@ashgti ashgti merged commit 8416bac into llvm:main May 22, 2025
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants