[Lldb-commits] [lldb] [LLDB-DAP] SBDebugger don't prefix title on progress updates (PR #124648)

Jacob Lalonde via lldb-commits lldb-commits at lists.llvm.org
Wed Feb 26 12:00:42 PST 2025


https://github.com/Jlalond updated https://github.com/llvm/llvm-project/pull/124648

>From 0587ba8239dbbd22aa40bde23d61f9504975817d Mon Sep 17 00:00:00 2001
From: Jacob Lalonde <jalalonde at fb.com>
Date: Mon, 27 Jan 2025 13:41:58 -0800
Subject: [PATCH 1/9] Only include title on the first message

---
 lldb/include/lldb/Core/DebuggerEvents.h | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/lldb/include/lldb/Core/DebuggerEvents.h b/lldb/include/lldb/Core/DebuggerEvents.h
index 49a4ecf8e537e..52e4f77d7637d 100644
--- a/lldb/include/lldb/Core/DebuggerEvents.h
+++ b/lldb/include/lldb/Core/DebuggerEvents.h
@@ -44,12 +44,15 @@ class ProgressEventData : public EventData {
   uint64_t GetCompleted() const { return m_completed; }
   uint64_t GetTotal() const { return m_total; }
   std::string GetMessage() const {
-    std::string message = m_title;
-    if (!m_details.empty()) {
-      message.append(": ");
-      message.append(m_details);
-    }
-    return message;
+    if (m_completed == 0) {
+      std::string message = m_title;
+      if (!m_details.empty()) {
+        message.append(": ");
+        message.append(m_details);
+      }
+      return message;
+    } else
+      return !m_details.empty() ? m_details : std::string();
   }
   const std::string &GetTitle() const { return m_title; }
   const std::string &GetDetails() const { return m_details; }

>From b64fe53741486ea9b6cde2e658b766aa68bf9389 Mon Sep 17 00:00:00 2001
From: Jacob Lalonde <jalalonde at fb.com>
Date: Mon, 27 Jan 2025 14:48:01 -0800
Subject: [PATCH 2/9] Add comment explaining if and add a test

---
 lldb/include/lldb/Core/DebuggerEvents.h                   | 1 +
 lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py | 5 +++++
 2 files changed, 6 insertions(+)

diff --git a/lldb/include/lldb/Core/DebuggerEvents.h b/lldb/include/lldb/Core/DebuggerEvents.h
index 52e4f77d7637d..ca06ee835fde3 100644
--- a/lldb/include/lldb/Core/DebuggerEvents.h
+++ b/lldb/include/lldb/Core/DebuggerEvents.h
@@ -44,6 +44,7 @@ class ProgressEventData : public EventData {
   uint64_t GetCompleted() const { return m_completed; }
   uint64_t GetTotal() const { return m_total; }
   std::string GetMessage() const {
+    // Only put the title in the message of the progress create event.
     if (m_completed == 0) {
       std::string message = m_title;
       if (!m_details.empty()) {
diff --git a/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py b/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py
index 36c0cef9c4714..945c3f7633364 100755
--- a/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py
+++ b/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py
@@ -41,8 +41,13 @@ def test_output(self):
         for event in self.dap_server.progress_events:
             event_type = event["event"]
             if "progressStart" in event_type:
+                title = event["body"]["title"]
+                self.assertIn("Progress tester", title)
                 start_found = True
             if "progressUpdate" in event_type:
+                message = event["body"]["message"]
+                print(f"Progress update: {message}")
+                self.assertNotIn("Progres tester", message)
                 update_found = True
 
         self.assertTrue(start_found)

>From 96f0ebb43f83cce80c76915f5412b9bb31dd3f12 Mon Sep 17 00:00:00 2001
From: Jacob Lalonde <jalalonde at fb.com>
Date: Thu, 30 Jan 2025 10:04:04 -0800
Subject: [PATCH 3/9] Migrate to structured data

Summary:

Test Plan:

Reviewers:

Subscribers:

Tasks:

Tags:

Differential Revision: https://phabricator.intern.facebook.com/D68927453
---
 lldb/include/lldb/Core/DebuggerEvents.h       | 16 ++--
 .../Handler/InitializeRequestHandler.cpp      | 77 ++++++++++++++++---
 2 files changed, 73 insertions(+), 20 deletions(-)

diff --git a/lldb/include/lldb/Core/DebuggerEvents.h b/lldb/include/lldb/Core/DebuggerEvents.h
index ca06ee835fde3..49a4ecf8e537e 100644
--- a/lldb/include/lldb/Core/DebuggerEvents.h
+++ b/lldb/include/lldb/Core/DebuggerEvents.h
@@ -44,16 +44,12 @@ class ProgressEventData : public EventData {
   uint64_t GetCompleted() const { return m_completed; }
   uint64_t GetTotal() const { return m_total; }
   std::string GetMessage() const {
-    // Only put the title in the message of the progress create event.
-    if (m_completed == 0) {
-      std::string message = m_title;
-      if (!m_details.empty()) {
-        message.append(": ");
-        message.append(m_details);
-      }
-      return message;
-    } else
-      return !m_details.empty() ? m_details : std::string();
+    std::string message = m_title;
+    if (!m_details.empty()) {
+      message.append(": ");
+      message.append(m_details);
+    }
+    return message;
   }
   const std::string &GetTitle() const { return m_title; }
   const std::string &GetDetails() const { return m_details; }
diff --git a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
index e9041f3985523..9f78349379069 100644
--- a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
@@ -18,8 +18,32 @@ using namespace lldb;
 
 namespace lldb_dap {
 
-static void ProgressEventThreadFunction(DAP &dap) {
-  llvm::set_thread_name(dap.name + ".progress_handler");
+static std::string GetStringFromStructuredData(lldb::SBStructuredData &data,
+                                               const char *key) {
+  lldb::SBStructuredData keyValue = data.GetValueForKey(key);
+  if (!keyValue)
+    return std::string();
+
+  const size_t length = keyValue.GetStringValue(nullptr, 0);
+
+  if (length == 0)
+    return std::string();
+
+  std::string str(length + 1, 0);
+  keyValue.GetStringValue(&str[0], length + 1);
+  return str;
+}
+
+static uint64_t GetUintFromStructuredData(lldb::SBStructuredData &data,
+                                          const char *key) {
+  lldb::SBStructuredData keyValue = data.GetValueForKey(key);
+
+  if (!keyValue.IsValid())
+    return 0;
+  return keyValue.GetUnsignedIntegerValue();
+}
+
+void ProgressEventThreadFunction(DAP &dap) {
   lldb::SBListener listener("lldb-dap.progress.listener");
   dap.debugger.GetBroadcaster().AddListener(
       listener, lldb::SBDebugger::eBroadcastBitProgress |
@@ -35,14 +59,47 @@ static void ProgressEventThreadFunction(DAP &dap) {
           done = true;
         }
       } else {
-        uint64_t progress_id = 0;
-        uint64_t completed = 0;
-        uint64_t total = 0;
-        bool is_debugger_specific = false;
-        const char *message = lldb::SBDebugger::GetProgressFromEvent(
-            event, progress_id, completed, total, is_debugger_specific);
-        if (message)
-          dap.SendProgressEvent(progress_id, message, completed, total);
+        lldb::SBStructuredData data =
+            lldb::SBDebugger::GetProgressDataFromEvent(event);
+
+        const uint64_t progress_id =
+            GetUintFromStructuredData(data, "progress_id");
+        const uint64_t completed = GetUintFromStructuredData(data, "completed");
+        const uint64_t total = GetUintFromStructuredData(data, "total");
+        const std::string details =
+            GetStringFromStructuredData(data, "details");
+
+        if (completed == 0) {
+          if (total == 1) {
+            // This progress is non deterministic and won't get updated until it
+            // is completed. Send the "message" which will be the combined title
+            // and detail. The only other progress event for thus
+            // non-deterministic progress will be the completed event So there
+            // will be no need to update the detail.
+            const std::string message =
+                GetStringFromStructuredData(data, "message");
+            dap.SendProgressEvent(progress_id, message.c_str(), completed,
+                                  total);
+          } else {
+            // This progress is deterministic and will receive updates,
+            // on the progress creation event VSCode will save the message in
+            // the create packet and use that as the title, so we send just the
+            // title in the progressCreate packet followed immediately by a
+            // detail packet, if there is any detail.
+            const std::string title =
+                GetStringFromStructuredData(data, "title");
+            dap.SendProgressEvent(progress_id, title.c_str(), completed, total);
+            if (!details.empty())
+              dap.SendProgressEvent(progress_id, details.c_str(), completed,
+                                    total);
+          }
+        } else {
+          // This progress event is either the end of the progress dialog, or an
+          // update with possible detail. The "detail" string we send to VS Code
+          // will be appended to the progress dialog's initial text from when it
+          // was created.
+          dap.SendProgressEvent(progress_id, details.c_str(), completed, total);
+        }
       }
     }
   }

>From 07cbc9d0bd54eaaf5f3a03595e67f2b9829d8e72 Mon Sep 17 00:00:00 2001
From: Jacob Lalonde <jalalonde at fb.com>
Date: Tue, 18 Feb 2025 11:28:58 -0800
Subject: [PATCH 4/9] Feedback from Jonas, and cleanup dev logging

---
 .../Handler/InitializeRequestHandler.cpp      | 47 +++++--------------
 1 file changed, 12 insertions(+), 35 deletions(-)

diff --git a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
index 9f78349379069..c3cea94bd3bf3 100644
--- a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
@@ -25,12 +25,8 @@ static std::string GetStringFromStructuredData(lldb::SBStructuredData &data,
     return std::string();
 
   const size_t length = keyValue.GetStringValue(nullptr, 0);
-
-  if (length == 0)
-    return std::string();
-
   std::string str(length + 1, 0);
-  keyValue.GetStringValue(&str[0], length + 1);
+  keyValue.GetStringValue(&str[0], length);
   return str;
 }
 
@@ -68,38 +64,19 @@ void ProgressEventThreadFunction(DAP &dap) {
         const uint64_t total = GetUintFromStructuredData(data, "total");
         const std::string details =
             GetStringFromStructuredData(data, "details");
-
+        std::string message;
+        // Include the title on the first event.
         if (completed == 0) {
-          if (total == 1) {
-            // This progress is non deterministic and won't get updated until it
-            // is completed. Send the "message" which will be the combined title
-            // and detail. The only other progress event for thus
-            // non-deterministic progress will be the completed event So there
-            // will be no need to update the detail.
-            const std::string message =
-                GetStringFromStructuredData(data, "message");
-            dap.SendProgressEvent(progress_id, message.c_str(), completed,
-                                  total);
-          } else {
-            // This progress is deterministic and will receive updates,
-            // on the progress creation event VSCode will save the message in
-            // the create packet and use that as the title, so we send just the
-            // title in the progressCreate packet followed immediately by a
-            // detail packet, if there is any detail.
-            const std::string title =
-                GetStringFromStructuredData(data, "title");
-            dap.SendProgressEvent(progress_id, title.c_str(), completed, total);
-            if (!details.empty())
-              dap.SendProgressEvent(progress_id, details.c_str(), completed,
-                                    total);
-          }
-        } else {
-          // This progress event is either the end of the progress dialog, or an
-          // update with possible detail. The "detail" string we send to VS Code
-          // will be appended to the progress dialog's initial text from when it
-          // was created.
-          dap.SendProgressEvent(progress_id, details.c_str(), completed, total);
+          const std::string title = GetStringFromStructuredData(data, "title");
+          message += title;
+          message += ": ";
         }
+
+        message += details;
+        // Verbose check, but we get -1 for the uint64 on failure to read
+        // so we check everything before broadcasting an event.
+        if (message.length() > 0)
+          dap.SendProgressEvent(progress_id, message.c_str(), completed, total);
       }
     }
   }

>From 4268576b2c1c08f4f6f1febd693699766d76bdfe Mon Sep 17 00:00:00 2001
From: Jacob Lalonde <jalalonde at fb.com>
Date: Wed, 19 Feb 2025 12:49:20 -0800
Subject: [PATCH 5/9] Fix bug in SBProgress, add new test cases, and refactor
 to support TITLE: DETAIL in all scenarios

---
 lldb/source/API/SBProgress.cpp                |   5 +-
 .../lldb-dap/progress/Progress_emitter.py     |  29 +++-
 .../lldb-dap/progress/TestDAP_Progress.py     | 127 +++++++++++++++++-
 3 files changed, 156 insertions(+), 5 deletions(-)

diff --git a/lldb/source/API/SBProgress.cpp b/lldb/source/API/SBProgress.cpp
index e67e289a60eff..403900aed4a8c 100644
--- a/lldb/source/API/SBProgress.cpp
+++ b/lldb/source/API/SBProgress.cpp
@@ -40,7 +40,10 @@ SBProgress::~SBProgress() = default;
 void SBProgress::Increment(uint64_t amount, const char *description) {
   LLDB_INSTRUMENT_VA(amount, description);
 
-  m_opaque_up->Increment(amount, description);
+  std::optional<std::string> description_opt;
+  if (description)
+    description_opt = description;
+  m_opaque_up->Increment(amount, description_opt);
 }
 
 lldb_private::Progress &SBProgress::ref() const { return *m_opaque_up; }
diff --git a/lldb/test/API/tools/lldb-dap/progress/Progress_emitter.py b/lldb/test/API/tools/lldb-dap/progress/Progress_emitter.py
index 7f4055cab9ddd..e962b1a10d402 100644
--- a/lldb/test/API/tools/lldb-dap/progress/Progress_emitter.py
+++ b/lldb/test/API/tools/lldb-dap/progress/Progress_emitter.py
@@ -37,7 +37,10 @@ def create_options(cls):
         )
 
         parser.add_option(
-            "--total", dest="total", help="Total to count up.", type="int"
+            "--total",
+            dest="total",
+            help="Total to count up, use -1 to identify as indeterminate",
+            type="int",
         )
 
         parser.add_option(
@@ -47,6 +50,13 @@ def create_options(cls):
             type="int",
         )
 
+        parser.add_option(
+            "--no-details",
+            dest="no_details",
+            help="Do not display details",
+            action="store_true",
+        )
+
         return parser
 
     def get_short_help(self):
@@ -68,10 +78,23 @@ def __call__(self, debugger, command, exe_ctx, result):
             return
 
         total = cmd_options.total
-        progress = lldb.SBProgress("Progress tester", "Detail", total, debugger)
+        if total == -1:
+            progress = lldb.SBProgress(
+                "Progress tester", "Indeterminate Detail", debugger
+            )
+        else:
+            progress = lldb.SBProgress("Progress tester", "Detail", total, debugger)
+
+        # Check to see if total is set to -1 to indicate an indeterminate progress
+        # then default to 10 steps.
+        if total == -1:
+            total = 10
 
         for i in range(1, total):
-            progress.Increment(1, f"Step {i}")
+            if cmd_options.no_details:
+                progress.Increment(1)
+            else:
+                progress.Increment(1, f"Step {i}")
             time.sleep(cmd_options.seconds)
 
 
diff --git a/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py b/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py
index 945c3f7633364..2611df042f677 100755
--- a/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py
+++ b/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py
@@ -4,6 +4,7 @@
 
 from lldbsuite.test.decorators import *
 from lldbsuite.test.lldbtest import *
+import json
 import os
 import time
 
@@ -18,7 +19,6 @@ def test_output(self):
         progress_emitter = os.path.join(os.getcwd(), "Progress_emitter.py")
         print(f"Progress emitter path: {progress_emitter}")
         source = "main.cpp"
-        # Set breakpoint in the thread function so we can step the threads
         breakpoint_ids = self.set_source_breakpoints(
             source, [line_number(source, "// break here")]
         )
@@ -52,3 +52,128 @@ def test_output(self):
 
         self.assertTrue(start_found)
         self.assertTrue(update_found)
+
+    @skipIfWindows
+    def test_output_nodetails(self):
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program)
+        progress_emitter = os.path.join(os.getcwd(), "Progress_emitter.py")
+        print(f"Progress emitter path: {progress_emitter}")
+        source = "main.cpp"
+        breakpoint_ids = self.set_source_breakpoints(
+            source, [line_number(source, "// break here")]
+        )
+        self.continue_to_breakpoints(breakpoint_ids)
+        self.dap_server.request_evaluate(
+            f"`command script import {progress_emitter}", context="repl"
+        )
+        self.dap_server.request_evaluate(
+            "`test-progress --total 3 --seconds 1 --no-details", context="repl"
+        )
+
+        self.dap_server.wait_for_event("progressEnd", 15)
+        # Expect at least a start, an update, and end event
+        # However because the underlying Progress instance is an RAII object and we can't guaruntee
+        # it's deterministic destruction in the python API, we verify just start and update
+        # otherwise this test could be flakey.
+        self.assertTrue(len(self.dap_server.progress_events) > 0)
+        start_found = False
+        update_found = False
+        for event in self.dap_server.progress_events:
+            event_type = event["event"]
+            if "progressStart" in event_type:
+                title = event["body"]["title"]
+                self.assertIn("Progress tester", title)
+                start_found = True
+            if "progressUpdate" in event_type:
+                message = event["body"]["message"]
+                # We send an update on first message to set the details, so ignore the first update
+                if not update_found:
+                    self.assertTrue(message == "", message)
+                update_found = True
+
+        self.assertTrue(start_found)
+        self.assertTrue(update_found)
+
+    @skipIfWindows
+    def test_output_indeterminate(self):
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program)
+        progress_emitter = os.path.join(os.getcwd(), "Progress_emitter.py")
+        print(f"Progress emitter path: {progress_emitter}")
+        source = "main.cpp"
+        breakpoint_ids = self.set_source_breakpoints(
+            source, [line_number(source, "// break here")]
+        )
+        self.continue_to_breakpoints(breakpoint_ids)
+        self.dap_server.request_evaluate(
+            f"`command script import {progress_emitter}", context="repl"
+        )
+        self.dap_server.request_evaluate(
+            "`test-progress --total -1 --seconds 1", context="repl"
+        )
+
+        self.dap_server.wait_for_event("progressEnd", 15)
+        # Expect at least a start, an update, and end event
+        # However because the underlying Progress instance is an RAII object and we can't guaruntee
+        # it's deterministic destruction in the python API, we verify just start and update
+        # otherwise this test could be flakey.
+        self.assertTrue(len(self.dap_server.progress_events) > 0)
+        start_found = False
+        update_found = False
+        for event in self.dap_server.progress_events:
+            event_type = event["event"]
+            if "progressStart" in event_type:
+                title = event["body"]["title"]
+                self.assertIn("Progress tester", title)
+                start_found = True
+            if "progressUpdate" in event_type:
+                message = event["body"]["message"]
+                print(f"Progress update: {message}")
+                self.assertNotIn("Progres tester", message)
+                update_found = True
+
+        self.assertTrue(start_found)
+        self.assertTrue(update_found)
+
+    @skipIfWindows
+    def test_output_nodetails_indeterminate(self):
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program)
+        progress_emitter = os.path.join(os.getcwd(), "Progress_emitter.py")
+        print(f"Progress emitter path: {progress_emitter}")
+        source = "main.cpp"
+        breakpoint_ids = self.set_source_breakpoints(
+            source, [line_number(source, "// break here")]
+        )
+        self.dap_server.request_evaluate(
+            f"`command script import {progress_emitter}", context="repl"
+        )
+
+        self.dap_server.request_evaluate(
+            "`test-progress --total -1 --seconds 1 --no-details", context="repl"
+        )
+
+        self.dap_server.wait_for_event("progressEnd", 15)
+        # Expect at least a start, an update, and end event
+        # However because the underlying Progress instance is an RAII object and we can't guaruntee
+        # it's deterministic destruction in the python API, we verify just start and update
+        # otherwise this test could be flakey.
+        self.assertTrue(len(self.dap_server.progress_events) > 0)
+        start_found = False
+        update_found = False
+        for event in self.dap_server.progress_events:
+            event_type = event["event"]
+            if "progressStart" in event_type:
+                title = event["body"]["title"]
+                self.assertIn("Progress tester", title)
+                start_found = True
+            if "progressUpdate" in event_type:
+                message = event["body"]["message"]
+                # We send an update on first message to set the details, so ignore the first update
+                if not update_found:
+                    self.assertTrue(message == "", message)
+                update_found = True
+
+        self.assertTrue(start_found)
+        self.assertTrue(update_found)

>From 10a4e37bdebabd7e1bdf4d118d860fd6c658d8f6 Mon Sep 17 00:00:00 2001
From: Jacob Lalonde <jalalonde at fb.com>
Date: Thu, 20 Feb 2025 09:35:54 -0800
Subject: [PATCH 6/9] Test refactor and comment improvements based on Greg's
 feedback

---
 .../lldb-dap/progress/Progress_emitter.py     |  9 ++--
 .../lldb-dap/progress/TestDAP_Progress.py     |  6 +--
 .../Handler/InitializeRequestHandler.cpp      | 47 ++++++++++++++-----
 3 files changed, 42 insertions(+), 20 deletions(-)

diff --git a/lldb/test/API/tools/lldb-dap/progress/Progress_emitter.py b/lldb/test/API/tools/lldb-dap/progress/Progress_emitter.py
index e962b1a10d402..9851600519e97 100644
--- a/lldb/test/API/tools/lldb-dap/progress/Progress_emitter.py
+++ b/lldb/test/API/tools/lldb-dap/progress/Progress_emitter.py
@@ -39,8 +39,9 @@ def create_options(cls):
         parser.add_option(
             "--total",
             dest="total",
-            help="Total to count up, use -1 to identify as indeterminate",
+            help="Total to count up, use None to identify as indeterminate",
             type="int",
+            default=None,
         )
 
         parser.add_option(
@@ -78,16 +79,16 @@ def __call__(self, debugger, command, exe_ctx, result):
             return
 
         total = cmd_options.total
-        if total == -1:
+        if total is None:
             progress = lldb.SBProgress(
                 "Progress tester", "Indeterminate Detail", debugger
             )
         else:
             progress = lldb.SBProgress("Progress tester", "Detail", total, debugger)
 
-        # Check to see if total is set to -1 to indicate an indeterminate progress
+        # Check to see if total is set to None to indicate an indeterminate progress
         # then default to 10 steps.
-        if total == -1:
+        if total is None:
             total = 10
 
         for i in range(1, total):
diff --git a/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py b/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py
index 2611df042f677..c7579e8074c1d 100755
--- a/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py
+++ b/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py
@@ -109,9 +109,7 @@ def test_output_indeterminate(self):
         self.dap_server.request_evaluate(
             f"`command script import {progress_emitter}", context="repl"
         )
-        self.dap_server.request_evaluate(
-            "`test-progress --total -1 --seconds 1", context="repl"
-        )
+        self.dap_server.request_evaluate("`test-progress --seconds 1", context="repl")
 
         self.dap_server.wait_for_event("progressEnd", 15)
         # Expect at least a start, an update, and end event
@@ -151,7 +149,7 @@ def test_output_nodetails_indeterminate(self):
         )
 
         self.dap_server.request_evaluate(
-            "`test-progress --total -1 --seconds 1 --no-details", context="repl"
+            "`test-progress --seconds 1 --no-details", context="repl"
         )
 
         self.dap_server.wait_for_event("progressEnd", 15)
diff --git a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
index c3cea94bd3bf3..9f78349379069 100644
--- a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
@@ -25,8 +25,12 @@ static std::string GetStringFromStructuredData(lldb::SBStructuredData &data,
     return std::string();
 
   const size_t length = keyValue.GetStringValue(nullptr, 0);
+
+  if (length == 0)
+    return std::string();
+
   std::string str(length + 1, 0);
-  keyValue.GetStringValue(&str[0], length);
+  keyValue.GetStringValue(&str[0], length + 1);
   return str;
 }
 
@@ -64,19 +68,38 @@ void ProgressEventThreadFunction(DAP &dap) {
         const uint64_t total = GetUintFromStructuredData(data, "total");
         const std::string details =
             GetStringFromStructuredData(data, "details");
-        std::string message;
-        // Include the title on the first event.
+
         if (completed == 0) {
-          const std::string title = GetStringFromStructuredData(data, "title");
-          message += title;
-          message += ": ";
+          if (total == 1) {
+            // This progress is non deterministic and won't get updated until it
+            // is completed. Send the "message" which will be the combined title
+            // and detail. The only other progress event for thus
+            // non-deterministic progress will be the completed event So there
+            // will be no need to update the detail.
+            const std::string message =
+                GetStringFromStructuredData(data, "message");
+            dap.SendProgressEvent(progress_id, message.c_str(), completed,
+                                  total);
+          } else {
+            // This progress is deterministic and will receive updates,
+            // on the progress creation event VSCode will save the message in
+            // the create packet and use that as the title, so we send just the
+            // title in the progressCreate packet followed immediately by a
+            // detail packet, if there is any detail.
+            const std::string title =
+                GetStringFromStructuredData(data, "title");
+            dap.SendProgressEvent(progress_id, title.c_str(), completed, total);
+            if (!details.empty())
+              dap.SendProgressEvent(progress_id, details.c_str(), completed,
+                                    total);
+          }
+        } else {
+          // This progress event is either the end of the progress dialog, or an
+          // update with possible detail. The "detail" string we send to VS Code
+          // will be appended to the progress dialog's initial text from when it
+          // was created.
+          dap.SendProgressEvent(progress_id, details.c_str(), completed, total);
         }
-
-        message += details;
-        // Verbose check, but we get -1 for the uint64 on failure to read
-        // so we check everything before broadcasting an event.
-        if (message.length() > 0)
-          dap.SendProgressEvent(progress_id, message.c_str(), completed, total);
       }
     }
   }

