mirror of
https://github.com/llvm/llvm-project.git
synced 2025-05-10 21:36:05 +00:00

Summary: This is split off from D47265 where I needed to be able to invoke every test with -f. That patch is kinda dead now, but this part seems like a good cleanup anyway. The problem with inline tests was in the way we were adding methods to the class, which left them with an incorrect __name__ property. This prevented dotest from finding them with -f. I fix this with (what I think is) the correct way of dynamically creating classes -- passing the list of methods during type construction instead of fixing up the class afterwards. Among other things this has the advantage of not needing to do anything special for debug info variants. As our test method will be visible to the metaclass, it will automagically do the multiplication for us. Reviewers: JDevlieghere, aprantl, tberghammer Subscribers: eraman, lldb-commits Differential Revision: https://reviews.llvm.org/D47579 llvm-svn: 334009
213 lines
7.0 KiB
Python
213 lines
7.0 KiB
Python
from __future__ import print_function
|
|
from __future__ import absolute_import
|
|
|
|
# System modules
|
|
import os
|
|
|
|
# Third-party modules
|
|
|
|
# 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].strip() # take off whitespace
|
|
new_breakpoint = parts[0].strip() != ""
|
|
|
|
return (command, new_breakpoint)
|
|
|
|
def parse_source_files(self, source_files):
|
|
for source_file in source_files:
|
|
file_handle = open(source_file)
|
|
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
|
|
|
|
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):
|
|
# Internal implementation
|
|
|
|
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]
|
|
|
|
makefile = open(makefilePath, 'w+')
|
|
|
|
level = os.sep.join(
|
|
[".."] * len(self.mydir.split(os.sep))) + os.sep + "make"
|
|
|
|
makefile.write("LEVEL = " + level + "\n")
|
|
|
|
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 $(LEVEL)/Makefile.rules\n")
|
|
makefile.write("\ncleanup:\n\trm -f Makefile *.d\n\n")
|
|
makefile.flush()
|
|
makefile.close()
|
|
|
|
def _test(self):
|
|
self.BuildMakefile()
|
|
self.build()
|
|
self.do_test()
|
|
|
|
def execute_user_command(self, __command):
|
|
exec(__command, globals(), locals())
|
|
|
|
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())
|
|
hit_breakpoints = 0
|
|
|
|
while lldbutil.get_stopped_thread(process, lldb.eStopReasonBreakpoint):
|
|
hit_breakpoints += 1
|
|
thread = lldbutil.get_stopped_thread(
|
|
process, lldb.eStopReasonBreakpoint)
|
|
breakpoint_id = thread.GetStopReasonDataAtIndex(0)
|
|
parser.handle_breakpoint(self, breakpoint_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)
|
|
|
|
# Utilities for testcases
|
|
|
|
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):
|
|
# 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]
|
|
|
|
# Derive the test name from the current file name
|
|
file_basename = os.path.basename(__file)
|
|
InlineTest.mydir = TestBase.compute_mydir(__file)
|
|
|
|
test_name, _ = os.path.splitext(file_basename)
|
|
|
|
test_func = ApplyDecoratorsToFunction(InlineTest._test, decorators)
|
|
# Build the test case
|
|
test_class = type(test_name, (InlineTest,), dict(test=test_func, name=test_name))
|
|
|
|
# Add the test case to the globals, and hide InlineTest
|
|
__globals.update({test_name: test_class})
|
|
|
|
# Keep track of the original test filename so we report it
|
|
# correctly in test results.
|
|
test_class.test_filename = __file
|
|
return test_class
|