brintos

brintos / llvm-project-archived public Read only

0
0
Text · 58.4 KiB · c8527ab Raw
1458 lines · python
1import json2import os3import re4import shutil5 6import lldb7from lldbsuite.test.decorators import *8from lldbsuite.test.lldbtest import *9from lldbsuite.test import lldbutil10 11 12class TestCase(TestBase):13    NO_DEBUG_INFO_TESTCASE = True14 15    def test_enable_disable(self):16        """17        Test "statistics disable" and "statistics enable". These don't do18        anything anymore for cheap to gather statistics. In the future if19        statistics are expensive to gather, we can enable the feature inside20        of LLDB and test that enabling and disabling stops expesive information21        from being gathered.22        """23        self.build()24        target = self.createTestTarget()25 26        self.expect(27            "statistics disable",28            substrs=["need to enable statistics before disabling"],29            error=True,30        )31        self.expect("statistics enable")32        self.expect("statistics enable", substrs=["already enabled"], error=True)33        self.expect("statistics disable")34        self.expect(35            "statistics disable",36            substrs=["need to enable statistics before disabling"],37            error=True,38        )39 40    def verify_key_in_dict(self, key, d, description):41        self.assertIn(42            key, d, 'make sure key "%s" is in dictionary %s' % (key, description)43        )44 45    def verify_key_not_in_dict(self, key, d, description):46        self.assertNotIn(47            key, d, 'make sure key "%s" is in dictionary %s' % (key, description)48        )49 50    def verify_keys(self, dict, description, keys_exist, keys_missing=None):51        """52        Verify that all keys in "keys_exist" list are top level items in53        "dict", and that all keys in "keys_missing" do not exist as top54        level items in "dict".55        """56        if keys_exist:57            for key in keys_exist:58                self.verify_key_in_dict(key, dict, description)59        if keys_missing:60            for key in keys_missing:61                self.verify_key_not_in_dict(key, dict, description)62 63    def verify_success_fail_count(self, stats, key, num_successes, num_fails):64        self.verify_key_in_dict(key, stats, 'stats["%s"]' % (key))65        success_fail_dict = stats[key]66        self.assertEqual(67            success_fail_dict["successes"], num_successes, "make sure success count"68        )69        self.assertEqual(70            success_fail_dict["failures"], num_fails, "make sure success count"71        )72 73    def get_target_stats(self, debug_stats):74        if "targets" in debug_stats:75            return debug_stats["targets"][0]76        return None77 78    def get_command_stats(self, debug_stats):79        if "commands" in debug_stats:80            return debug_stats["commands"]81        return None82 83    def test_expressions_frame_var_counts(self):84        self.build()85        lldbutil.run_to_source_breakpoint(86            self, "// break here", lldb.SBFileSpec("main.cpp")87        )88 89        self.expect("expr patatino", substrs=["27"])90        stats = self.get_target_stats(self.get_stats())91        self.verify_success_fail_count(stats, "expressionEvaluation", 1, 0)92        self.expect(93            "expr doesnt_exist",94            error=True,95            substrs=["undeclared identifier 'doesnt_exist'"],96        )97        # Doesn't successfully execute.98        self.expect("expr int *i = nullptr; *i", error=True)99        # Interpret an integer as an array with 3 elements is a failure for100        # the "expr" command, but the expression evaluation will succeed and101        # be counted as a success even though the "expr" options will for the102        # command to fail. It is more important to track expression evaluation103        # from all sources instead of just through the command, so this was104        # changed. If we want to track command success and fails, we can do105        # so using another metric.106        self.expect(107            "expr -Z 3 -- 1",108            error=True,109            substrs=["expression cannot be used with --element-count"],110        )111        # We should have gotten 3 new failures and the previous success.112        stats = self.get_target_stats(self.get_stats())113        self.verify_success_fail_count(stats, "expressionEvaluation", 2, 2)114 115        self.expect("statistics enable")116        # 'frame var' with enabled statistics will change stats.117        self.expect("frame var", substrs=["27"])118        stats = self.get_target_stats(self.get_stats())119        self.verify_success_fail_count(stats, "frameVariable", 1, 0)120 121        # Test that "stopCount" is available when the process has run122        self.assertIn("stopCount", stats, 'ensure "stopCount" is in target JSON')123        self.assertGreater(124            stats["stopCount"], 0, 'make sure "stopCount" is greater than zero'125        )126 127    def test_default_no_run(self):128        """Test "statistics dump" without running the target.129 130        When we don't run the target, we expect to not see any 'firstStopTime'131        or 'launchOrAttachTime' top level keys that measure the launch or132        attach of the target.133 134        Output expected to be something like:135 136        (lldb) statistics dump137        {138          "memory" : {...},139          "modules" : [...],140          "targets" : [141            {142                "targetCreateTime": 0.26566899599999999,143                "expressionEvaluation": {144                    "failures": 0,145                    "successes": 0146                },147                "frameVariable": {148                    "failures": 0,149                    "successes": 0150                },151                "moduleIdentifiers": [...],152            }153          ],154          "totalDebugInfoByteSize": 182522234,155          "totalDebugInfoIndexTime": 2.33343,156          "totalDebugInfoParseTime": 8.2121400240000071,157          "totalSymbolTableParseTime": 0.123,158          "totalSymbolTableIndexTime": 0.234,159        }160        """161        self.build()162        target = self.createTestTarget()163 164        # Verify top-level keys.165        debug_stats = self.get_stats()166        debug_stat_keys = [167            "memory",168            "modules",169            "targets",170            "totalSymbolTableParseTime",171            "totalSymbolTableIndexTime",172            "totalSymbolTablesLoadedFromCache",173            "totalSymbolTablesSavedToCache",174            "totalSymbolTableSymbolCount",175            "totalSymbolTablesLoaded",176            "totalDebugInfoByteSize",177            "totalDebugInfoIndexTime",178            "totalDebugInfoIndexLoadedFromCache",179            "totalDebugInfoIndexSavedToCache",180            "totalDebugInfoParseTime",181            "totalDwoFileCount",182            "totalLoadedDwoFileCount",183            "totalDwoErrorCount",184        ]185        self.verify_keys(debug_stats, '"debug_stats"', debug_stat_keys, None)186        if self.getPlatform() != "windows":187            self.assertGreater(debug_stats["totalSymbolTableSymbolCount"], 0)188            self.assertGreater(debug_stats["totalSymbolTablesLoaded"], 0)189 190        # Verify target stats keys.191        target_stats = debug_stats["targets"][0]192        target_stat_keys_exist = [193            "expressionEvaluation",194            "frameVariable",195            "moduleIdentifiers",196            "targetCreateTime",197        ]198        target_stat_keys_missing = ["firstStopTime", "launchOrAttachTime"]199        self.verify_keys(200            target_stats,201            '"target_stats"',202            target_stat_keys_exist,203            target_stat_keys_missing,204        )205        self.assertGreater(target_stats["targetCreateTime"], 0.0)206 207        # Verify module stats keys.208        for module_stats in debug_stats["modules"]:209            module_stat_keys_exist = [210                "symbolTableSymbolCount",211            ]212            self.verify_keys(213                module_stats, '"module_stats"', module_stat_keys_exist, None214            )215            if self.getPlatform() != "windows":216                self.assertGreater(module_stats["symbolTableSymbolCount"], 0)217 218    def test_default_no_run_no_preload_symbols(self):219        """Test "statistics dump" without running the target and without220        preloading symbols.221 222        Checks that symbol count are zero.223        """224        # Make sure symbols will not be preloaded.225        self.runCmd("settings set target.preload-symbols false")226 227        # Build and load the target228        self.build()229        self.createTestTarget()230 231        # Get statistics232        debug_stats = self.get_stats()233 234        # No symbols should be loaded in the main module.235        main_module_stats = debug_stats["modules"][0]236        if self.getPlatform() != "windows":237            self.assertEqual(main_module_stats["symbolTableSymbolCount"], 0)238 239    def test_default_with_run(self):240        """Test "statistics dump" when running the target to a breakpoint.241 242        When we run the target, we expect to see 'launchOrAttachTime' and243        'firstStopTime' top level keys.244 245        Output expected to be something like:246 247        (lldb) statistics dump248        {249          "memory" : {...},250          "modules" : [...],251          "targets" : [252                {253                    "firstStopTime": 0.34164492800000001,254                    "launchOrAttachTime": 0.31969605400000001,255                    "moduleIdentifiers": [...],256                    "targetCreateTime": 0.0040863039999999998257                    "expressionEvaluation": {258                        "failures": 0,259                        "successes": 0260                    },261                    "frameVariable": {262                        "failures": 0,263                        "successes": 0264                    },265                }266            ],267            "totalDebugInfoByteSize": 182522234,268            "totalDebugInfoIndexTime": 2.33343,269            "totalDebugInfoParseTime": 8.2121400240000071,270            "totalSymbolTableParseTime": 0.123,271            "totalSymbolTableIndexTime": 0.234,272        }273 274        """275        self.build()276        target = self.createTestTarget()277        lldbutil.run_to_source_breakpoint(278            self, "// break here", lldb.SBFileSpec("main.cpp")279        )280        debug_stats = self.get_stats()281        debug_stat_keys = [282            "memory",283            "modules",284            "targets",285            "totalSymbolTableParseTime",286            "totalSymbolTableIndexTime",287            "totalSymbolTablesLoadedFromCache",288            "totalSymbolTablesSavedToCache",289            "totalDebugInfoByteSize",290            "totalDebugInfoIndexTime",291            "totalDebugInfoIndexLoadedFromCache",292            "totalDebugInfoIndexSavedToCache",293            "totalDebugInfoParseTime",294            "totalDwoFileCount",295            "totalLoadedDwoFileCount",296            "totalDwoErrorCount",297        ]298        self.verify_keys(debug_stats, '"debug_stats"', debug_stat_keys, None)299        stats = debug_stats["targets"][0]300        keys_exist = [301            "expressionEvaluation",302            "firstStopTime",303            "frameVariable",304            "launchOrAttachTime",305            "moduleIdentifiers",306            "targetCreateTime",307            "summaryProviderStatistics",308        ]309        self.verify_keys(stats, '"stats"', keys_exist, None)310        self.assertGreater(stats["firstStopTime"], 0.0)311        self.assertGreater(stats["launchOrAttachTime"], 0.0)312        self.assertGreater(stats["targetCreateTime"], 0.0)313 314    def test_memory(self):315        """316        Test "statistics dump" and the memory information.317        """318        self.build()319        exe = self.getBuildArtifact("a.out")320        target = self.createTestTarget(file_path=exe)321        debug_stats = self.get_stats()322        debug_stat_keys = [323            "memory",324            "modules",325            "targets",326            "totalSymbolTableParseTime",327            "totalSymbolTableIndexTime",328            "totalSymbolTablesLoadedFromCache",329            "totalSymbolTablesSavedToCache",330            "totalDebugInfoParseTime",331            "totalDebugInfoIndexTime",332            "totalDebugInfoIndexLoadedFromCache",333            "totalDebugInfoIndexSavedToCache",334            "totalDebugInfoByteSize",335            "totalDwoFileCount",336            "totalLoadedDwoFileCount",337            "totalDwoErrorCount",338        ]339        self.verify_keys(debug_stats, '"debug_stats"', debug_stat_keys, None)340 341        memory = debug_stats["memory"]342        memory_keys = [343            "strings",344        ]345        self.verify_keys(memory, '"memory"', memory_keys, None)346 347        strings = memory["strings"]348        strings_keys = [349            "bytesTotal",350            "bytesUsed",351            "bytesUnused",352        ]353        self.verify_keys(strings, '"strings"', strings_keys, None)354 355    def find_module_in_metrics(self, path, stats):356        modules = stats["modules"]357        for module in modules:358            if module["path"] == path:359                return module360        return None361 362    def find_module_by_id_in_metrics(self, id, stats):363        modules = stats["modules"]364        for module in modules:365            if module["identifier"] == id:366                return module367        return None368 369    def test_modules(self):370        """371        Test "statistics dump" and the module information.372        """373        self.build()374        exe = self.getBuildArtifact("a.out")375        target = self.createTestTarget(file_path=exe)376        debug_stats = self.get_stats()377        debug_stat_keys = [378            "memory",379            "modules",380            "targets",381            "totalSymbolTableParseTime",382            "totalSymbolTableIndexTime",383            "totalSymbolTablesLoadedFromCache",384            "totalSymbolTablesSavedToCache",385            "totalDebugInfoParseTime",386            "totalDebugInfoIndexTime",387            "totalDebugInfoIndexLoadedFromCache",388            "totalDebugInfoIndexSavedToCache",389            "totalDebugInfoByteSize",390            "totalDwoFileCount",391            "totalLoadedDwoFileCount",392            "totalDwoErrorCount",393        ]394        self.verify_keys(debug_stats, '"debug_stats"', debug_stat_keys, None)395        stats = debug_stats["targets"][0]396        keys_exist = [397            "moduleIdentifiers",398        ]399        self.verify_keys(stats, '"stats"', keys_exist, None)400        exe_module = self.find_module_in_metrics(exe, debug_stats)401        module_keys = [402            "debugInfoByteSize",403            "debugInfoIndexLoadedFromCache",404            "debugInfoIndexTime",405            "debugInfoIndexSavedToCache",406            "debugInfoParseTime",407            "identifier",408            "path",409            "symbolTableIndexTime",410            "symbolTableLoadedFromCache",411            "symbolTableParseTime",412            "symbolTableSavedToCache",413            "dwoFileCount",414            "loadedDwoFileCount",415            "dwoErrorCount",416            "triple",417            "uuid",418        ]419        self.assertNotEqual(exe_module, None)420        self.verify_keys(exe_module, 'module dict for "%s"' % (exe), module_keys)421 422    def test_commands(self):423        """424        Test "statistics dump" and the command information.425        """426        self.build()427        exe = self.getBuildArtifact("a.out")428        target = self.createTestTarget(file_path=exe)429 430        interp = self.dbg.GetCommandInterpreter()431        result = lldb.SBCommandReturnObject()432        interp.HandleCommand("target list", result)433        interp.HandleCommand("target list", result)434 435        debug_stats = self.get_stats()436 437        command_stats = self.get_command_stats(debug_stats)438        self.assertNotEqual(command_stats, None)439        self.assertEqual(command_stats["target list"], 2)440 441    def test_breakpoints(self):442        """Test "statistics dump"443 444        Output expected to be something like:445 446        {447          "memory" : {...},448          "modules" : [...],449          "targets" : [450                {451                    "firstStopTime": 0.34164492800000001,452                    "launchOrAttachTime": 0.31969605400000001,453                    "moduleIdentifiers": [...],454                    "targetCreateTime": 0.0040863039999999998455                    "expressionEvaluation": {456                        "failures": 0,457                        "successes": 0458                    },459                    "frameVariable": {460                        "failures": 0,461                        "successes": 0462                    },463                    "breakpoints": [464                        {465                            "details": {...},466                            "id": 1,467                            "resolveTime": 2.65438675468                        },469                        {470                            "details": {...},471                            "id": 2,472                            "resolveTime": 4.3632581669999997473                        }474                    ]475                }476            ],477            "totalDebugInfoByteSize": 182522234,478            "totalDebugInfoIndexTime": 2.33343,479            "totalDebugInfoParseTime": 8.2121400240000071,480            "totalSymbolTableParseTime": 0.123,481            "totalSymbolTableIndexTime": 0.234,482            "totalBreakpointResolveTime": 7.0176449170000001483        }484 485        """486        self.build()487        target = self.createTestTarget()488        self.runCmd("b main.cpp:7")489        self.runCmd("b a_function")490        debug_stats = self.get_stats()491        debug_stat_keys = [492            "memory",493            "modules",494            "targets",495            "totalSymbolTableParseTime",496            "totalSymbolTableIndexTime",497            "totalSymbolTablesLoadedFromCache",498            "totalSymbolTablesSavedToCache",499            "totalDebugInfoParseTime",500            "totalDebugInfoIndexTime",501            "totalDebugInfoIndexLoadedFromCache",502            "totalDebugInfoIndexSavedToCache",503            "totalDebugInfoByteSize",504            "totalDwoFileCount",505            "totalLoadedDwoFileCount",506            "totalDwoErrorCount",507        ]508        self.verify_keys(debug_stats, '"debug_stats"', debug_stat_keys, None)509        target_stats = debug_stats["targets"][0]510        keys_exist = [511            "breakpoints",512            "expressionEvaluation",513            "frameVariable",514            "targetCreateTime",515            "moduleIdentifiers",516            "totalBreakpointResolveTime",517            "summaryProviderStatistics",518        ]519        self.verify_keys(target_stats, '"stats"', keys_exist, None)520        self.assertGreater(target_stats["totalBreakpointResolveTime"], 0.0)521        breakpoints = target_stats["breakpoints"]522        bp_keys_exist = [523            "details",524            "id",525            "internal",526            "numLocations",527            "numResolvedLocations",528            "resolveTime",529        ]530        for breakpoint in breakpoints:531            self.verify_keys(532                breakpoint, 'target_stats["breakpoints"]', bp_keys_exist, None533            )534 535    @add_test_categories(["dwo"])536    def test_non_split_dwarf_has_no_dwo_files(self):537        """538        Test "statistics dump" and the dwo file count.539        Builds a binary without split-dwarf mode, and then540        verifies the dwo file count is zero after running "statistics dump"541        """542        da = {"CXX_SOURCES": "third.cpp baz.cpp", "EXE": self.getBuildArtifact("a.out")}543        self.build(dictionary=da, debug_info=["debug_names"])544        self.addTearDownCleanup(dictionary=da)545        exe = self.getBuildArtifact("a.out")546        target = self.createTestTarget(file_path=exe)547        debug_stats = self.get_stats()548        self.assertIn("totalDwoFileCount", debug_stats)549        self.assertIn("totalLoadedDwoFileCount", debug_stats)550 551        # Verify that the dwo file count is zero552        self.assertEqual(debug_stats["totalDwoFileCount"], 0)553        self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 0)554 555    @add_test_categories(["dwo"])556    def test_no_debug_names_eager_loads_dwo_files(self):557        """558        Test the eager loading behavior of DWO files when debug_names is absent by559        building a split-dwarf binary without debug_names and then running "statistics dump".560        DWO file loading behavior:561        - With debug_names: DebugNamesDWARFIndex allows for lazy loading.562          DWO files are loaded on-demand when symbols are actually looked up563        - Without debug_names: ManualDWARFIndex uses eager loading.564          All DWO files are loaded upfront during the first symbol lookup to build a manual index.565        """566        da = {"CXX_SOURCES": "third.cpp baz.cpp", "EXE": self.getBuildArtifact("a.out")}567        self.build(dictionary=da, debug_info=["dwo"])568        self.addTearDownCleanup(dictionary=da)569        exe = self.getBuildArtifact("a.out")570        target = self.createTestTarget(file_path=exe)571        debug_stats = self.get_stats()572        self.assertIn("totalDwoFileCount", debug_stats)573        self.assertIn("totalLoadedDwoFileCount", debug_stats)574 575        # Verify that all DWO files are loaded576        self.assertEqual(debug_stats["totalDwoFileCount"], 2)577        self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 2)578 579    @add_test_categories(["dwo"])580    def test_split_dwarf_dwo_file_count(self):581        """582        Test "statistics dump" and the dwo file count.583        Builds a binary w/ separate .dwo files and debug_names, and then584        verifies the loaded dwo file count is the expected count after running585        various commands586        """587        da = {"CXX_SOURCES": "third.cpp baz.cpp", "EXE": self.getBuildArtifact("a.out")}588        # -gsplit-dwarf creates separate .dwo files,589        # -gpubnames enables the debug_names accelerator tables for faster symbol lookup590        #  and lazy loading of DWO files591        # Expected output: third.dwo (contains main) and baz.dwo (contains Baz struct/function)592        self.build(dictionary=da, debug_info=["dwo", "debug_names"])593        self.addTearDownCleanup(dictionary=da)594        exe = self.getBuildArtifact("a.out")595        target = self.createTestTarget(file_path=exe)596        debug_stats = self.get_stats()597 598        # 1) 2 DWO files available but none loaded yet599        self.assertEqual(len(debug_stats["modules"]), 1)600        self.assertIn("totalLoadedDwoFileCount", debug_stats)601        self.assertIn("totalDwoFileCount", debug_stats)602        self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 0)603        self.assertEqual(debug_stats["totalDwoFileCount"], 2)604 605        # Since there's only one module, module stats should have the same counts as total counts606        self.assertIn("dwoFileCount", debug_stats["modules"][0])607        self.assertIn("loadedDwoFileCount", debug_stats["modules"][0])608        self.assertEqual(debug_stats["modules"][0]["loadedDwoFileCount"], 0)609        self.assertEqual(debug_stats["modules"][0]["dwoFileCount"], 2)610 611        # 2) Setting breakpoint in main triggers loading of third.dwo (contains main function)612        self.runCmd("b main")613        debug_stats = self.get_stats()614        self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 1)615        self.assertEqual(debug_stats["totalDwoFileCount"], 2)616 617        self.assertEqual(debug_stats["modules"][0]["loadedDwoFileCount"], 1)618        self.assertEqual(debug_stats["modules"][0]["dwoFileCount"], 2)619 620        # 3) Type lookup forces loading of baz.dwo (contains struct Baz definition)621        self.runCmd("type lookup Baz")622        debug_stats = self.get_stats()623        self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 2)624        self.assertEqual(debug_stats["totalDwoFileCount"], 2)625 626        self.assertEqual(debug_stats["modules"][0]["loadedDwoFileCount"], 2)627        self.assertEqual(debug_stats["modules"][0]["dwoFileCount"], 2)628 629    @add_test_categories(["dwo"])630    def test_dwp_dwo_file_count(self):631        """632        Test "statistics dump" and the loaded dwo file count.633        Builds a binary w/ a separate .dwp file and debug_names, and then634        verifies the loaded dwo file count is the expected count after running635        various commands.636 637        We expect the DWO file counters to reflect the number of compile units638        loaded from the DWP file (each representing what was originally a separate DWO file)639        """640        da = {"CXX_SOURCES": "third.cpp baz.cpp", "EXE": self.getBuildArtifact("a.out")}641        self.build(dictionary=da, debug_info=["dwp", "debug_names"])642        self.addTearDownCleanup(dictionary=da)643        exe = self.getBuildArtifact("a.out")644        target = self.createTestTarget(file_path=exe)645        debug_stats = self.get_stats()646 647        # Initially: 2 DWO files available but none loaded yet648        self.assertIn("totalLoadedDwoFileCount", debug_stats)649        self.assertIn("totalDwoFileCount", debug_stats)650        self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 0)651        self.assertEqual(debug_stats["totalDwoFileCount"], 2)652 653        # Setting breakpoint in main triggers parsing of the CU within a.dwp corresponding to third.dwo (contains main function)654        self.runCmd("b main")655        debug_stats = self.get_stats()656        self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 1)657        self.assertEqual(debug_stats["totalDwoFileCount"], 2)658 659        # Type lookup forces parsing of the CU within a.dwp corresponding to baz.dwo (contains struct Baz definition)660        self.runCmd("type lookup Baz")661        debug_stats = self.get_stats()662        self.assertEqual(debug_stats["totalDwoFileCount"], 2)663        self.assertEqual(debug_stats["totalLoadedDwoFileCount"], 2)664 665    @add_test_categories(["dwo"])666    def test_dwo_missing_error_stats(self):667        """668        Test that DWO missing errors are reported correctly in statistics.669        This test:670        1) Builds a program with split DWARF (.dwo files)671        2) Delete one of the two .dwo files672        3) Verify that 1 DWO error is reported in statistics673        """674        da = {675            "CXX_SOURCES": "dwo_error_main.cpp dwo_error_foo.cpp",676            "EXE": self.getBuildArtifact("a.out"),677        }678        # -gsplit-dwarf creates separate .dwo files,679        # Expected output: dwo_error_main.dwo (contains main) and dwo_error_foo.dwo (contains foo struct/function)680        self.build(dictionary=da, debug_info="dwo")681        exe = self.getBuildArtifact("a.out")682 683        expected_dwo_files = [684            self.getBuildArtifact("dwo_error_main.dwo"),685            self.getBuildArtifact("dwo_error_foo.dwo"),686        ]687 688        # Verify expected files exist689        for dwo_file in expected_dwo_files:690            self.assertTrue(691                os.path.exists(dwo_file),692                f"Expected .dwo file does not exist: {dwo_file}",693            )694 695        # Remove one of .dwo files to trigger DWO load error696        dwo_main_file = self.getBuildArtifact("dwo_error_main.dwo")697        os.remove(dwo_main_file)698 699        target = self.createTestTarget(file_path=exe)700        debug_stats = self.get_stats()701 702        # Check DWO load error statistics are reported703        self.assertIn("totalDwoErrorCount", debug_stats)704        self.assertEqual(debug_stats["totalDwoErrorCount"], 1)705 706        # Since there's only one module, module stats should have the same count as total count707        self.assertIn("dwoErrorCount", debug_stats["modules"][0])708        self.assertEqual(debug_stats["modules"][0]["dwoErrorCount"], 1)709 710    @add_test_categories(["dwo"])711    def test_dwo_id_mismatch_error_stats(self):712        """713        Test that DWO ID mismatch errors are reported correctly in statistics.714        This test:715        1) Builds a program with split DWARF (.dwo files)716        2) Replace one of the .dwo files with a mismatched one to cause a DWO ID mismatch error717        3) Verifies that a DWO error is reported in statistics718        """719        da = {720            "CXX_SOURCES": "dwo_error_main.cpp dwo_error_foo.cpp",721            "EXE": self.getBuildArtifact("a.out"),722        }723        # -gsplit-dwarf creates separate .dwo files,724        # Expected output: dwo_error_main.dwo (contains main) and dwo_error_foo.dwo (contains foo struct/function)725        self.build(dictionary=da, debug_info="dwo")726        exe = self.getBuildArtifact("a.out")727 728        expected_dwo_files = [729            self.getBuildArtifact("dwo_error_main.dwo"),730            self.getBuildArtifact("dwo_error_foo.dwo"),731        ]732 733        # Verify expected files exist734        for dwo_file in expected_dwo_files:735            self.assertTrue(736                os.path.exists(dwo_file),737                f"Expected .dwo file does not exist: {dwo_file}",738            )739 740        # Replace one of the original .dwo file content with another one to trigger DWO ID mismatch error741        dwo_foo_file = self.getBuildArtifact("dwo_error_foo.dwo")742        dwo_main_file = self.getBuildArtifact("dwo_error_main.dwo")743 744        shutil.copy(dwo_main_file, dwo_foo_file)745 746        # Create a new target and get stats747        target = self.createTestTarget(file_path=exe)748        debug_stats = self.get_stats()749 750        # Check that DWO load error statistics are reported751        self.assertIn("totalDwoErrorCount", debug_stats)752        self.assertEqual(debug_stats["totalDwoErrorCount"], 1)753 754        # Since there's only one module, module stats should have the same count as total count755        self.assertIn("dwoErrorCount", debug_stats["modules"][0])756        self.assertEqual(debug_stats["modules"][0]["dwoErrorCount"], 1)757 758    @skipUnlessDarwin759    @no_debug_info_test760    def test_dsym_binary_has_symfile_in_stats(self):761        """762        Test that if our executable has a stand alone dSYM file containing763        debug information, that the dSYM file path is listed as a key/value764        pair in the "a.out" binaries module stats. Also verify the the main765        executable's module statistics has a debug info size that is greater766        than zero as the dSYM contains debug info.767        """768        self.build(debug_info="dsym")769        exe_name = "a.out"770        exe = self.getBuildArtifact(exe_name)771        dsym = self.getBuildArtifact(exe_name + ".dSYM")772        # Make sure the executable file exists after building.773        self.assertTrue(os.path.exists(exe))774        # Make sure the dSYM file exists after building.775        self.assertTrue(os.path.isdir(dsym))776 777        # Create the target778        target = self.createTestTarget(file_path=exe)779 780        debug_stats = self.get_stats()781 782        exe_stats = self.find_module_in_metrics(exe, debug_stats)783        # If we have a dSYM file, there should be a key/value pair in the module784        # statistics and the path should match the dSYM file path in the build785        # artifacts.786        self.assertIn("symbolFilePath", exe_stats)787        stats_dsym = exe_stats["symbolFilePath"]788 789        # Make sure main executable's module info has debug info size that is790        # greater than zero as the dSYM file and main executable work together791        # in the lldb.SBModule class to provide the data.792        self.assertGreater(exe_stats["debugInfoByteSize"], 0)793 794        # The "dsym" variable contains the bundle directory for the dSYM, while795        # the "stats_dsym" will have the796        self.assertIn(dsym, stats_dsym)797        # Since we have a dSYM file, we should not be loading DWARF from the .o798        # files and the .o file module identifiers should NOT be in the module799        # statistics.800        self.assertNotIn("symbolFileModuleIdentifiers", exe_stats)801 802    @skipUnlessDarwin803    @no_debug_info_test804    def test_no_dsym_binary_has_symfile_identifiers_in_stats(self):805        """806        Test that if our executable loads debug info from the .o files,807        that the module statistics contains a 'symbolFileModuleIdentifiers'808        key which is a list of module identifiers, and verify that the809        module identifier can be used to find the .o file's module stats.810        Also verify the the main executable's module statistics has a debug811        info size that is zero, as the main executable itself has no debug812        info, but verify that the .o files have debug info size that is813        greater than zero. This test ensures that we don't double count814        debug info.815        """816        self.build(debug_info="dwarf")817        exe_name = "a.out"818        exe = self.getBuildArtifact(exe_name)819        dsym = self.getBuildArtifact(exe_name + ".dSYM")820        # Make sure the executable file exists after building.821        self.assertTrue(os.path.exists(exe))822        # Make sure the dSYM file doesn't exist after building.823        self.assertFalse(os.path.isdir(dsym))824 825        # Create the target826        target = self.createTestTarget(file_path=exe)827 828        # Force the 'main.o' .o file's DWARF to be loaded so it will show up829        # in the stats.830        self.runCmd("b main.cpp:7")831 832        debug_stats = self.get_stats("--all-targets")833 834        exe_stats = self.find_module_in_metrics(exe, debug_stats)835        # If we don't have a dSYM file, there should not be a key/value pair in836        # the module statistics.837        self.assertNotIn("symbolFilePath", exe_stats)838 839        # Make sure main executable's module info has debug info size that is840        # zero as there is no debug info in the main executable, only in the841        # .o files. The .o files will also only be loaded if something causes842        # them to be loaded, so we set a breakpoint to force the .o file debug843        # info to be loaded.844        self.assertEqual(exe_stats["debugInfoByteSize"], 0)845 846        # When we don't have a dSYM file, the SymbolFileDWARFDebugMap class847        # should create modules for each .o file that contains DWARF that the848        # symbol file creates, so we need to verify that we have a valid module849        # identifier for main.o that is we should not be loading DWARF from the .o850        # files and the .o file module identifiers should NOT be in the module851        # statistics.852        self.assertIn("symbolFileModuleIdentifiers", exe_stats)853 854        symfileIDs = exe_stats["symbolFileModuleIdentifiers"]855        for symfileID in symfileIDs:856            o_module = self.find_module_by_id_in_metrics(symfileID, debug_stats)857            self.assertNotEqual(o_module, None)858            # Make sure each .o file has some debug info bytes.859            self.assertGreater(o_module["debugInfoByteSize"], 0)860 861    @skipUnlessDarwin862    @no_debug_info_test863    def test_had_frame_variable_errors(self):864        """865        Test that if we have frame variable errors that we see this in the866        statistics for the module that had issues.867        """868        self.build(debug_info="dwarf")869        exe_name = "a.out"870        exe = self.getBuildArtifact(exe_name)871        dsym = self.getBuildArtifact(exe_name + ".dSYM")872        main_obj = self.getBuildArtifact("main.o")873        # Make sure the executable file exists after building.874        self.assertTrue(os.path.exists(exe))875        # Make sure the dSYM file doesn't exist after building.876        self.assertFalse(os.path.isdir(dsym))877        # Make sure the main.o object file exists after building.878        self.assertTrue(os.path.exists(main_obj))879 880        # Delete the main.o file that contains the debug info so we force an881        # error when we run to main and try to get variables882        os.unlink(main_obj)883 884        (target, process, thread, bkpt) = lldbutil.run_to_name_breakpoint(self, "main")885 886        # Get stats and verify we had errors.887        stats = self.get_stats()888        exe_stats = self.find_module_in_metrics(exe, stats)889        self.assertIsNotNone(exe_stats)890 891        # Make sure we have "debugInfoHadVariableErrors" variable that is set to892        # false before failing to get local variables due to missing .o file.893        self.assertFalse(exe_stats["debugInfoHadVariableErrors"])894 895        # Verify that the top level statistic that aggregates the number of896        # modules with debugInfoHadVariableErrors is zero897        self.assertEqual(stats["totalModuleCountWithVariableErrors"], 0)898 899        # Try and fail to get variables900        vars = thread.GetFrameAtIndex(0).GetVariables(True, True, False, True)901 902        # Make sure we got an error back that indicates that variables were not903        # available904        self.assertTrue(vars.GetError().Fail())905 906        # Get stats and verify we had errors.907        stats = self.get_stats()908        exe_stats = self.find_module_in_metrics(exe, stats)909        self.assertIsNotNone(exe_stats)910 911        # Make sure we have "hadFrameVariableErrors" variable that is set to912        # true after failing to get local variables due to missing .o file.913        self.assertTrue(exe_stats["debugInfoHadVariableErrors"])914 915        # Verify that the top level statistic that aggregates the number of916        # modules with debugInfoHadVariableErrors is greater than zero917        self.assertGreater(stats["totalModuleCountWithVariableErrors"], 0)918 919    def test_transcript_happy_path(self):920        """921        Test "statistics dump" and the transcript information.922        """923        self.build()924        exe = self.getBuildArtifact("a.out")925        target = self.createTestTarget(file_path=exe)926        self.runCmd("settings set interpreter.save-transcript true")927        self.runCmd("version")928 929        # Verify the output of a first "statistics dump"930        debug_stats = self.get_stats("--transcript true")931        self.assertIn("transcript", debug_stats)932        transcript = debug_stats["transcript"]933        self.assertEqual(len(transcript), 2)934        self.assertEqual(transcript[0]["commandName"], "version")935        self.assertEqual(transcript[1]["commandName"], "statistics dump")936        # The first "statistics dump" in the transcript should have no output937        self.assertNotIn("output", transcript[1])938 939        # Verify the output of a second "statistics dump"940        debug_stats = self.get_stats("--transcript true")941        self.assertIn("transcript", debug_stats)942        transcript = debug_stats["transcript"]943        self.assertEqual(len(transcript), 3)944        self.assertEqual(transcript[0]["commandName"], "version")945        self.assertEqual(transcript[1]["commandName"], "statistics dump")946        # The first "statistics dump" in the transcript should have output now947        self.assertIn("output", transcript[1])948        self.assertEqual(transcript[2]["commandName"], "statistics dump")949        # The second "statistics dump" in the transcript should have no output950        self.assertNotIn("output", transcript[2])951 952    def test_transcript_warning_when_disabled(self):953        """954        Test that "statistics dump --transcript=true" shows a warning when955        transcript saving is disabled.956        """957        self.build()958        exe = self.getBuildArtifact("a.out")959        target = self.createTestTarget(file_path=exe)960 961        # Ensure transcript saving is disabled (this is the default)962        self.runCmd("settings set interpreter.save-transcript false")963 964        # Request transcript in statistics dump and check for warning965        interpreter = self.dbg.GetCommandInterpreter()966        res = lldb.SBCommandReturnObject()967        interpreter.HandleCommand("statistics dump --transcript=true", res)968        self.assertTrue(res.Succeeded())969        # We should warn about transcript being requested but not saved970        self.assertIn(971            "transcript requested but none was saved. Enable with "972            "'settings set interpreter.save-transcript true'",973            res.GetError(),974        )975 976    def verify_stats(self, stats, expectation, options):977        for field_name in expectation:978            idx = field_name.find(".")979            if idx == -1:980                # `field_name` is a top-level field981                exists = field_name in stats982                should_exist = expectation[field_name]983                should_exist_string = "" if should_exist else "not "984                self.assertEqual(985                    exists,986                    should_exist,987                    f"'{field_name}' should {should_exist_string}exist for 'statistics dump{options}'",988                )989            else:990                # `field_name` is a string of "<top-level field>.<second-level field>"991                top_level_field_name = field_name[0:idx]992                second_level_field_name = field_name[idx + 1 :]993                for top_level_field in (994                    stats[top_level_field_name] if top_level_field_name in stats else {}995                ):996                    exists = second_level_field_name in top_level_field997                    should_exist = expectation[field_name]998                    should_exist_string = "" if should_exist else "not "999                    self.assertEqual(1000                        exists,1001                        should_exist,1002                        f"'{field_name}' should {should_exist_string}exist for 'statistics dump{options}'",1003                    )1004 1005    def get_test_cases_for_sections_existence(self):1006        should_always_exist_or_not = {1007            "totalDebugInfoEnabled": True,1008            "memory": True,1009        }1010        test_cases = [1011            {  # Everything mode1012                "command_options": "",1013                "api_options": {},1014                "expect": {1015                    "commands": True,1016                    "targets": True,1017                    "targets.moduleIdentifiers": True,1018                    "targets.breakpoints": True,1019                    "targets.expressionEvaluation": True,1020                    "targets.frameVariable": True,1021                    "targets.totalSharedLibraryEventHitCount": True,1022                    "modules": True,1023                    "transcript": False,1024                },1025            },1026            {  # Summary mode1027                "command_options": " --summary",1028                "api_options": {1029                    "SetSummaryOnly": True,1030                },1031                "expect": {1032                    "commands": False,1033                    "targets": True,1034                    "targets.moduleIdentifiers": False,1035                    "targets.breakpoints": False,1036                    "targets.expressionEvaluation": False,1037                    "targets.frameVariable": False,1038                    "targets.totalSharedLibraryEventHitCount": True,1039                    "modules": False,1040                    "transcript": False,1041                },1042            },1043            {  # Summary mode with targets1044                "command_options": " --summary --targets=true",1045                "api_options": {1046                    "SetSummaryOnly": True,1047                    "SetIncludeTargets": True,1048                },1049                "expect": {1050                    "commands": False,1051                    "targets": True,1052                    "targets.moduleIdentifiers": False,1053                    "targets.breakpoints": False,1054                    "targets.expressionEvaluation": False,1055                    "targets.frameVariable": False,1056                    "targets.totalSharedLibraryEventHitCount": True,1057                    "modules": False,1058                    "transcript": False,1059                },1060            },1061            {  # Summary mode without targets1062                "command_options": " --summary --targets=false",1063                "api_options": {1064                    "SetSummaryOnly": True,1065                    "SetIncludeTargets": False,1066                },1067                "expect": {1068                    "commands": False,1069                    "targets": False,1070                    "modules": False,1071                    "transcript": False,1072                },1073            },1074            {  # Summary mode with modules1075                "command_options": " --summary --modules=true",1076                "api_options": {1077                    "SetSummaryOnly": True,1078                    "SetIncludeModules": True,1079                },1080                "expect": {1081                    "commands": False,1082                    "targets": True,1083                    "targets.moduleIdentifiers": False,1084                    "targets.breakpoints": False,1085                    "targets.expressionEvaluation": False,1086                    "targets.frameVariable": False,1087                    "targets.totalSharedLibraryEventHitCount": True,1088                    "modules": True,1089                    "transcript": False,1090                },1091            },1092            {  # Default mode without modules and transcript1093                "command_options": " --modules=false --transcript=false",1094                "api_options": {1095                    "SetIncludeModules": False,1096                    "SetIncludeTranscript": False,1097                },1098                "expect": {1099                    "commands": True,1100                    "targets": True,1101                    "targets.moduleIdentifiers": False,1102                    "targets.breakpoints": True,1103                    "targets.expressionEvaluation": True,1104                    "targets.frameVariable": True,1105                    "targets.totalSharedLibraryEventHitCount": True,1106                    "modules": False,1107                    "transcript": False,1108                },1109            },1110            {  # Default mode without modules and with transcript1111                "command_options": " --modules=false --transcript=true",1112                "api_options": {1113                    "SetIncludeModules": False,1114                    "SetIncludeTranscript": True,1115                },1116                "expect": {1117                    "commands": True,1118                    "targets": True,1119                    "targets.moduleIdentifiers": False,1120                    "targets.breakpoints": True,1121                    "targets.expressionEvaluation": True,1122                    "targets.frameVariable": True,1123                    "targets.totalSharedLibraryEventHitCount": True,1124                    "modules": False,1125                    "transcript": True,1126                },1127            },1128            {  # Default mode without modules1129                "command_options": " --modules=false",1130                "api_options": {1131                    "SetIncludeModules": False,1132                },1133                "expect": {1134                    "commands": True,1135                    "targets": True,1136                    "targets.moduleIdentifiers": False,1137                    "targets.breakpoints": True,1138                    "targets.expressionEvaluation": True,1139                    "targets.frameVariable": True,1140                    "targets.totalSharedLibraryEventHitCount": True,1141                    "modules": False,1142                    "transcript": False,1143                },1144            },1145            {  # Default mode with transcript1146                "command_options": " --transcript=true",1147                "api_options": {1148                    "SetIncludeTranscript": True,1149                },1150                "expect": {1151                    "commands": True,1152                    "targets": True,1153                    "targets.moduleIdentifiers": True,1154                    "targets.breakpoints": True,1155                    "targets.expressionEvaluation": True,1156                    "targets.frameVariable": True,1157                    "targets.totalSharedLibraryEventHitCount": True,1158                    "modules": True,1159                    "transcript": True,1160                },1161            },1162        ]1163        return (should_always_exist_or_not, test_cases)1164 1165    def test_sections_existence_through_command(self):1166        """1167        Test "statistics dump" and the existence of sections when different1168        options are given through the command line (CLI or HandleCommand).1169        """1170        self.build()1171        exe = self.getBuildArtifact("a.out")1172        target = self.createTestTarget(file_path=exe)1173 1174        # Create some transcript so that it can be tested.1175        self.runCmd("settings set interpreter.save-transcript true")1176        self.runCmd("version")1177        self.runCmd("b main")1178        # Then disable transcript so that it won't change during verification1179        self.runCmd("settings set interpreter.save-transcript false")1180 1181        # Expectation1182        (1183            should_always_exist_or_not,1184            test_cases,1185        ) = self.get_test_cases_for_sections_existence()1186 1187        # Verification1188        for test_case in test_cases:1189            options = test_case["command_options"]1190            # Get statistics dump result1191            stats = self.get_stats(options)1192            # Verify that each field should exist (or not)1193            expectation = {**should_always_exist_or_not, **test_case["expect"]}1194            self.verify_stats(stats, expectation, options)1195 1196    def test_sections_existence_through_api(self):1197        """1198        Test "statistics dump" and the existence of sections when different1199        options are given through the public API.1200        """1201        self.build()1202        exe = self.getBuildArtifact("a.out")1203        target = self.createTestTarget(file_path=exe)1204 1205        # Create some transcript so that it can be tested.1206        self.runCmd("settings set interpreter.save-transcript true")1207        self.runCmd("version")1208        self.runCmd("b main")1209        # But disable transcript so that it won't change during verification1210        self.runCmd("settings set interpreter.save-transcript false")1211 1212        # Expectation1213        (1214            should_always_exist_or_not,1215            test_cases,1216        ) = self.get_test_cases_for_sections_existence()1217 1218        # Verification1219        for test_case in test_cases:1220            # Create options1221            options = test_case["api_options"]1222            sb_options = lldb.SBStatisticsOptions()1223            for method_name, param_value in options.items():1224                getattr(sb_options, method_name)(param_value)1225            # Get statistics dump result1226            stream = lldb.SBStream()1227            target.GetStatistics(sb_options).GetAsJSON(stream)1228            stats = json.loads(stream.GetData())1229            # Verify that each field should exist (or not)1230            expectation = {**should_always_exist_or_not, **test_case["expect"]}1231            self.verify_stats(stats, expectation, options)1232 1233    def test_order_of_options_do_not_matter(self):1234        """1235        Test "statistics dump" and the order of options.1236        """1237        self.build()1238        exe = self.getBuildArtifact("a.out")1239        target = self.createTestTarget(file_path=exe)1240 1241        # Create some transcript so that it can be tested.1242        self.runCmd("settings set interpreter.save-transcript true")1243        self.runCmd("version")1244        self.runCmd("b main")1245        # Then disable transcript so that it won't change during verification1246        self.runCmd("settings set interpreter.save-transcript false")1247 1248        # The order of the following options shouldn't matter1249        test_cases = [1250            (" --summary", " --targets=true"),1251            (" --summary", " --targets=false"),1252            (" --summary", " --modules=true"),1253            (" --summary", " --modules=false"),1254            (" --summary", " --transcript=true"),1255            (" --summary", " --transcript=false"),1256        ]1257 1258        # Verification1259        for options in test_cases:1260            debug_stats_0 = self.get_stats(options[0] + options[1])1261            debug_stats_1 = self.get_stats(options[1] + options[0])1262            # Redact all numbers1263            debug_stats_0 = re.sub(r"\d+", "0", json.dumps(debug_stats_0))1264            debug_stats_1 = re.sub(r"\d+", "0", json.dumps(debug_stats_1))1265            # Verify that the two output are the same1266            self.assertEqual(1267                debug_stats_0,1268                debug_stats_1,1269                f"The order of options '{options[0]}' and '{options[1]}' should not matter",1270            )1271 1272    @skipIfWindows1273    def test_summary_statistics_providers(self):1274        """1275        Test summary timing statistics is included in statistics dump when1276        a type with a summary provider exists, and is evaluated.1277        """1278 1279        self.build()1280        target = self.createTestTarget()1281        lldbutil.run_to_source_breakpoint(1282            self, "// stop here", lldb.SBFileSpec("main.cpp")1283        )1284        self.expect("frame var", substrs=["hello world"])1285        stats = self.get_target_stats(self.get_stats())1286        self.assertIn("summaryProviderStatistics", stats)1287        summary_providers = stats["summaryProviderStatistics"]1288        # We don't want to take a dependency on the type name, so we just look1289        # for string and that it was called once.1290        summary_provider_str = str(summary_providers)1291        self.assertIn("string", summary_provider_str)1292        self.assertIn("'count': 1", summary_provider_str)1293        self.assertIn("'totalTime':", summary_provider_str)1294        # We may hit the std::string C++ provider, or a summary provider string1295        self.assertIn("'type':", summary_provider_str)1296        self.assertTrue(1297            "c++" in summary_provider_str or "string" in summary_provider_str1298        )1299 1300        self.runCmd("continue")1301        self.runCmd("command script import BoxFormatter.py")1302        self.expect("frame var", substrs=["box = [27]"])1303        stats = self.get_target_stats(self.get_stats())1304        self.assertIn("summaryProviderStatistics", stats)1305        summary_providers = stats["summaryProviderStatistics"]1306        summary_provider_str = str(summary_providers)1307        self.assertIn("BoxFormatter.summary", summary_provider_str)1308        self.assertIn("'count': 1", summary_provider_str)1309        self.assertIn("'totalTime':", summary_provider_str)1310        self.assertIn("'type': 'python'", summary_provider_str)1311 1312    @skipIfWindows1313    def test_summary_statistics_providers_vec(self):1314        """1315        Test summary timing statistics is included in statistics dump when1316        a type with a summary provider exists, and is evaluated. This variation1317        tests that vector recurses into it's child type.1318        """1319        self.build()1320        target = self.createTestTarget()1321        lldbutil.run_to_source_breakpoint(1322            self, "// stop vector", lldb.SBFileSpec("main.cpp")1323        )1324        self.expect(1325            "frame var", substrs=["int_vec", "double_vec", "[0] = 1", "[7] = 8"]1326        )1327        stats = self.get_target_stats(self.get_stats())1328        self.assertIn("summaryProviderStatistics", stats)1329        summary_providers = stats["summaryProviderStatistics"]1330        summary_provider_str = str(summary_providers)1331        self.assertIn("'count': 2", summary_provider_str)1332        self.assertIn("'totalTime':", summary_provider_str)1333        self.assertIn("'type':", summary_provider_str)1334        # We may hit the std::vector C++ provider, or a summary provider string1335        if "c++" in summary_provider_str:1336            self.assertIn("std::vector", summary_provider_str)1337 1338    @skipIfWindows1339    def test_multiple_targets(self):1340        """1341        Test statistics dump only reports the stats from current target and1342        "statistics dump --all-targets" includes all target stats.1343        """1344        da = {"CXX_SOURCES": "main.cpp", "EXE": self.getBuildArtifact("a.out")}1345        self.build(dictionary=da)1346        self.addTearDownCleanup(dictionary=da)1347 1348        db = {"CXX_SOURCES": "second.cpp", "EXE": self.getBuildArtifact("second.out")}1349        self.build(dictionary=db)1350        self.addTearDownCleanup(dictionary=db)1351 1352        main_exe = self.getBuildArtifact("a.out")1353        second_exe = self.getBuildArtifact("second.out")1354 1355        (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(1356            self, "// break here", lldb.SBFileSpec("main.cpp"), None, "a.out"1357        )1358        debugger_stats1 = self.get_stats()1359        self.assertIsNotNone(self.find_module_in_metrics(main_exe, debugger_stats1))1360        self.assertIsNone(self.find_module_in_metrics(second_exe, debugger_stats1))1361 1362        (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(1363            self, "// break here", lldb.SBFileSpec("second.cpp"), None, "second.out"1364        )1365        debugger_stats2 = self.get_stats()1366        self.assertIsNone(self.find_module_in_metrics(main_exe, debugger_stats2))1367        self.assertIsNotNone(self.find_module_in_metrics(second_exe, debugger_stats2))1368 1369        all_targets_stats = self.get_stats("--all-targets")1370        self.assertIsNotNone(self.find_module_in_metrics(main_exe, all_targets_stats))1371        self.assertIsNotNone(self.find_module_in_metrics(second_exe, all_targets_stats))1372 1373    # Return some level of the plugin stats hierarchy.1374    # Will return either the top-level node, the namespace node, or a specific1375    # plugin node based on requested values.1376    #1377    # If any of the requested keys are not found in the stats then return None.1378    #1379    # Plugin stats look like this:1380    #1381    # "plugins": {1382    #     "system-runtime": [1383    #         {1384    #             "enabled": true,1385    #             "name": "systemruntime-macosx"1386    #         }1387    #     ]1388    # },1389    def get_plugin_stats(self, debugger_stats, plugin_namespace=None, plugin_name=None):1390        # Get top level plugin stats.1391        if "plugins" not in debugger_stats:1392            return None1393        plugins = debugger_stats["plugins"]1394        if not plugin_namespace:1395            return plugins1396 1397        # Plugin namespace stats.1398        if plugin_namespace not in plugins:1399            return None1400        plugins_for_namespace = plugins[plugin_namespace]1401        if not plugin_name:1402            return plugins_for_namespace1403 1404        # Specific plugin stats.1405        for plugin in debugger_stats["plugins"][plugin_namespace]:1406            if plugin["name"] == plugin_name:1407                return plugin1408        return None1409 1410    def test_plugin_stats(self):1411        """1412        Test "statistics dump" contains plugin info.1413        """1414        self.build()1415        exe = self.getBuildArtifact("a.out")1416        target = self.createTestTarget(file_path=exe)1417        debugger_stats = self.get_stats()1418 1419        # Verify that the statistics dump contains the plugin information.1420        plugins = self.get_plugin_stats(debugger_stats)1421        self.assertIsNotNone(plugins)1422 1423        # Check for a known plugin namespace that should be in the stats.1424        system_runtime_plugins = self.get_plugin_stats(debugger_stats, "system-runtime")1425        self.assertIsNotNone(system_runtime_plugins)1426 1427        # Validate the keys exists for the bottom-level plugin stats.1428        plugin_keys_exist = [1429            "name",1430            "enabled",1431        ]1432        for plugin in system_runtime_plugins:1433            self.verify_keys(1434                plugin, 'debugger_stats["plugins"]["system-runtime"]', plugin_keys_exist1435            )1436 1437        # Check for a known plugin that is enabled by default.1438        system_runtime_macosx_plugin = self.get_plugin_stats(1439            debugger_stats, "system-runtime", "systemruntime-macosx"1440        )1441        self.assertIsNotNone(system_runtime_macosx_plugin)1442        self.assertTrue(system_runtime_macosx_plugin["enabled"])1443 1444        # Now disable the plugin and check the stats again.1445        # The stats should show the plugin is disabled.1446        self.runCmd("plugin disable system-runtime.systemruntime-macosx")1447        debugger_stats = self.get_stats()1448        system_runtime_macosx_plugin = self.get_plugin_stats(1449            debugger_stats, "system-runtime", "systemruntime-macosx"1450        )1451        self.assertIsNotNone(system_runtime_macosx_plugin)1452        self.assertFalse(system_runtime_macosx_plugin["enabled"])1453 1454        # Plugins should not show up in the stats when disabled with an option.1455        debugger_stats = self.get_stats("--plugins false")1456        plugins = self.get_plugin_stats(debugger_stats)1457        self.assertIsNone(plugins)1458