>From 318f29325d0c3fde58800d4a8f863f750f6d2704 Mon Sep 17 00:00:00 2001
From: Jacob Lalonde <jalalonde at fb.com>
Date: Thu, 20 Feb 2025 13:49:58 -0800
Subject: [PATCH 7/9] Fix tests, correct for new behavior

---
 .../API/tools/lldb-dap/progress/Progress_emitter.py  |  5 +++--
 .../API/tools/lldb-dap/progress/TestDAP_Progress.py  | 12 ++++++------
 2 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/lldb/test/API/tools/lldb-dap/progress/Progress_emitter.py b/lldb/test/API/tools/lldb-dap/progress/Progress_emitter.py
index 9851600519e97..08f2d86e78d67 100644
--- a/lldb/test/API/tools/lldb-dap/progress/Progress_emitter.py
+++ b/lldb/test/API/tools/lldb-dap/progress/Progress_emitter.py
@@ -56,6 +56,7 @@ def create_options(cls):
             dest="no_details",
             help="Do not display details",
             action="store_true",
+            default=False,
         )
 
         return parser
@@ -81,10 +82,10 @@ def __call__(self, debugger, command, exe_ctx, result):
         total = cmd_options.total
         if total is None:
             progress = lldb.SBProgress(
-                "Progress tester", "Indeterminate Detail", debugger
+                "Progress tester", "Initial Indeterminate Detail", debugger
             )
         else:
-            progress = lldb.SBProgress("Progress tester", "Detail", total, debugger)
+            progress = lldb.SBProgress("Progress tester", "Initial Detail", total, debugger)
 
         # Check to see if total is set to None to indicate an indeterminate progress
         # then default to 10 steps.
diff --git a/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py b/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py
index c7579e8074c1d..d4f4f42330546 100755
--- a/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py
+++ b/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py
@@ -87,9 +87,7 @@ def test_output_nodetails(self):
                 start_found = True
             if "progressUpdate" in event_type:
                 message = event["body"]["message"]
-                # We send an update on first message to set the details, so ignore the first update
-                if not update_found:
-                    self.assertTrue(message == "", message)
+                self.assertEqual("Initial Detail", message)
                 update_found = True
 
         self.assertTrue(start_found)
@@ -128,7 +126,9 @@ def test_output_indeterminate(self):
             if "progressUpdate" in event_type:
                 message = event["body"]["message"]
                 print(f"Progress update: {message}")
-                self.assertNotIn("Progres tester", message)
+                # Check on the first update we set the initial detail.
+                if not update_found:
+                    self.assertEqual("Step 1", message)
                 update_found = True
 
         self.assertTrue(start_found)
@@ -168,9 +168,9 @@ def test_output_nodetails_indeterminate(self):
                 start_found = True
             if "progressUpdate" in event_type:
                 message = event["body"]["message"]
