Index: debuginfo-tests/dexter/Commands.md =================================================================== --- debuginfo-tests/dexter/Commands.md +++ debuginfo-tests/dexter/Commands.md @@ -7,6 +7,7 @@ * [DexExpectWatchValue](Commands.md#DexExpectWatchValue) * [DexUnreachable](Commands.md#DexUnreachable) * [DexWatch](Commands.md#DexWatch) +* [DexDeclareFile](Commands.md#DexDeclareFile) --- ## DexExpectProgramState @@ -216,6 +217,23 @@ ### Heuristic This command does not contribute to the heuristic score. +---- +## DexDeclareFile + DexDeclareFile(declared_file) + + Args: + name (str): A declared file path for which all subsequent commands + will have their path attribute set too. + +### Description +Changes the path attribute of all commands from this point in the test onwards. +The new path holds until the end of the test file or until a new DexDeclareFile +command is encountered. Used in conjunction with .dex files, DexDeclareFile can +be used to write your dexter commands in a separate test file avoiding inlined +Dexter commands mixed with test source. + +### Heuristic +This command does not contribute to the heuristic score. --- ## DexWatch Index: debuginfo-tests/dexter/dex/command/ParseCommand.py =================================================================== --- debuginfo-tests/dexter/dex/command/ParseCommand.py +++ debuginfo-tests/dexter/dex/command/ParseCommand.py @@ -18,6 +18,7 @@ from dex.utils.Exceptions import CommandParseError from dex.command.CommandBase import CommandBase +from dex.command.commands.DexDeclareFile import DexDeclareFile from dex.command.commands.DexExpectProgramState import DexExpectProgramState from dex.command.commands.DexExpectStepKind import DexExpectStepKind from dex.command.commands.DexExpectStepOrder import DexExpectStepOrder @@ -37,6 +38,7 @@ { name (str): command (class) } """ return { + DexDeclareFile.get_name() : DexDeclareFile, DexExpectProgramState.get_name() : DexExpectProgramState, DexExpectStepKind.get_name() : DexExpectStepKind, DexExpectStepOrder.get_name() : DexExpectStepOrder, @@ -194,6 +196,8 @@ def _find_all_commands_in_file(path, file_lines, valid_commands): + cmd_path = path + declared_files = set() commands = defaultdict(dict) paren_balance = 0 region_start = TextPoint(0, 0) @@ -236,7 +240,7 @@ command = _build_command( valid_commands[command_name], raw_text, - path, + cmd_path, cmd_point.get_lineno(), ) except SyntaxError as e: @@ -252,6 +256,12 @@ err_point.char += len(command_name) raise format_parse_err(str(e), path, file_lines, err_point) else: + if type(command) is DexDeclareFile: + cmd_path = command.declared_file + if not os.path.isabs(cmd_path): + source_dir = os.path.dirname(path) + cmd_path = os.path.join(source_dir, cmd_path) + declared_files.add(cmd_path) resolve_labels(command, commands) assert (path, cmd_point) not in commands[command_name], ( command_name, commands[command_name]) @@ -263,32 +273,34 @@ err_point.char += len(command_name) msg = "Unbalanced parenthesis starting here" raise format_parse_err(msg, path, file_lines, err_point) - return dict(commands) + return dict(commands), declared_files -def _find_all_commands(source_files): +def _find_all_commands(test_files): commands = defaultdict(dict) valid_commands = _get_valid_commands() - for source_file in source_files: - with open(source_file) as fp: + new_source_files = set() + for test_file in test_files: + with open(test_file) as fp: lines = fp.readlines() - file_commands = _find_all_commands_in_file(source_file, lines, - valid_commands) + file_commands, declared_files = _find_all_commands_in_file(test_file, + lines, valid_commands) for command_name in file_commands: commands[command_name].update(file_commands[command_name]) + new_source_files |= declared_files - return dict(commands) + return dict(commands), new_source_files -def get_command_infos(source_files): +def get_command_infos(test_files): with Timer('parsing commands'): try: - commands = _find_all_commands(source_files) + commands, new_source_files = _find_all_commands(test_files) command_infos = OrderedDict() for command_type in commands: for command in commands[command_type].values(): if command_type not in command_infos: command_infos[command_type] = [] command_infos[command_type].append(command) - return OrderedDict(command_infos) + return OrderedDict(command_infos), new_source_files except CommandParseError as e: msg = 'parser error: {}({}): {}\n{}\n{}\n'.format( e.filename, e.lineno, e.info, e.src, e.caret) @@ -326,7 +338,8 @@ Returns: { cmd_name: { (path, line): command_obj } } """ - return _find_all_commands_in_file(__file__, lines, self.valid_commands) + cmds, declared_files = _find_all_commands_in_file(__file__, lines, self.valid_commands) + return cmds def _find_all_mock_values_in_lines(self, lines): Index: debuginfo-tests/dexter/dex/command/commands/DexDeclareFile.py =================================================================== --- /dev/null +++ debuginfo-tests/dexter/dex/command/commands/DexDeclareFile.py @@ -0,0 +1,29 @@ +# 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 +"""Commmand sets the path for all following commands to 'declared_file'. +""" + +from pathlib import PurePath + +from dex.command.CommandBase import CommandBase + + +class DexDeclareFile(CommandBase): + def __init__(self, declared_file): + + if not isinstance(declared_file, str): + raise TypeError('invalid argument type') + + self.declared_file = PurePath(declared_file) + super(DexDeclareFile, self).__init__() + + @staticmethod + def get_name(): + return __class__.__name__ + + def eval(self): + return self.declared_file Index: debuginfo-tests/dexter/dex/tools/TestToolBase.py =================================================================== --- debuginfo-tests/dexter/dex/tools/TestToolBase.py +++ debuginfo-tests/dexter/dex/tools/TestToolBase.py @@ -100,26 +100,38 @@ options.executable = os.path.join( self.context.working_directory.path, 'tmp.exe') + # test files contain dexter commands. + options.test_files = [] + # source files are to be compiled by the builder script and may also + # contains dexter commands. + options.source_files = [] if os.path.isdir(options.test_path): - subdirs = sorted([ r for r, _, f in os.walk(options.test_path) if 'test.cfg' in f ]) for subdir in subdirs: - - # TODO: read file extensions from the test.cfg file instead so - # that this isn't just limited to C and C++. - options.source_files = [ - os.path.normcase(os.path.join(subdir, f)) - for f in os.listdir(subdir) if any( - f.endswith(ext) for ext in ['.c', '.cpp']) - ] + for f in os.listdir(subdir): + # TODO: read file extensions from the test.cfg file instead so + # that this isn't just limited to C and C++. + file_path = os.path.normcase(os.path.join(subdir, f)) + if f.endswith('.cpp'): + options.source_files.append(file_path) + elif f.endswith('.c'): + options.source_files.append(file_path) + elif f.endswith('.dex'): + options.test_files.append(file_path) + # source files can contain dexter commands too. + options.test_files = options.test_files + options.source_files self._run_test(self._get_test_name(subdir)) else: - options.source_files = [options.test_path] + # We're dealing with a direct file path to a test file. If the file is non + # .dex, then it must be a source file. + if not options.test_path.endswith('.dex'): + options.source_files = [options.test_path] + options.test_files = [options.test_path] self._run_test(self._get_test_name(options.test_path)) return self._handle_results() Index: debuginfo-tests/dexter/dex/tools/test/Tool.py =================================================================== --- debuginfo-tests/dexter/dex/tools/test/Tool.py +++ debuginfo-tests/dexter/dex/tools/test/Tool.py @@ -138,8 +138,11 @@ source_paths=self.context.options.source_files, dexter_version=self.context.version) - step_collection.commands = get_command_infos( - self.context.options.source_files) + step_collection.commands, new_source_files = get_command_infos( + self.context.options.test_files) + + for new_source_file in new_source_files: + self.context.options.source_files.append(new_source_file) if 'DexLimitSteps' in step_collection.commands: debugger_controller = ConditionalController(self.context, step_collection) Index: debuginfo-tests/dexter/feature_tests/commands/penalty/dex_declare_file.cpp =================================================================== --- /dev/null +++ debuginfo-tests/dexter/feature_tests/commands/penalty/dex_declare_file.cpp @@ -0,0 +1,17 @@ +// Purpose: +// Check that \DexDeclareFile causes a DexExpectWatchValue's to generate a +// missing value penalty when the declared path is incorrect. +// +// UNSUPPORTED: system-darwin +// +// +// RUN: not %dexter_regression_test -- %s | FileCheck %s +// CHECK: dex_declare_file.cpp + +int main() { + int result = 0; + return result; //DexLabel('return') +} + +// DexDeclareFile('not_dex_declare_file.cpp') +// DexExpectWatchValue('result', 0, on_line='return') Index: debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_file/dex_and_source/commands.dex =================================================================== --- /dev/null +++ debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_file/dex_and_source/commands.dex @@ -0,0 +1,2 @@ +DexDeclareFile('test.cpp') +DexExpectWatchValue('result', 0, on_line=14) Index: debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_file/dex_and_source/lit.local.cfg.py =================================================================== --- /dev/null +++ debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_file/dex_and_source/lit.local.cfg.py @@ -0,0 +1 @@ +config.suffixes = ['.cpp'] Index: debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_file/dex_and_source/test.cpp =================================================================== --- /dev/null +++ debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_file/dex_and_source/test.cpp @@ -0,0 +1,15 @@ +// Purpose: +// Check that \DexDeclareFile changes the path of all succeeding commands +// to the file path it declares. Also check that dexter correctly accepts +// files with .dex extensions. +// +// UNSUPPORTED: system-darwin +// +// +// RUN: %dexter_regression_test -- %S | FileCheck %s +// CHECK: dex_and_source + +int main() { + int result = 0; + return result; +} Index: debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_file/precompiled_binary/commands.dex =================================================================== --- /dev/null +++ debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_file/precompiled_binary/commands.dex @@ -0,0 +1,18 @@ +# Purpose: +# Check that \DexDeclareFile's file declaration can reference source files +# in a precompiled binary. +# +# UNSUPPORTED: system-darwin +# +# RUN: %clang %S/test.cpp -O0 -g -o %t +# RUN: %dexter_regression_test --binary %t %s | FileCheck %s +# CHECK: commands.dex +# +# test.cpp +# 1. int main() { +# 2. int result = 0; +# 3. return result; +# 4. } + +DexDeclareFile('test.cpp') +DexExpectWatchValue('result', 0, on_line=3) Index: debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_file/precompiled_binary/lit.local.cfg.py =================================================================== --- /dev/null +++ debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_file/precompiled_binary/lit.local.cfg.py @@ -0,0 +1 @@ +config.suffixes = ['.dex'] Index: debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_file/precompiled_binary/test.cpp =================================================================== --- /dev/null +++ debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_file/precompiled_binary/test.cpp @@ -0,0 +1,4 @@ +int main() { + int result = 0; + return result; +} Index: debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_file/precompiled_binary_different_dir/dex_commands/commands.dex =================================================================== --- /dev/null +++ debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_file/precompiled_binary_different_dir/dex_commands/commands.dex @@ -0,0 +1,19 @@ +# Purpose: +# Check that \DexDeclareFile's file declaration can reference source files +# not included in the test directory +# +# UNSUPPORTED: system-darwin +# +# RUN: %clang %S/../source/test.cpp -O0 -g -o %t +# RUN: %dexter_regression_test --binary %t %s | FileCheck %s +# RUN: rm %t +# CHECK: commands.dex +# +# test.cpp +# 1. int main() { +# 2. int result = 0; +# 3. return result; +# 4. } + +DexDeclareFile('../source/test.cpp') +DexExpectWatchValue('result', 0, on_line=3) Index: debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_file/precompiled_binary_different_dir/lit.local.cfg.py =================================================================== --- /dev/null +++ debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_file/precompiled_binary_different_dir/lit.local.cfg.py @@ -0,0 +1 @@ +config.suffixes = ['.dex'] Index: debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_file/precompiled_binary_different_dir/source/test.cpp =================================================================== --- /dev/null +++ debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_file/precompiled_binary_different_dir/source/test.cpp @@ -0,0 +1,4 @@ +int main() { + int result = 0; + return result; +}