From 51420c9a3041206ea0634086854b13560696e032 Mon Sep 17 00:00:00 2001 From: Jacob Lalonde Date: Mon, 27 Jan 2025 13:41:58 -0800 Subject: [PATCH 01/15] 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 dde0774f3fba6057811748e062b3b90ed652af07 Mon Sep 17 00:00:00 2001 From: Jacob Lalonde Date: Mon, 27 Jan 2025 14:48:01 -0800 Subject: [PATCH 02/15] 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 cd19400bd6be4275d2ffd9370003260ccd29096b Mon Sep 17 00:00:00 2001 From: Jacob Lalonde Date: Thu, 30 Jan 2025 10:04:04 -0800 Subject: [PATCH 03/15] 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 b6b08661232398d7db7b737848823949406f8c17 Mon Sep 17 00:00:00 2001 From: Jacob Lalonde Date: Tue, 18 Feb 2025 11:28:58 -0800 Subject: [PATCH 04/15] 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 9ae2fbbe817ed59aabb7ef240c35fcf75347b7d1 Mon Sep 17 00:00:00 2001 From: Jacob Lalonde Date: Wed, 19 Feb 2025 12:49:20 -0800 Subject: [PATCH 05/15] Add new test cases, and refactor to support TITLE: DETAIL in all scenarios --- .../lldb-dap/progress/Progress_emitter.py | 29 +++- .../lldb-dap/progress/TestDAP_Progress.py | 127 +++++++++++++++++- 2 files changed, 152 insertions(+), 4 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 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 ddab9fb0008af38df38ca3799123deb70e085669 Mon Sep 17 00:00:00 2001 From: Jacob Lalonde Date: Thu, 20 Feb 2025 09:35:54 -0800 Subject: [PATCH 06/15] 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 5e94d105a7ec5ddd34176b02622004305834ca31 Mon Sep 17 00:00:00 2001 From: Jacob Lalonde Date: Thu, 20 Feb 2025 13:49:58 -0800 Subject: [PATCH 07/15] 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 682eb633380fa7cfeaa265a37efe2cef699047fb Mon Sep 17 00:00:00 2001 From: Jacob Lalonde Date: Thu, 20 Feb 2025 15:09:47 -0800 Subject: [PATCH 08/15] 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 bb079b28ec303a2b55dbc2d8efb04126b2677f74 Mon Sep 17 00:00:00 2001 From: Jacob Lalonde Date: Wed, 26 Feb 2025 12:00:06 -0800 Subject: [PATCH 09/15] Refactor tests to be concise and add progresse end, add additional check to sbprogress description check --- .../lldb-dap/progress/TestDAP_Progress.py | 143 +++++++----------- 1 file changed, 53 insertions(+), 90 deletions(-) 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..4e3f459c5dfc1 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,41 @@ 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 +65,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 +88,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 +109,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 +133,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, + ) From cee75403b68f532a6da7042d397d7bf63ac36af4 Mon Sep 17 00:00:00 2001 From: Jacob Lalonde Date: Mon, 3 Mar 2025 15:08:35 -0800 Subject: [PATCH 10/15] Add Finalize to the test to ensure we always get the end message, and update the doc string --- lldb/bindings/interface/SBProgressDocstrings.i | 10 +++++++++- .../API/tools/lldb-dap/progress/Progress_emitter.py | 5 ++++- .../API/tools/lldb-dap/progress/TestDAP_Progress.py | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lldb/bindings/interface/SBProgressDocstrings.i b/lldb/bindings/interface/SBProgressDocstrings.i index 5459d1af5155c..aabea45bbc40b 100644 --- a/lldb/bindings/interface/SBProgressDocstrings.i +++ b/lldb/bindings/interface/SBProgressDocstrings.i @@ -11,7 +11,15 @@ The Progress class helps make sure that progress is correctly reported and will always send an initial progress update, updates when Progress::Increment() is called, and also will make sure that a progress completed update is reported even if the user doesn't explicitly cause one -to be sent.") lldb::SBProgress; +to be sent. + +Progress can either be deterministic, incrementing up to a known total or non-deterministic +with an unbounded total. Deterministic is better if you know the items of work in advance, but non-deterministic +exposes a way to update a user during a long running process that work is taking place. + +For non-deterministic + +") lldb::SBProgress; %feature("docstring", "Finalize the SBProgress, which will cause a progress end event to be emitted. This 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 1f6810d7bd7b4..e94a09676e067 100644 --- a/lldb/test/API/tools/lldb-dap/progress/Progress_emitter.py +++ b/lldb/test/API/tools/lldb-dap/progress/Progress_emitter.py @@ -39,7 +39,7 @@ def create_options(cls): parser.add_option( "--total", dest="total", - help="Total to count up, use None to identify as indeterminate", + help="Total items in this progress object. When this option is not specified, this will be an indeterminate progress.", type="int", default=None, ) @@ -101,6 +101,9 @@ def __call__(self, debugger, command, exe_ctx, result): progress.Increment(1, f"Step {i}") time.sleep(cmd_options.seconds) + # Not required for deterministic progress, but required for indeterminate progress. + progress.Finalize() + def __lldb_init_module(debugger, dict): # Register all classes that have a register_lldb_command method 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 4e3f459c5dfc1..5529a5a8c58ba 100755 --- a/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py +++ b/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py @@ -19,7 +19,7 @@ def verify_progress_events( expected_not_in_message=None, only_verify_first_update=False, ): - self.dap_server.wait_for_event("progressEnd", 15) + self.dap_server.wait_for_event("progressEnd", 5) self.assertTrue(len(self.dap_server.progress_events) > 0) start_found = False update_found = False From 41b9d7d10509dfdbed1952bb82b6a4234e13253b Mon Sep 17 00:00:00 2001 From: Jacob Lalonde Date: Mon, 3 Mar 2025 15:09:33 -0800 Subject: [PATCH 11/15] Cleanup test of print statements, run formatter --- lldb/bindings/interface/SBProgressDocstrings.i | 7 +++++-- lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lldb/bindings/interface/SBProgressDocstrings.i b/lldb/bindings/interface/SBProgressDocstrings.i index aabea45bbc40b..b67bf5f8ce633 100644 --- a/lldb/bindings/interface/SBProgressDocstrings.i +++ b/lldb/bindings/interface/SBProgressDocstrings.i @@ -17,8 +17,11 @@ Progress can either be deterministic, incrementing up to a known total or non-de with an unbounded total. Deterministic is better if you know the items of work in advance, but non-deterministic exposes a way to update a user during a long running process that work is taking place. -For non-deterministic - +For all progresses the details provided in the constructor will be sent until an increment detail +is provided. This detail will also continue to be broadcasted on any subsequent update that doesn't +specify a new detail. Some implementations differ on throttling updates and this behavior differs primarily +if the progress is deterministic or non-deterministic. For DAP, non-deterministic update messages have a higher +throttling rate than deterministic ones. ") lldb::SBProgress; %feature("docstring", 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 5529a5a8c58ba..13c9e59e2eb77 100755 --- a/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py +++ b/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py @@ -32,7 +32,6 @@ def verify_progress_events( 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: From 458707cc12222b28e1323df243985b4ae7e5d5a4 Mon Sep 17 00:00:00 2001 From: Jacob Lalonde Date: Mon, 3 Mar 2025 15:24:05 -0800 Subject: [PATCH 12/15] Add some basic code to the summary --- lldb/bindings/interface/SBProgressDocstrings.i | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lldb/bindings/interface/SBProgressDocstrings.i b/lldb/bindings/interface/SBProgressDocstrings.i index b67bf5f8ce633..70c4d2929592c 100644 --- a/lldb/bindings/interface/SBProgressDocstrings.i +++ b/lldb/bindings/interface/SBProgressDocstrings.i @@ -22,7 +22,20 @@ is provided. This detail will also continue to be broadcasted on any subsequent specify a new detail. Some implementations differ on throttling updates and this behavior differs primarily if the progress is deterministic or non-deterministic. For DAP, non-deterministic update messages have a higher throttling rate than deterministic ones. -") lldb::SBProgress; + +Below are examples in Python for deterministic and non-deterministic progresses. + + deterministic_progress = lldb.SBProgress('Deterministic Progress', 'Detail', 3, lldb.SBDebugger) + for i in range(3): + deterministic_progress.Increment(1, f'Update {i}') + + non_deterministic_progress = lldb.SBProgress('Non deterministic progress, 'Detail', lldb.SBDebugger) + for i in range(10): + non_deterministic_progress.Increment(1) + + # Explicitly send a progressEnd from Python. + non_deterministic_progress.Finalize() +") lldb::SBProgress; %feature("docstring", "Finalize the SBProgress, which will cause a progress end event to be emitted. This From adb146fa573ad5d706c122a454449eeeba87b68b Mon Sep 17 00:00:00 2001 From: Jacob Lalonde Date: Mon, 3 Mar 2025 15:46:00 -0800 Subject: [PATCH 13/15] Fix non-deterministic title case --- lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py | 8 ++------ lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) 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 13c9e59e2eb77..55c690ef0d0f7 100755 --- a/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py +++ b/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py @@ -51,7 +51,6 @@ def test_output(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")] @@ -74,7 +73,6 @@ 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")] @@ -97,7 +95,6 @@ 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")] @@ -109,7 +106,7 @@ def test_output_indeterminate(self): self.dap_server.request_evaluate("`test-progress --seconds 1", context="repl") self.verify_progress_events( - expected_title="Progress tester", + expected_title="Progress tester: Initial Indeterminate Detail", expected_message="Step 1", only_verify_first_update=True, ) @@ -119,7 +116,6 @@ 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")] @@ -133,7 +129,7 @@ def test_output_nodetails_indeterminate(self): ) self.verify_progress_events( - expected_title="Progress tester", + expected_title="Progress tester: Initial Indeterminate Detail", expected_message="Initial Indeterminate Detail", only_verify_first_update=True, ) diff --git a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp index 9f78349379069..167ea4758992e 100644 --- a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp @@ -70,7 +70,7 @@ void ProgressEventThreadFunction(DAP &dap) { GetStringFromStructuredData(data, "details"); if (completed == 0) { - if (total == 1) { + if (total == UINT64_MAX) { // 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 From f2c98a22b71730bb7be6a364873629d5dde10d52 Mon Sep 17 00:00:00 2001 From: Jacob Lalonde Date: Mon, 3 Mar 2025 15:55:14 -0800 Subject: [PATCH 14/15] Greg comment feedback --- .../bindings/interface/SBProgressDocstrings.i | 28 +++++++++++++++---- .../lldb-dap/progress/TestDAP_Progress.py | 2 +- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/lldb/bindings/interface/SBProgressDocstrings.i b/lldb/bindings/interface/SBProgressDocstrings.i index 70c4d2929592c..03e71ba729d82 100644 --- a/lldb/bindings/interface/SBProgressDocstrings.i +++ b/lldb/bindings/interface/SBProgressDocstrings.i @@ -25,15 +25,33 @@ throttling rate than deterministic ones. Below are examples in Python for deterministic and non-deterministic progresses. - deterministic_progress = lldb.SBProgress('Deterministic Progress', 'Detail', 3, lldb.SBDebugger) - for i in range(3): - deterministic_progress.Increment(1, f'Update {i}') + deterministic_progress1 = lldb.SBProgress('Deterministic Progress', 'Detail', 3, lldb.SBDebugger) + for i in range(3): + deterministic_progress1.Increment(1, f'Update {i}') + + # The call to Finalize() is a no-op as we already incremented the right amount of + # times and caused the end event to be sent. + deterministic_progress1.Finalize() + deterministic_progress2 = lldb.SBProgress('Deterministic Progress', 'Detail', 10, lldb.SBDebugger) + for i in range(3): + deterministic_progress2.Increment(1, f'Update {i}') + + # Cause the progress end event to be sent even if we didn't increment the right + # number of times. Real world examples would be in a try-finally block to ensure + # progress clean-up. + + deterministic_progress2.Finalize() +If you don't call Finalize() when the progress is not done, the progress object will eventually get +garbage collected by the Python runtime, the end event will eventually get sent, but it is best not to +rely on the garbage collection when using lldb.SBProgress. + +Non-deterministic progresses behave the same, but omit the total in the constructor. non_deterministic_progress = lldb.SBProgress('Non deterministic progress, 'Detail', lldb.SBDebugger) for i in range(10): non_deterministic_progress.Increment(1) - - # Explicitly send a progressEnd from Python. + # Explicitly send a progressEnd, otherwise this will be sent + # when the python runtime cleans up this object. non_deterministic_progress.Finalize() ") lldb::SBProgress; 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 55c690ef0d0f7..f723a2d254825 100755 --- a/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py +++ b/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py @@ -19,7 +19,7 @@ def verify_progress_events( expected_not_in_message=None, only_verify_first_update=False, ): - self.dap_server.wait_for_event("progressEnd", 5) + self.dap_server.wait_for_event("progressEnd", 15) self.assertTrue(len(self.dap_server.progress_events) > 0) start_found = False update_found = False From c7a5f3faac11e4249cb8b9249419d5611bbcee13 Mon Sep 17 00:00:00 2001 From: Jacob Lalonde Date: Tue, 4 Mar 2025 14:28:13 -0800 Subject: [PATCH 15/15] Add and remove lines as recommended --- lldb/bindings/interface/SBProgressDocstrings.i | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lldb/bindings/interface/SBProgressDocstrings.i b/lldb/bindings/interface/SBProgressDocstrings.i index 03e71ba729d82..7b7e1dc79187c 100644 --- a/lldb/bindings/interface/SBProgressDocstrings.i +++ b/lldb/bindings/interface/SBProgressDocstrings.i @@ -28,19 +28,18 @@ Below are examples in Python for deterministic and non-deterministic progresses. deterministic_progress1 = lldb.SBProgress('Deterministic Progress', 'Detail', 3, lldb.SBDebugger) for i in range(3): deterministic_progress1.Increment(1, f'Update {i}') - # The call to Finalize() is a no-op as we already incremented the right amount of # times and caused the end event to be sent. - deterministic_progress1.Finalize() + deterministic_progress1.Finalize() + deterministic_progress2 = lldb.SBProgress('Deterministic Progress', 'Detail', 10, lldb.SBDebugger) for i in range(3): - deterministic_progress2.Increment(1, f'Update {i}') - + deterministic_progress2.Increment(1, f'Update {i}') # Cause the progress end event to be sent even if we didn't increment the right # number of times. Real world examples would be in a try-finally block to ensure # progress clean-up. - deterministic_progress2.Finalize() + If you don't call Finalize() when the progress is not done, the progress object will eventually get garbage collected by the Python runtime, the end event will eventually get sent, but it is best not to rely on the garbage collection when using lldb.SBProgress.