-                # We send an update on first message to set the details, so ignore the first update
+                # Check on the first update we set the initial detail.
                 if not update_found:
-                    self.assertTrue(message == "", message)
+                    self.assertEqual("Initial Indeterminate Detail", message)
                 update_found = True
 
         self.assertTrue(start_found)

>From 79394c3887bf2a3b745a1784a104b835c1e2cde8 Mon Sep 17 00:00:00 2001
From: Jacob Lalonde <jalalonde at fb.com>
Date: Thu, 20 Feb 2025 15:09:47 -0800
Subject: [PATCH 8/9] Python formatting

---
 lldb/test/API/tools/lldb-dap/progress/Progress_emitter.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/lldb/test/API/tools/lldb-dap/progress/Progress_emitter.py b/lldb/test/API/tools/lldb-dap/progress/Progress_emitter.py
index 08f2d86e78d67..1f6810d7bd7b4 100644
--- a/lldb/test/API/tools/lldb-dap/progress/Progress_emitter.py
+++ b/lldb/test/API/tools/lldb-dap/progress/Progress_emitter.py
@@ -85,7 +85,9 @@ def __call__(self, debugger, command, exe_ctx, result):
                 "Progress tester", "Initial Indeterminate Detail", debugger
             )
         else:
-            progress = lldb.SBProgress("Progress tester", "Initial Detail", total, debugger)
+            progress = lldb.SBProgress(
+                "Progress tester", "Initial Detail", total, debugger
+            )
 
         # Check to see if total is set to None to indicate an indeterminate progress
         # then default to 10 steps.

