xref: /llvm-project/lldb/test/API/python_api/global_module_cache/TestGlobalModuleCache.py (revision 01c4ecb7ae21a61312ff0c0176c0ab9f8656c159)
1"""
2Test the use of the global module cache in lldb
3"""
4
5import lldb
6
7from lldbsuite.test.decorators import *
8from lldbsuite.test.lldbtest import *
9from lldbsuite.test import lldbutil
10import os
11import shutil
12from pathlib import Path
13import time
14
15class GlobalModuleCacheTestCase(TestBase):
16    # NO_DEBUG_INFO_TESTCASE = True
17
18    def check_counter_var(self, thread, value):
19        frame = thread.frames[0]
20        var = frame.FindVariable("counter")
21        self.assertTrue(var.GetError().Success(), "Got counter variable")
22        self.assertEqual(var.GetValueAsUnsigned(), value, "This was one-print")
23
24    def copy_to_main(self, src, dst):
25        # We are relying on the source file being newer than the .o file from
26        # a previous build, so sleep a bit here to ensure that the touch is later.
27        time.sleep(2)
28        try:
29            shutil.copy(src, dst)
30        except:
31            self.fail(f"Could not copy {src} to {dst}")
32        Path(dst).touch()
33
34    # The rerun tests indicate rerunning on Windows doesn't really work, so
35    # this one won't either.
36    @skipIfWindows
37    def test_OneTargetOneDebugger(self):
38        self.do_test(True, True)
39
40    # This behaves as implemented but that behavior is not desirable.
41    # This test tests for the desired behavior as an expected fail.
42    @skipIfWindows
43    @expectedFailureAll
44    def test_TwoTargetsOneDebugger(self):
45        self.do_test(False, True)
46
47    @skipIfWindows
48    @expectedFailureAll
49    def test_OneTargetTwoDebuggers(self):
50        self.do_test(True, False)
51
52    def do_test(self, one_target, one_debugger):
53        # Here to debug flakiness on Arm, remove later!
54        log_cmd_result = lldb.SBCommandReturnObject()
55        interp = self.dbg.GetCommandInterpreter()
56        interp.HandleCommand("log enable lldb step", log_cmd_result)
57
58        # Make sure that if we have one target, and we run, then
59        # change the binary and rerun, the binary (and any .o files
60        # if using dwarf in .o file debugging) get removed from the
61        # shared module cache.  They are no longer reachable.
62        debug_style = self.getDebugInfo()
63
64        # Before we do anything, clear the global module cache so we don't
65        # see objects from other runs:
66        lldb.SBDebugger.MemoryPressureDetected()
67
68        # Set up the paths for our two versions of main.c:
69        main_c_path = os.path.join(self.getBuildDir(), "main.c")
70        one_print_path = os.path.join(self.getSourceDir(), "one-print.c")
71        two_print_path = os.path.join(self.getSourceDir(), "two-print.c")
72        main_filespec = lldb.SBFileSpec(main_c_path)
73
74        # First copy the one-print.c to main.c in the build folder and
75        # build our a.out from there:
76        self.copy_to_main(one_print_path, main_c_path)
77        self.build(dictionary={"C_SOURCES": main_c_path, "EXE": "a.out"})
78
79        (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
80            self, "return counter;", main_filespec
81        )
82
83        # Make sure we ran the version we intended here:
84        self.check_counter_var(thread, 1)
85        process.Kill()
86
87        # Now copy two-print.c over main.c, rebuild, and rerun:
88        # os.unlink(target.GetExecutable().fullpath)
89        self.copy_to_main(two_print_path, main_c_path)
90
91        self.build(dictionary={"C_SOURCES": main_c_path, "EXE": "a.out"})
92        error = lldb.SBError()
93        if one_debugger:
94            if one_target:
95                (_, process, thread, _) = lldbutil.run_to_breakpoint_do_run(
96                    self, target, bkpt
97                )
98            else:
99                (target2, process2, thread, bkpt) = lldbutil.run_to_source_breakpoint(
100                    self, "return counter;", main_filespec
101                )
102        else:
103            if one_target:
104                new_debugger = lldb.SBDebugger().Create()
105                self.old_debugger = self.dbg
106                self.dbg = new_debugger
107                def cleanupDebugger(self):
108                    lldb.SBDebugger.Destroy(self.dbg)
109                    self.dbg = self.old_debugger
110                    self.old_debugger = None
111
112                self.addTearDownHook(cleanupDebugger)
113                (target2, process2, thread, bkpt) = lldbutil.run_to_source_breakpoint(
114                    self, "return counter;", main_filespec
115                )
116
117        # In two-print.c counter will be 2:
118        self.check_counter_var(thread, 2)
119
120        # If we made two targets, destroy the first one, that should free up the
121        # unreachable Modules:
122        if not one_target:
123            target.Clear()
124
125        num_a_dot_out_entries = 1
126        # For dSYM's there will be two lines of output, one for the a.out and one
127        # for the dSYM.
128        if debug_style == "dsym":
129            num_a_dot_out_entries += 1
130
131        error = self.check_image_list_result(num_a_dot_out_entries, 1)
132        # Even if this fails, MemoryPressureDetected should fix this.
133        lldb.SBDebugger.MemoryPressureDetected()
134        error_after_mpd = self.check_image_list_result(num_a_dot_out_entries, 1)
135        fail_msg = ""
136        if error != "":
137            fail_msg = "Error before MPD: " + error
138
139        if error_after_mpd != "":
140            fail_msg = fail_msg + "\nError after MPD: " + error_after_mpd
141        if fail_msg != "":
142            self.fail(fail_msg)
143
144    def check_image_list_result(self, num_a_dot_out, num_main_dot_o):
145        # Check the global module list, there should only be one a.out, and if we are
146        # doing dwarf in .o file, there should only be one .o file.  This returns
147        # an error string on error - rather than asserting, so you can stage this
148        # failing.
149        image_cmd_result = lldb.SBCommandReturnObject()
150        interp = self.dbg.GetCommandInterpreter()
151        interp.HandleCommand("image list -g", image_cmd_result)
152        if self.TraceOn():
153            print(f"Expected: a.out: {num_a_dot_out} main.o: {num_main_dot_o}")
154            print(image_cmd_result)
155
156        image_list_str = image_cmd_result.GetOutput()
157        image_list = image_list_str.splitlines()
158        found_a_dot_out = 0
159        found_main_dot_o = 0
160
161        for line in image_list:
162            # FIXME: force this to be at the end of the string:
163            if "a.out" in line:
164                found_a_dot_out += 1
165            if "main.o" in line:
166                found_main_dot_o += 1
167
168        if num_a_dot_out != found_a_dot_out:
169            return f"Got {found_a_dot_out} number of a.out's, expected {num_a_dot_out}"
170
171        if found_main_dot_o > 0 and num_main_dot_o != found_main_dot_o:
172            return f"Got {found_main_dot_o} number of main.o's, expected {num_main_dot_o}"
173
174        return ""
175