• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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