>From 681352cc4f771bc1dec873f5105d6515fefc1997 Mon Sep 17 00:00:00 2001
From: Jacob Lalonde <jalalonde at fb.com>
Date: Wed, 26 Feb 2025 12:00:06 -0800
Subject: [PATCH 9/9] Refactor tests to be concise and add progresse end, add
 additional check to sbprogress description check

---
 lldb/source/API/SBProgress.cpp                |   2 +-
 .../lldb-dap/progress/TestDAP_Progress.py     | 144 +++++++-----------
 2 files changed, 55 insertions(+), 91 deletions(-)

diff --git a/lldb/source/API/SBProgress.cpp b/lldb/source/API/SBProgress.cpp
index 403900aed4a8c..d40e11da973d4 100644
--- a/lldb/source/API/SBProgress.cpp
+++ b/lldb/source/API/SBProgress.cpp
@@ -41,7 +41,7 @@ void SBProgress::Increment(uint64_t amount, const char *description) {
   LLDB_INSTRUMENT_VA(amount, description);
 
   std::optional<std::string> description_opt;
-  if (description)
+  if (description && description[0])
     description_opt = description;
   m_opaque_up->Increment(amount, description_opt);
 }
diff --git a/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py b/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py
index d4f4f42330546..968351c8179f1 100755
--- a/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py
+++ b/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py
@@ -12,6 +12,42 @@
 
 
 class TestDAP_progress(lldbdap_testcase.DAPTestCaseBase):
