Skip to content

Conversation

@mark-undoio
Copy link
Contributor

Early support for debugging Google Test binaries.

This makes use of the gtest_annotations addon, which uses the undoex library to create annotations within a recording. This allows Explain to list test results, jump to specific test failures and then resume debugging the case at hand.

If Google Test support is not present the relevant tools will return an error (ideally we'd only expose them when Google Test support is present but Claude doesn't handle tool changes currently).

@mark-undoio
Copy link
Contributor Author

The program that I tested against was:

#include <gtest/gtest.h>
#include <undo_gtest_annotation.h>
#include <algorithm>
#include <numeric>
#include <vector>
#include <stdexcept>

TEST(StandardAlgorithms, SortVector) {
    std::vector<int> numbers = {5, 2, 8, 1, 9};
    std::sort(numbers.begin(), numbers.end());
    EXPECT_EQ(numbers[0], 1);
    EXPECT_EQ(numbers[4], 9);
}

TEST(StandardAlgorithms, AccumulateSum) {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    int sum = std::accumulate(numbers.begin(), numbers.end(), 0);
    EXPECT_EQ(sum, 15);
}

TEST(StandardAlgorithms, FindElement) {
    std::vector<int> numbers = {10, 20, 30, 40, 50};
    auto it = std::find(numbers.begin(), numbers.end(), 30);
    EXPECT_NE(it, numbers.end());
    EXPECT_EQ(*it, 30);
}

TEST(StandardAlgorithms, CountElements) {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    int count = std::count_if(numbers.begin(), numbers.end(),
                               [](int n) { return n > 0; });
    if (count > 0) {
        throw std::runtime_error("Unexpected value encountered");
    }
    EXPECT_EQ(count, 5);  // This line won't be reached
}

TEST(StandardAlgorithms, TransformVector) {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    std::vector<int> doubled(numbers.size());
    std::transform(numbers.begin(), numbers.end(), doubled.begin(),
                   [](int n) { return n * 2; });
    EXPECT_EQ(doubled[0], 2);
    EXPECT_EQ(doubled[4], 10);
}

TEST(StandardAlgorithms, CopyVector) {
    std::vector<int> source = {7, 8, 9};
    std::vector<int> dest(source.size());
    std::copy(source.begin(), source.end(), dest.begin());
    EXPECT_EQ(dest, source);
}

GTEST_API_ int
main(int argc, char **argv)
{
    testing::InitGoogleTest(&argc, argv);

    testing::UnitTest::GetInstance()->listeners().Append(new undo_annotation::UndoAnnotationListener);
    return RUN_ALL_TESTS();
}

This was generated by Claude Code and provides a single failing test to step back into. With the exception handling + this additional Gtests support it's able to navigate straight to the test failure of interest and analyse within it.

In principle we could also set time limits to the boundaries of that test case - I've not experimented with that. In the general case we might also want to be able to identify stale state from previous tests in the recording so it's not clear to me that doing so would always be a good thing.

@barisione barisione self-assigned this Oct 31, 2025
The symbol for this counter appears to have a couple of spellings that come up
in practice in different programs.

The field is still in the same place in the structure returned by
`__cxa_get_globals()` so in future we might want to just look at it by offset,
rather than name.  For now this will do.
This was the majority of the implementation for the
`reverse_step_into_current_line` tool but it will also be useful for other cases
where we need to wind back into to a specific function whilst capturing return
values or exceptions.
This identifies tests more specifically and also makes it possible to derive the
specific test class name from the annotation, which is useful for setting
breakpoints.
We can retrieve this using the layout of the structure as it's part of the ABI.
That means we can be independent of debug information for the C++ runtime.
@mark-undoio mark-undoio force-pushed the explain-gtest-experimental branch from 4ec4c9b to 3794d21 Compare November 6, 2025 19:14
"""
Is this program linked against gtest libraries?
"""
obj_paths = [Path(o.filename) for o in gdb.objfiles() if o.filename is not None]
Copy link
Collaborator

Choose a reason for hiding this comment

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

This could be a generator expression to avoid going through all the object files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants