154 lines · python
1# DExTer : Debugging Experience Tester2# ~~~~~~ ~ ~~ ~ ~~3#4# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.5# See https://llvm.org/LICENSE.txt for license information.6# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception7"""Base class for subtools that run tests."""8 9import abc10from datetime import datetime11import os12import sys13 14from dex.debugger.Debuggers import add_debugger_tool_arguments15from dex.debugger.Debuggers import handle_debugger_tool_options16from dex.heuristic.Heuristic import add_heuristic_tool_arguments17from dex.tools.ToolBase import ToolBase18from dex.utils import get_root_directory19from dex.utils.Exceptions import Error, ToolArgumentError20from dex.utils.ReturnCode import ReturnCode21 22 23def add_executable_arguments(parser):24 executable_group = parser.add_mutually_exclusive_group(required=True)25 executable_group.add_argument(26 "--binary", metavar="<file>", help="provide binary file to debug"27 )28 executable_group.add_argument(29 "--vs-solution",30 metavar="<file>",31 help="provide a path to an already existing visual studio solution.",32 )33 34 35class TestToolBase(ToolBase):36 def __init__(self, *args, **kwargs):37 super(TestToolBase, self).__init__(*args, **kwargs)38 39 def add_tool_arguments(self, parser, defaults):40 parser.description = self.__doc__41 add_debugger_tool_arguments(parser, self.context, defaults)42 add_executable_arguments(parser)43 add_heuristic_tool_arguments(parser)44 45 parser.add_argument(46 "test_path",47 type=str,48 metavar="<test-path>",49 nargs="?",50 default=os.path.abspath(os.path.join(get_root_directory(), "..", "tests")),51 help="directory containing test(s)",52 )53 54 parser.add_argument(55 "--results-directory",56 type=str,57 metavar="<directory>",58 default=None,59 help="directory to save results (default: none)",60 )61 parser.add_argument(62 "--test-root-dir",63 type=str,64 metavar="<directory>",65 default=None,66 help="if passed, result names will include relative path from this directory",67 )68 69 def handle_options(self, defaults):70 options = self.context.options71 72 if options.vs_solution:73 options.vs_solution = os.path.abspath(options.vs_solution)74 if not os.path.isfile(options.vs_solution):75 raise Error(76 '<d>could not find VS solution file</> <r>"{}"</>'.format(77 options.vs_solution78 )79 )80 elif options.binary:81 options.binary = os.path.abspath(options.binary)82 if not os.path.isfile(options.binary):83 raise Error(84 '<d>could not find binary file</> <r>"{}"</>'.format(options.binary)85 )86 87 try:88 handle_debugger_tool_options(self.context, defaults)89 except ToolArgumentError as e:90 raise Error(e)91 92 options.test_path = os.path.abspath(options.test_path)93 options.test_path = os.path.normcase(options.test_path)94 if not os.path.isfile(options.test_path) and not os.path.isdir(95 options.test_path96 ):97 raise Error(98 '<d>could not find test path</> <r>"{}"</>'.format(options.test_path)99 )100 101 if options.results_directory:102 options.results_directory = os.path.abspath(options.results_directory)103 if not os.path.isdir(options.results_directory):104 try:105 os.makedirs(options.results_directory, exist_ok=True)106 except OSError as e:107 raise Error(108 '<d>could not create directory</> <r>"{}"</> <y>({})</>'.format(109 options.results_directory, e.strerror110 )111 )112 113 def go(self) -> ReturnCode: # noqa114 options = self.context.options115 116 options.executable = os.path.join(117 self.context.working_directory.path, "tmp.exe"118 )119 120 # Test files contain dexter commands.121 options.test_files = [options.test_path]122 # Source files are the files that the program was built from, and are123 # used to determine whether a breakpoint is external to the program124 # (e.g. into a system header) or not.125 options.source_files = []126 if not options.test_path.endswith(".dex"):127 options.source_files = [options.test_path]128 self._run_test(self._get_test_name(options.test_path))129 130 return self._handle_results()131 132 @staticmethod133 def _is_current_directory(test_directory):134 return test_directory == "."135 136 def _get_test_name(self, test_path):137 """Get the test name from either the test file, or the sub directory138 path it's stored in.139 """140 # Test names are either relative to an explicitly given test root directory, or else we just use the base name.141 if self.context.options.test_root_dir is not None:142 test_name = os.path.relpath(test_path, self.context.options.test_root_dir)143 else:144 test_name = os.path.basename(test_path)145 return test_name146 147 @abc.abstractmethod148 def _run_test(self, test_dir):149 pass150 151 @abc.abstractmethod152 def _handle_results(self) -> ReturnCode:153 pass154