This patch allows a visual studio solution file to be passed directly into Dexter, instead of using a pre-built binary and a small internal solution file with template arguments. This is primarily to allow launching an application that has specific launch configuration requirements, without needing all the details of this configuration to be built directly into Dexter or adding a config file that simply duplicates existing settings in the VS solution. Reviewed By: Orlando Differential Revision: https://reviews.llvm.org/D110167
163 lines
6.4 KiB
Python
163 lines
6.4 KiB
Python
# 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
|