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