1# DExTer : Debugging Experience Tester 2# ~~~~~~ ~ ~~ ~ ~~ 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-exception 7"""Base class for subtools that run tests.""" 8 9import abc 10from datetime import datetime 11import os 12import sys 13 14from dex.debugger.Debuggers import add_debugger_tool_arguments 15from dex.debugger.Debuggers import handle_debugger_tool_options 16from dex.heuristic.Heuristic import add_heuristic_tool_arguments 17from dex.tools.ToolBase import ToolBase 18from dex.utils import get_root_directory 19from dex.utils.Exceptions import Error, ToolArgumentError 20from dex.utils.ReturnCode import ReturnCode 21 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 62 def handle_options(self, defaults): 63 options = self.context.options 64 65 if options.vs_solution: 66 options.vs_solution = os.path.abspath(options.vs_solution) 67 if not os.path.isfile(options.vs_solution): 68 raise Error( 69 '<d>could not find VS solution file</> <r>"{}"</>'.format( 70 options.vs_solution 71 ) 72 ) 73 elif options.binary: 74 options.binary = os.path.abspath(options.binary) 75 if not os.path.isfile(options.binary): 76 raise Error( 77 '<d>could not find binary file</> <r>"{}"</>'.format(options.binary) 78 ) 79 80 try: 81 handle_debugger_tool_options(self.context, defaults) 82 except ToolArgumentError as e: 83 raise Error(e) 84 85 options.test_path = os.path.abspath(options.test_path) 86 options.test_path = os.path.normcase(options.test_path) 87 if not os.path.isfile(options.test_path) and not os.path.isdir( 88 options.test_path 89 ): 90 raise Error( 91 '<d>could not find test path</> <r>"{}"</>'.format(options.test_path) 92 ) 93 94 if options.results_directory: 95 options.results_directory = os.path.abspath(options.results_directory) 96 if not os.path.isdir(options.results_directory): 97 try: 98 os.makedirs(options.results_directory, exist_ok=True) 99 except OSError as e: 100 raise Error( 101 '<d>could not create directory</> <r>"{}"</> <y>({})</>'.format( 102 options.results_directory, e.strerror 103 ) 104 ) 105 106 def go(self) -> ReturnCode: # noqa 107 options = self.context.options 108 109 options.executable = os.path.join( 110 self.context.working_directory.path, "tmp.exe" 111 ) 112 113 # Test files contain dexter commands. 114 options.test_files = [options.test_path] 115 # Source files are the files that the program was built from, and are 116 # used to determine whether a breakpoint is external to the program 117 # (e.g. into a system header) or not. 118 options.source_files = [] 119 if not options.test_path.endswith(".dex"): 120 options.source_files = [options.test_path] 121 self._run_test(self._get_test_name(options.test_path)) 122 123 return self._handle_results() 124 125 @staticmethod 126 def _is_current_directory(test_directory): 127 return test_directory == "." 128 129 def _get_test_name(self, test_path): 130 """Get the test name from either the test file, or the sub directory 131 path it's stored in. 132 """ 133 # test names are distinguished by their relative path from the 134 # specified test path. 135 test_name = os.path.relpath(test_path, self.context.options.test_path) 136 if self._is_current_directory(test_name): 137 test_name = os.path.basename(test_path) 138 return test_name 139 140 @abc.abstractmethod 141 def _run_test(self, test_dir): 142 pass 143 144 @abc.abstractmethod 145 def _handle_results(self) -> ReturnCode: 146 pass 147