xref: /llvm-project/cross-project-tests/debuginfo-tests/dexter/dex/tools/TestToolBase.py (revision 45a40c163932d12b72b33bd1d8a84519392b5d39)
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