This patch adds a "DexCommandLine" command, allowing dexter tests to specify what command line options the test should be started with. I've also plumbed it through into the debuggers. This eases the matter of pointing Dexter at larger tests, or controlling different paths through a single binary from a Dexter test. Differential Revision: https://reviews.llvm.org/D115330
110 lines
4.5 KiB
Python
110 lines
4.5 KiB
Python
# DExTer : Debugging Experience Tester
|
|
# ~~~~~~ ~ ~~ ~ ~~
|
|
#
|
|
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
# See https://llvm.org/LICENSE.txt for license information.
|
|
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
"""Default class for controlling debuggers."""
|
|
|
|
from itertools import chain
|
|
import os
|
|
import time
|
|
|
|
from dex.debugger.DebuggerControllers.DebuggerControllerBase import DebuggerControllerBase
|
|
from dex.debugger.DebuggerControllers.ControllerHelpers import in_source_file, update_step_watches
|
|
from dex.utils.Exceptions import DebuggerException, LoadDebuggerException
|
|
|
|
class EarlyExitCondition(object):
|
|
def __init__(self, on_line, hit_count, expression, values):
|
|
self.on_line = on_line
|
|
self.hit_count = hit_count
|
|
self.expression = expression
|
|
self.values = values
|
|
|
|
class DefaultController(DebuggerControllerBase):
|
|
def __init__(self, context, step_collection):
|
|
self.source_files = context.options.source_files
|
|
self.watches = set()
|
|
self.step_index = 0
|
|
super(DefaultController, self).__init__(context, step_collection)
|
|
|
|
def _break_point_all_lines(self):
|
|
for s in self.context.options.source_files:
|
|
with open(s, 'r') as fp:
|
|
num_lines = len(fp.readlines())
|
|
for line in range(1, num_lines + 1):
|
|
try:
|
|
self.debugger.add_breakpoint(s, line)
|
|
except DebuggerException:
|
|
raise LoadDebuggerException(DebuggerException.msg)
|
|
|
|
def _get_early_exit_conditions(self):
|
|
commands = self.step_collection.commands
|
|
early_exit_conditions = []
|
|
if 'DexFinishTest' in commands:
|
|
finish_commands = commands['DexFinishTest']
|
|
for fc in finish_commands:
|
|
condition = EarlyExitCondition(on_line=fc.on_line,
|
|
hit_count=fc.hit_count,
|
|
expression=fc.expression,
|
|
values=fc.values)
|
|
early_exit_conditions.append(condition)
|
|
return early_exit_conditions
|
|
|
|
def _should_exit(self, early_exit_conditions, line_no):
|
|
for condition in early_exit_conditions:
|
|
if condition.on_line == line_no:
|
|
exit_condition_hit = condition.expression is None
|
|
if condition.expression is not None:
|
|
# For the purposes of consistent behaviour with the
|
|
# Conditional Controller, check equality in the debugger
|
|
# rather than in python (as the two can differ).
|
|
for value in condition.values:
|
|
expr_val = self.debugger.evaluate_expression(f'({condition.expression}) == ({value})')
|
|
if expr_val.value == 'true':
|
|
exit_condition_hit = True
|
|
break
|
|
if exit_condition_hit:
|
|
if condition.hit_count <= 0:
|
|
return True
|
|
else:
|
|
condition.hit_count -= 1
|
|
return False
|
|
|
|
|
|
def _run_debugger_custom(self, cmdline):
|
|
self.step_collection.debugger = self.debugger.debugger_info
|
|
self._break_point_all_lines()
|
|
self.debugger.launch(cmdline)
|
|
|
|
for command_obj in chain.from_iterable(self.step_collection.commands.values()):
|
|
self.watches.update(command_obj.get_watches())
|
|
early_exit_conditions = self._get_early_exit_conditions()
|
|
|
|
max_steps = self.context.options.max_steps
|
|
for _ in range(max_steps):
|
|
while self.debugger.is_running:
|
|
pass
|
|
|
|
if self.debugger.is_finished:
|
|
break
|
|
|
|
self.step_index += 1
|
|
step_info = self.debugger.get_step_info(self.watches, self.step_index)
|
|
|
|
if step_info.current_frame:
|
|
update_step_watches(step_info, self.watches, self.step_collection.commands)
|
|
self.step_collection.new_step(self.context, step_info)
|
|
if self._should_exit(early_exit_conditions, step_info.current_frame.loc.lineno):
|
|
break
|
|
|
|
if in_source_file(self.source_files, step_info):
|
|
self.debugger.step()
|
|
else:
|
|
self.debugger.go()
|
|
|
|
time.sleep(self.context.options.pause_between_steps)
|
|
else:
|
|
raise DebuggerException(
|
|
'maximum number of steps reached ({})'.format(max_steps))
|