Skip to content

Commit 4ec4c9b

Browse files
committed
Explain: Initial support for navigating Google Test programs.
1 parent 7f6364f commit 4ec4c9b

File tree

1 file changed

+79
-0
lines changed

1 file changed

+79
-0
lines changed

explain/explain.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,36 @@ def cpp_exception_state_present() -> bool:
250250
return True
251251

252252

253+
def gtest_libraries_present() -> bool:
254+
"""
255+
Is this program linked against gtest libraries?
256+
"""
257+
obj_paths = [Path(o.filename) for o in gdb.objfiles() if o.filename is not None]
258+
return any(o.name.startswith("libgtest") for o in obj_paths)
259+
260+
261+
class GTestNotAvailable(Exception):
262+
"""
263+
Raised by GTest-specific tools if the Google Test libraries are not present.
264+
"""
265+
266+
def __init__(self):
267+
super().__init__("Tool unavailable: This program was not run with gtest.")
268+
269+
270+
class GTestAnnotationsNotAvailable(Exception):
271+
"""
272+
Raised by GTest-specific tools if our Google Test annotations are not found.
273+
"""
274+
275+
def __init__(self):
276+
super().__init__(
277+
"Tool unavailable: Did not find gtest annotations, which are required to navigate "
278+
"Google Test recordings. Maybe the program was built without the "
279+
"Undo's `undo_gtest_annotation.h` addon?"
280+
)
281+
282+
253283
class UdbMcpGateway:
254284
"""
255285
Plumbing class to expose selected UDB functionality as a set of tools to an MCP server.
@@ -646,6 +676,55 @@ def tool_ugo_bookmark(self, name) -> None:
646676
"""
647677
self.udb.bookmarks.goto(name)
648678

679+
@report
680+
@chain_of_thought
681+
def tool_gtest_get_tests(self) -> list[tuple[str, str]]:
682+
"""
683+
Retrieve a list of GTest tests captured in this recording, along with their results.
684+
685+
Full test names are returned in the form:
686+
687+
<test suite name>.<test name>/run<run number>
688+
689+
Each instance of a test is assigned a unique run number. Run numbers don't have meaning
690+
beyond being a unique suffix.
691+
692+
Returns a list of (<full test name>, <result>) tuples.
693+
"""
694+
if not gtest_libraries_present():
695+
raise GTestNotAvailable()
696+
697+
results = list(self.udb.annotations.get("", "u-test-result"))
698+
if not results:
699+
raise GTestAnnotationsNotAvailable()
700+
701+
return [(r.name, r.get_content_as_printable_text()) for r in results]
702+
703+
@report
704+
@source_context
705+
@chain_of_thought
706+
def tool_gtest_goto_test(self, name) -> str:
707+
"""
708+
Move to the end of the specified Gtest test case.
709+
"""
710+
if not gtest_libraries_present():
711+
raise GTestNotAvailable()
712+
713+
if not list(self.udb.annotations.get("", "u-test-result")):
714+
raise GTestAnnotationsNotAvailable()
715+
716+
results = list(self.udb.annotations.get(name, "u-test-result"))
717+
if len(results) != 1:
718+
raise Exception("Must specify a unique, existing test identifier.")
719+
720+
annotation = results[0]
721+
self.udb.time.goto(annotation.bbcount)
722+
723+
test_name, _ = annotation.name.split("/run")
724+
target_fn = test_name.replace(".", "_") + "_Test::TestBody"
725+
726+
return self._reverse_into_target_function(target_fn)
727+
649728

650729
command.register_prefix(
651730
"uexperimental mcp",

0 commit comments

Comments
 (0)