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 do build/run tests.""" 8 9import abc 10from datetime import datetime 11import os 12import sys 13 14from dex.builder import add_builder_tool_arguments 15from dex.builder import handle_builder_tool_options 16from dex.debugger.Debuggers import add_debugger_tool_arguments 17from dex.debugger.Debuggers import handle_debugger_tool_options 18from dex.heuristic.Heuristic import add_heuristic_tool_arguments 19from dex.tools.ToolBase import ToolBase 20from dex.utils import get_root_directory, warn 21from dex.utils.Exceptions import Error, ToolArgumentError 22from dex.utils.ReturnCode import ReturnCode 23 24 25class TestToolBase(ToolBase): 26 def __init__(self, *args, **kwargs): 27 super(TestToolBase, self).__init__(*args, **kwargs) 28 self.build_script: str = None 29 30 def add_tool_arguments(self, parser, defaults): 31 parser.description = self.__doc__ 32 add_builder_tool_arguments(parser) 33 add_debugger_tool_arguments(parser, self.context, defaults) 34 add_heuristic_tool_arguments(parser) 35 36 parser.add_argument( 37 'test_path', 38 type=str, 39 metavar='<test-path>', 40 nargs='?', 41 default=os.path.abspath( 42 os.path.join(get_root_directory(), '..', 'tests')), 43 help='directory containing test(s)') 44 45 parser.add_argument( 46 '--results-directory', 47 type=str, 48 metavar='<directory>', 49 default=os.path.abspath( 50 os.path.join(get_root_directory(), '..', 'results', 51 datetime.now().strftime('%Y-%m-%d-%H%M-%S'))), 52 help='directory to save results') 53 54 def handle_options(self, defaults): 55 options = self.context.options 56 57 # We accept either or both of --binary and --builder. 58 if not options.binary and not options.builder: 59 raise Error('expected --builder or --binary') 60 61 # --binary overrides --builder 62 if options.binary: 63 if options.builder: 64 warn(self.context, "overriding --builder with --binary\n") 65 66 options.binary = os.path.abspath(options.binary) 67 if not os.path.isfile(options.binary): 68 raise Error('<d>could not find binary file</> <r>"{}"</>' 69 .format(options.binary)) 70 else: 71 try: 72 self.build_script = handle_builder_tool_options(self.context) 73 except ToolArgumentError as e: 74 raise Error(e) 75 76 try: 77 handle_debugger_tool_options(self.context, defaults) 78 except ToolArgumentError as e: 79 raise Error(e) 80 81 options.test_path = os.path.abspath(options.test_path) 82 options.test_path = os.path.normcase(options.test_path) 83 if not os.path.isfile(options.test_path) and not os.path.isdir(options.test_path): 84 raise Error( 85 '<d>could not find test path</> <r>"{}"</>'.format( 86 options.test_path)) 87 88 options.results_directory = os.path.abspath(options.results_directory) 89 if not os.path.isdir(options.results_directory): 90 try: 91 os.makedirs(options.results_directory, exist_ok=True) 92 except OSError as e: 93 raise Error( 94 '<d>could not create directory</> <r>"{}"</> <y>({})</>'. 95 format(options.results_directory, e.strerror)) 96 97 def go(self) -> ReturnCode: # noqa 98 options = self.context.options 99 100 options.executable = os.path.join( 101 self.context.working_directory.path, 'tmp.exe') 102 103 if os.path.isdir(options.test_path): 104 105 subdirs = sorted([ 106 r for r, _, f in os.walk(options.test_path) 107 if 'test.cfg' in f 108 ]) 109 110 for subdir in subdirs: 111 112 # TODO: read file extensions from the test.cfg file instead so 113 # that this isn't just limited to C and C++. 114 options.source_files = [ 115 os.path.normcase(os.path.join(subdir, f)) 116 for f in os.listdir(subdir) if any( 117 f.endswith(ext) for ext in ['.c', '.cpp']) 118 ] 119 120 self._run_test(self._get_test_name(subdir)) 121 else: 122 options.source_files = [options.test_path] 123 self._run_test(self._get_test_name(options.test_path)) 124 125 return self._handle_results() 126 127 @staticmethod 128 def _is_current_directory(test_directory): 129 return test_directory == '.' 130 131 def _get_test_name(self, test_path): 132 """Get the test name from either the test file, or the sub directory 133 path it's stored in. 134 """ 135 # test names are distinguished by their relative path from the 136 # specified test path. 137 test_name = os.path.relpath(test_path, 138 self.context.options.test_path) 139 if self._is_current_directory(test_name): 140 test_name = os.path.basename(test_path) 141 return test_name 142 143 @abc.abstractmethod 144 def _run_test(self, test_dir): 145 pass 146 147 @abc.abstractmethod 148 def _handle_results(self) -> ReturnCode: 149 pass 150