+
+    def verify_progress_events(
+        self,
+        expected_title,
+        expected_message=None,
+        expected_not_in_message=None,
+        only_verify_first_update=False,
+    ):
+        self.dap_server.wait_for_event("progressEnd", 15)
+        self.assertTrue(len(self.dap_server.progress_events) > 0)
+        start_found = False
+        update_found = False
+        end_found = False
+        for event in self.dap_server.progress_events:
+            event_type = event["event"]
+            if "progressStart" in event_type:
+                title = event["body"]["title"]
+                self.assertIn(expected_title, title)
+                start_found = True
+            if "progressUpdate" in event_type:
+                message = event["body"]["message"]
+                print(f"Progress update: {message}")
+                if only_verify_first_update and update_found:
+                    continue
+                if expected_message is not None:
+                    self.assertIn(expected_message, message)
+                if expected_not_in_message is not None:
+                    self.assertNotIn(expected_not_in_message, message)
+                update_found = True
+            if "progressEnd" in event_type:
+                end_found = True
+
+        self.assertTrue(start_found)
+        self.assertTrue(update_found)
+        self.assertTrue(end_found)
+
     @skipIfWindows
     def test_output(self):
         program = self.getBuildArtifact("a.out")
@@ -30,28 +66,10 @@ def test_output(self):
             "`test-progress --total 3 --seconds 1", context="repl"
         )
 
