Skip to content
Snippets Groups Projects
Commit 3a094d8b authored by Jeremy Morse's avatar Jeremy Morse
Browse files

[Dexter] Allow tests to specify command line options

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
parent d17fb46e
No related branches found
No related tags found
No related merge requests found
Showing
with 120 additions and 15 deletions
......@@ -12,6 +12,7 @@
* [DexDeclareAddress](Commands.md#DexDeclareAddress)
* [DexDeclareFile](Commands.md#DexDeclareFile)
* [DexFinishTest](Commands.md#DexFinishTest)
* [DexCommandLine](Commands.md#DexCommandLine)
---
## DexExpectProgramState
......@@ -336,6 +337,25 @@ for the first 'hit_count' times the line and condition are hit.
### Heuristic
This command does not contribute to the heuristic score.
----
## DexCommandLine
DexCommandLine(command_line)
Args:
command_line (list): List of strings that form the command line.
### Description
Specifies the command line with which to launch the test. The arguments will
be appended to the default command line, i.e. the path to the compiled binary,
and will be passed to the program under test.
This command does not contribute to any part of the debug experience testing or
runtime instrumentation -- it's only for communicating arguments to the program
under test.
### Heuristic
This command does not contribute to the heuristic score.
---
## DexWatch
DexWatch(*expressions)
......
......@@ -18,6 +18,7 @@ from collections import defaultdict, OrderedDict
from dex.utils.Exceptions import CommandParseError
from dex.command.CommandBase import CommandBase
from dex.command.commands.DexCommandLine import DexCommandLine
from dex.command.commands.DexDeclareFile import DexDeclareFile
from dex.command.commands.DexDeclareAddress import DexDeclareAddress
from dex.command.commands.DexExpectProgramState import DexExpectProgramState
......@@ -41,6 +42,7 @@ def _get_valid_commands():
{ name (str): command (class) }
"""
return {
DexCommandLine.get_name() : DexCommandLine,
DexDeclareAddress.get_name() : DexDeclareAddress,
DexDeclareFile.get_name() : DexDeclareFile,
DexExpectProgramState.get_name() : DexExpectProgramState,
......@@ -322,6 +324,10 @@ def _find_all_commands_in_file(path, file_lines, valid_commands, source_root_dir
# TODO: keep stored paths as PurePaths for 'longer'.
cmd_path = str(PurePath(cmd_path))
declared_files.add(cmd_path)
elif type(command) is DexCommandLine and 'DexCommandLine' in commands:
msg = "More than one DexCommandLine in file"
raise format_parse_err(msg, path, file_lines, err_point)
assert (path, cmd_point) not in commands[command_name], (
command_name, commands[command_name])
commands[command_name][path, cmd_point] = command
......
# 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
"""A Command that specifies the command line with which to run the test.
"""
from dex.command.CommandBase import CommandBase
class DexCommandLine(CommandBase):
def __init__(self, the_cmdline):
if type(the_cmdline) is not list:
raise TypeError('Expected list, got {}'.format(type(the_cmdline)))
for x in the_cmdline:
if type(x) is not str:
raise TypeError('Command line element "{}" has type {}'.format(x, type(x)))
self.the_cmdline = the_cmdline
super(DexCommandLine, self).__init__()
def eval(self):
raise NotImplementedError('DexCommandLine commands cannot be evaled.')
@staticmethod
def get_name():
return __class__.__name__
@staticmethod
def get_subcommands() -> dict:
return None
......@@ -69,16 +69,15 @@ class BreakpointRange:
class ConditionalController(DebuggerControllerBase):
def __init__(self, context, step_collection):
self.context = context
self.step_collection = step_collection
self._bp_ranges = None
self._build_bp_ranges()
self._watches = set()
self._step_index = 0
self._pause_between_steps = context.options.pause_between_steps
self._max_steps = context.options.max_steps
# Map {id: BreakpointRange}
self._leading_bp_handles = {}
super(ConditionalController, self).__init__(context, step_collection)
self._build_bp_ranges()
def _build_bp_ranges(self):
commands = self.step_collection.commands
......@@ -126,7 +125,7 @@ class ConditionalController(DebuggerControllerBase):
id = self.debugger.add_breakpoint(bpr.path, bpr.range_from)
self._leading_bp_handles[id] = bpr
def _run_debugger_custom(self):
def _run_debugger_custom(self, cmdline):
# TODO: Add conditional and unconditional breakpoint support to dbgeng.
if self.debugger.get_name() == 'dbgeng':
raise DebuggerException('DexLimitSteps commands are not supported by dbgeng')
......@@ -137,7 +136,7 @@ class ConditionalController(DebuggerControllerBase):
for command_obj in chain.from_iterable(self.step_collection.commands.values()):
self._watches.update(command_obj.get_watches())
self.debugger.launch()
self.debugger.launch(cmdline)
time.sleep(self._pause_between_steps)
exit_desired = False
......
......@@ -9,6 +9,10 @@
import abc
class DebuggerControllerBase(object, metaclass=abc.ABCMeta):
def __init__(self, context, step_collection):
self.context = context
self.step_collection = step_collection
@abc.abstractclassmethod
def _run_debugger_custom(self):
"""Specify your own implementation of run_debugger_custom in your own
......@@ -20,9 +24,19 @@ class DebuggerControllerBase(object, metaclass=abc.ABCMeta):
"""Responsible for correctly launching and tearing down the debugger.
"""
self.debugger = debugger
# Fetch command line options, if any.
the_cmdline = []
commands = self.step_collection.commands
if 'DexCommandLine' in commands:
cmd_line_objs = commands['DexCommandLine']
assert len(cmd_line_objs) == 1
cmd_line_obj = cmd_line_objs[0]
the_cmdline = cmd_line_obj.the_cmdline
with self.debugger:
if not self.debugger.loading_error:
self._run_debugger_custom()
self._run_debugger_custom(the_cmdline)
# We may need to pickle this debugger controller after running the
# debugger. Debuggers are not picklable objects, so set to None.
......
......@@ -23,11 +23,10 @@ class EarlyExitCondition(object):
class DefaultController(DebuggerControllerBase):
def __init__(self, context, step_collection):
self.context = context
self.step_collection = step_collection
self.source_files = self.context.options.source_files
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:
......@@ -73,10 +72,10 @@ class DefaultController(DebuggerControllerBase):
return False
def _run_debugger_custom(self):
def _run_debugger_custom(self, cmdline):
self.step_collection.debugger = self.debugger.debugger_info
self._break_point_all_lines()
self.debugger.launch()
self.debugger.launch(cmdline)
for command_obj in chain.from_iterable(self.step_collection.commands.values()):
self.watches.update(command_obj.get_watches())
......
......@@ -95,7 +95,8 @@ class DbgEng(DebuggerBase):
# but is something that should be considered in the future.
raise NotImplementedError('delete_conditional_breakpoint is not yet implemented by dbgeng')
def launch(self):
def launch(self, cmdline):
assert len(cmdline) == 0, "Command lines unimplemented for dbgeng right now"
# We are, by this point, already launched.
self.step_info = probe_process.probe_state(self.client)
......
......@@ -169,8 +169,8 @@ class LLDB(DebuggerBase):
pass
self._target.BreakpointDelete(id)
def launch(self):
self._process = self._target.LaunchSimple(None, None, os.getcwd())
def launch(self, cmdline):
self._process = self._target.LaunchSimple(cmdline, None, os.getcwd())
if not self._process or self._process.GetNumThreads() == 0:
raise DebuggerException('could not launch process')
if self._process.GetNumThreads() != 1:
......
......@@ -229,7 +229,26 @@ class VisualStudio(DebuggerBase, metaclass=abc.ABCMeta): # pylint: disable=abst
bp.Delete()
break
def launch(self):
def _fetch_property(self, props, name):
num_props = props.Count
result = None
for x in range(1, num_props+1):
item = props.Item(x)
if item.Name == name:
return item
assert False, "Couldn't find property {}".format(name)
def launch(self, cmdline):
cmdline_str = ' '.join(cmdline)
# In a slightly baroque manner, lookup the VS project that runs when
# you click "run", and set its command line options to the desired
# command line options.
startup_proj_name = str(self._fetch_property(self._interface.Solution.Properties, 'StartupProject'))
project = self._fetch_property(self._interface.Solution, startup_proj_name)
ActiveConfiguration = self._fetch_property(project.Properties, 'ActiveConfiguration').Object
ActiveConfiguration.DebugSettings.CommandArguments = cmdline_str
self._fn_go()
def step(self):
......
// UNSUPPORTED: dbgeng
//
// RUN: %dexter_regression_test -- %s | FileCheck %s
// CHECK: command_line.c:
int main(int argc, const char **argv) {
if (argc == 4)
return 0; // DexLabel('retline')
return 1; // DexUnreachable()
}
// DexExpectWatchValue('argc', '4', on_line=ref('retline'))
// Three args will be appended to the 'default' argument.
// DexCommandLine(['a', 'b', 'c'])
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment