from __future__ import print_function from __future__ import absolute_import # System modules import os import textwrap # Third-party modules import io # LLDB modules import lldb from .lldbtest import * from . import configuration from . import lldbutil from .decorators import * def source_type(filename): _, extension = os.path.splitext(filename) return { ".c": "C_SOURCES", ".cpp": "CXX_SOURCES", ".cxx": "CXX_SOURCES", ".cc": "CXX_SOURCES", ".m": "OBJC_SOURCES", ".mm": "OBJCXX_SOURCES", }.get(extension, None) class CommandParser: def __init__(self): self.breakpoints = [] def parse_one_command(self, line): parts = line.split("//%") command = None new_breakpoint = True if len(parts) == 2: command = parts[1].rstrip() new_breakpoint = parts[0].strip() != "" return (command, new_breakpoint) def parse_source_files(self, source_files): for source_file in source_files: file_handle = io.open(source_file, encoding="utf-8") lines = file_handle.readlines() line_number = 0 # non-NULL means we're looking through whitespace to find # additional commands current_breakpoint = None for line in lines: line_number = line_number + 1 # 1-based, so we do this first (command, new_breakpoint) = self.parse_one_command(line) if new_breakpoint: current_breakpoint = None if command is not None: if current_breakpoint is None: current_breakpoint = {} current_breakpoint["file_name"] = source_file current_breakpoint["line_number"] = line_number current_breakpoint["command"] = command self.breakpoints.append(current_breakpoint) else: current_breakpoint["command"] = ( current_breakpoint["command"] + "\n" + command ) for bkpt in self.breakpoints: bkpt["command"] = textwrap.dedent(bkpt["command"]) def set_breakpoints(self, target): for breakpoint in self.breakpoints: breakpoint["breakpoint"] = target.BreakpointCreateByLocation( breakpoint["file_name"], breakpoint["line_number"] ) def handle_breakpoint(self, test, breakpoint_id): for breakpoint in self.breakpoints: if breakpoint["breakpoint"].GetID() == breakpoint_id: test.execute_user_command(breakpoint["command"]) return class InlineTest(TestBase): def getBuildDirBasename(self): return self.__class__.__name__ + "." + self.testMethodName def BuildMakefile(self): makefilePath = self.getBuildArtifact("Makefile") if os.path.exists(makefilePath): return categories = {} for f in os.listdir(self.getSourceDir()): t = source_type(f) if t: if t in list(categories.keys()): categories[t].append(f) else: categories[t] = [f] with open(makefilePath, "w+") as makefile: for t in list(categories.keys()): line = t + " := " + " ".join(categories[t]) makefile.write(line + "\n") if ("OBJCXX_SOURCES" in list(categories.keys())) or ( "OBJC_SOURCES" in list(categories.keys()) ): makefile.write("LDFLAGS = $(CFLAGS) -lobjc -framework Foundation\n") if "CXX_SOURCES" in list(categories.keys()): makefile.write("CXXFLAGS += -std=c++11\n") makefile.write("include Makefile.rules\n") def _test(self): self.BuildMakefile() self.build(dictionary=self._build_dict) self.do_test() def execute_user_command(self, __command): exec(__command, globals(), locals()) def _get_breakpoint_ids(self, thread): ids = set() for i in range(0, thread.GetStopReasonDataCount(), 2): ids.add(thread.GetStopReasonDataAtIndex(i)) self.assertGreater(len(ids), 0) return sorted(ids) def do_test(self): exe = self.getBuildArtifact("a.out") source_files = [f for f in os.listdir(self.getSourceDir()) if source_type(f)] target = self.dbg.CreateTarget(exe) parser = CommandParser() parser.parse_source_files(source_files) parser.set_breakpoints(target) process = target.LaunchSimple(None, None, self.get_process_working_directory()) self.assertIsNotNone(process, PROCESS_IS_VALID) hit_breakpoints = 0 while lldbutil.get_stopped_thread(process, lldb.eStopReasonBreakpoint): hit_breakpoints += 1 thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonBreakpoint) for bp_id in self._get_breakpoint_ids(thread): parser.handle_breakpoint(self, bp_id) process.Continue() self.assertTrue( hit_breakpoints > 0, "inline test did not hit a single breakpoint" ) # Either the process exited or the stepping plan is complete. self.assertTrue( process.GetState() in [lldb.eStateStopped, lldb.eStateExited], PROCESS_EXITED, ) def check_expression(self, expression, expected_result, use_summary=True): value = self.frame().EvaluateExpression(expression) self.assertTrue(value.IsValid(), expression + "returned a valid value") if self.TraceOn(): print(value.GetSummary()) print(value.GetValue()) if use_summary: answer = value.GetSummary() else: answer = value.GetValue() report_str = "%s expected: %s got: %s" % (expression, expected_result, answer) self.assertTrue(answer == expected_result, report_str) def ApplyDecoratorsToFunction(func, decorators): tmp = func if isinstance(decorators, list): for decorator in decorators: tmp = decorator(tmp) elif hasattr(decorators, "__call__"): tmp = decorators(tmp) return tmp def MakeInlineTest(__file, __globals, decorators=None, name=None, build_dict=None): # Adjust the filename if it ends in .pyc. We want filenames to # reflect the source python file, not the compiled variant. if __file is not None and __file.endswith(".pyc"): # Strip the trailing "c" __file = __file[0:-1] if name is None: # Derive the test name from the current file name file_basename = os.path.basename(__file) name, _ = os.path.splitext(file_basename) test_func = ApplyDecoratorsToFunction(InlineTest._test, decorators) # Build the test case test_class = type( name, (InlineTest,), dict(test=test_func, name=name, _build_dict=build_dict) ) # Add the test case to the globals, and hide InlineTest __globals.update({name: test_class}) # Keep track of the original test filename so we report it # correctly in test results. test_class.test_filename = __file test_class.mydir = TestBase.compute_mydir(__file) return test_class