-        self.dap_server.wait_for_event("progressEnd", 15)
-        # Expect at least a start, an update, and end event
-        # However because the underlying Progress instance is an RAII object and we can't guaruntee
-        # it's deterministic destruction in the python API, we verify just start and update
-        # otherwise this test could be flakey.
-        self.assertTrue(len(self.dap_server.progress_events) > 0)
-        start_found = False
-        update_found = False
-        for event in self.dap_server.progress_events:
-            event_type = event["event"]
-            if "progressStart" in event_type:
-                title = event["body"]["title"]
-                self.assertIn("Progress tester", title)
-                start_found = True
-            if "progressUpdate" in event_type:
-                message = event["body"]["message"]
-                print(f"Progress update: {message}")
-                self.assertNotIn("Progres tester", message)
-                update_found = True
-
-        self.assertTrue(start_found)
-        self.assertTrue(update_found)
+        self.verify_progress_events(
+            expected_title="Progress tester",
+            expected_not_in_message="Progress tester",
+        )
 
     @skipIfWindows
     def test_output_nodetails(self):
@@ -71,27 +89,10 @@ def test_output_nodetails(self):
             "`test-progress --total 3 --seconds 1 --no-details", context="repl"
         )
 
-        self.dap_server.wait_for_event("progressEnd", 15)
-        # Expect at least a start, an update, and end event
-        # However because the underlying Progress instance is an RAII object and we can't guaruntee
-        # it's deterministic destruction in the python API, we verify just start and update
-        # otherwise this test could be flakey.
-        self.assertTrue(len(self.dap_server.progress_events) > 0)
-        start_found = False
-        update_found = False
-        for event in self.dap_server.progress_events:
-            event_type = event["event"]
-            if "progressStart" in event_type:
-                title = event["body"]["title"]
-                self.assertIn("Progress tester", title)
-                start_found = True
-            if "progressUpdate" in event_type:
-                message = event["body"]["message"]
-                self.assertEqual("Initial Detail", message)
-                update_found = True
-
-        self.assertTrue(start_found)
-        self.assertTrue(update_found)
+        self.verify_progress_events(
+            expected_title="Progress tester",
+            expected_message="Initial Detail",
+        )
 
     @skipIfWindows
     def test_output_indeterminate(self):
