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