| # 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 |
| """Base class for subtools that do build/run tests.""" |
| |
| import abc |
| from datetime import datetime |
| import os |
| import sys |
| |
| from dex.builder import add_builder_tool_arguments |
| from dex.builder import handle_builder_tool_options |
| from dex.debugger.Debuggers import add_debugger_tool_arguments |
| from dex.debugger.Debuggers import handle_debugger_tool_options |
| from dex.heuristic.Heuristic import add_heuristic_tool_arguments |
| from dex.tools.ToolBase import ToolBase |
| from dex.utils import get_root_directory, warn |
| from dex.utils.Exceptions import Error, ToolArgumentError |
| from dex.utils.ReturnCode import ReturnCode |
| |
| |
| class TestToolBase(ToolBase): |
| def __init__(self, *args, **kwargs): |
| super(TestToolBase, self).__init__(*args, **kwargs) |
| self.build_script: str = None |
| |
| def add_tool_arguments(self, parser, defaults): |
| parser.description = self.__doc__ |
| add_builder_tool_arguments(parser) |
| add_debugger_tool_arguments(parser, self.context, defaults) |
| add_heuristic_tool_arguments(parser) |
| |
| parser.add_argument( |
| 'test_path', |
| type=str, |
| metavar='<test-path>', |
| nargs='?', |
| default=os.path.abspath( |
| os.path.join(get_root_directory(), '..', 'tests')), |
| help='directory containing test(s)') |
| |
| parser.add_argument( |
| '--results-directory', |
| type=str, |
| metavar='<directory>', |
| default=os.path.abspath( |
| os.path.join(get_root_directory(), '..', 'results', |
| datetime.now().strftime('%Y-%m-%d-%H%M-%S'))), |
| help='directory to save results') |
| |
| def handle_options(self, defaults): |
| options = self.context.options |
| |
| if not options.builder and (options.cflags or options.ldflags): |
| warn(self.context, '--cflags and --ldflags will be ignored when not' |
| ' using --builder') |
| |
| if options.vs_solution: |
| options.vs_solution = os.path.abspath(options.vs_solution) |
| if not os.path.isfile(options.vs_solution): |
| raise Error('<d>could not find VS solution file</> <r>"{}"</>' |
| .format(options.vs_solution)) |
| elif options.binary: |
| options.binary = os.path.abspath(options.binary) |
| if not os.path.isfile(options.binary): |
| raise Error('<d>could not find binary file</> <r>"{}"</>' |
| .format(options.binary)) |
| else: |
| try: |
| self.build_script = handle_builder_tool_options(self.context) |
| except ToolArgumentError as e: |
| raise Error(e) |
| |
| try: |
| handle_debugger_tool_options(self.context, defaults) |
| except ToolArgumentError as e: |
| raise Error(e) |
| |
| options.test_path = os.path.abspath(options.test_path) |
| options.test_path = os.path.normcase(options.test_path) |
| if not os.path.isfile(options.test_path) and not os.path.isdir(options.test_path): |
| raise Error( |
| '<d>could not find test path</> <r>"{}"</>'.format( |
| options.test_path)) |
| |
| options.results_directory = os.path.abspath(options.results_directory) |
| if not os.path.isdir(options.results_directory): |
| try: |
| os.makedirs(options.results_directory, exist_ok=True) |
| except OSError as e: |
| raise Error( |
| '<d>could not create directory</> <r>"{}"</> <y>({})</>'. |
| format(options.results_directory, e.strerror)) |
| |
| def go(self) -> ReturnCode: # noqa |
| options = self.context.options |
| |
| 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: |
| 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: |
| # 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() |
| |
| @staticmethod |
| def _is_current_directory(test_directory): |
| return test_directory == '.' |
| |
| def _get_test_name(self, test_path): |
| """Get the test name from either the test file, or the sub directory |
| path it's stored in. |
| """ |
| # test names are distinguished by their relative path from the |
| # specified test path. |
| test_name = os.path.relpath(test_path, |
| self.context.options.test_path) |
| if self._is_current_directory(test_name): |
| test_name = os.path.basename(test_path) |
| return test_name |
| |
| @abc.abstractmethod |
| def _run_test(self, test_dir): |
| pass |
| |
| @abc.abstractmethod |
| def _handle_results(self) -> ReturnCode: |
| pass |