@@ -109,30 +110,11 @@ def test_output_indeterminate(self):
         )
         self.dap_server.request_evaluate("`test-progress --seconds 1", context="repl")
 
-        self.dap_server.wait_for_event("progressEnd", 15)
-        # Expect at least a start, an update, and end event
-        # However because the underlying Progress instance is an RAII object and we can't guaruntee
-        # it's deterministic destruction in the python API, we verify just start and update
-        # otherwise this test could be flakey.
-        self.assertTrue(len(self.dap_server.progress_events) > 0)
-        start_found = False
-        update_found = False
-        for event in self.dap_server.progress_events:
-            event_type = event["event"]
-            if "progressStart" in event_type:
-                title = event["body"]["title"]
-                self.assertIn("Progress tester", title)
-                start_found = True
-            if "progressUpdate" in event_type:
-                message = event["body"]["message"]
-                print(f"Progress update: {message}")
-                # Check on the first update we set the initial detail.
-                if not update_found:
-                    self.assertEqual("Step 1", message)
-                update_found = True
-
-        self.assertTrue(start_found)
-        self.assertTrue(update_found)
+        self.verify_progress_events(
+            expected_title="Progress tester",
+            expected_message="Step 1",
+            only_verify_first_update=True,
+        )
 
     @skipIfWindows
     def test_output_nodetails_indeterminate(self):
@@ -152,26 +134,8 @@ def test_output_nodetails_indeterminate(self):
             "`test-progress --seconds 1 --no-details", context="repl"
         )
 
-        self.dap_server.wait_for_event("progressEnd", 15)
-        # Expect at least a start, an update, and end event
-        # However because the underlying Progress instance is an RAII object and we can't guaruntee
-        # it's deterministic destruction in the python API, we verify just start and update
-        # otherwise this test could be flakey.
-        self.assertTrue(len(self.dap_server.progress_events) > 0)
-        start_found = False
-        update_found = False
-        for event in self.dap_server.progress_events:
-            event_type = event["event"]
-            if "progressStart" in event_type:
-                title = event["body"]["title"]
-                self.assertIn("Progress tester", title)
-                start_found = True
-            if "progressUpdate" in event_type:
-                message = event["body"]["message"]
-                # Check on the first update we set the initial detail.
-                if not update_found:
-                    self.assertEqual("Initial Indeterminate Detail", message)
-                update_found = True
-
-        self.assertTrue(start_found)
-        self.assertTrue(update_found)
+        self.verify_progress_events(
+            expected_title="Progress tester",
+            expected_message="Initial Indeterminate Detail",
+            only_verify_first_update=True,
+        )



More information about the lldb-commits mailing list