xref: /openbsd/gnu/llvm/llvm/utils/lit/lit/LitConfig.py (revision d415bd75)
1from __future__ import absolute_import
2import inspect
3import os
4import platform
5import sys
6
7import lit.Test
8import lit.formats
9import lit.TestingConfig
10import lit.util
11
12# LitConfig must be a new style class for properties to work
13class LitConfig(object):
14    """LitConfig - Configuration data for a 'lit' test runner instance, shared
15    across all tests.
16
17    The LitConfig object is also used to communicate with client configuration
18    files, it is always passed in as the global variable 'lit' so that
19    configuration files can access common functionality and internal components
20    easily.
21    """
22
23    def __init__(self, progname, path, quiet,
24                 useValgrind, valgrindLeakCheck, valgrindArgs,
25                 noExecute, debug, isWindows, order,
26                 params, config_prefix = None,
27                 maxIndividualTestTime = 0,
28                 parallelism_groups = {},
29                 echo_all_commands = False):
30        # The name of the test runner.
31        self.progname = progname
32        # The items to add to the PATH environment variable.
33        self.path = [str(p) for p in path]
34        self.quiet = bool(quiet)
35        self.useValgrind = bool(useValgrind)
36        self.valgrindLeakCheck = bool(valgrindLeakCheck)
37        self.valgrindUserArgs = list(valgrindArgs)
38        self.noExecute = noExecute
39        self.debug = debug
40        self.isWindows = bool(isWindows)
41        self.order = order
42        self.params = dict(params)
43        self.bashPath = None
44
45        # Configuration files to look for when discovering test suites.
46        self.config_prefix = config_prefix or 'lit'
47        self.suffixes = ['cfg.py', 'cfg']
48        self.config_names = ['%s.%s' % (self.config_prefix,x) for x in self.suffixes]
49        self.site_config_names = ['%s.site.%s' % (self.config_prefix,x) for x in self.suffixes]
50        self.local_config_names = ['%s.local.%s' % (self.config_prefix,x) for x in self.suffixes]
51
52        self.numErrors = 0
53        self.numWarnings = 0
54
55        self.valgrindArgs = []
56        if self.useValgrind:
57            self.valgrindArgs = ['valgrind', '-q', '--run-libc-freeres=no',
58                                 '--tool=memcheck', '--trace-children=yes',
59                                 '--error-exitcode=123']
60            if self.valgrindLeakCheck:
61                self.valgrindArgs.append('--leak-check=full')
62            else:
63                # The default is 'summary'.
64                self.valgrindArgs.append('--leak-check=no')
65            self.valgrindArgs.extend(self.valgrindUserArgs)
66
67        self.maxIndividualTestTime = maxIndividualTestTime
68        self.parallelism_groups = parallelism_groups
69        self.echo_all_commands = echo_all_commands
70
71    @property
72    def maxIndividualTestTime(self):
73        """
74            Interface for getting maximum time to spend executing
75            a single test
76        """
77        return self._maxIndividualTestTime
78
79    @property
80    def maxIndividualTestTimeIsSupported(self):
81        """
82            Returns a tuple (<supported> , <error message>)
83            where
84            `<supported>` is True if setting maxIndividualTestTime is supported
85                on the current host, returns False otherwise.
86            `<error message>` is an empty string if `<supported>` is True,
87                otherwise is contains a string describing why setting
88                maxIndividualTestTime is not supported.
89        """
90        return lit.util.killProcessAndChildrenIsSupported()
91
92    @maxIndividualTestTime.setter
93    def maxIndividualTestTime(self, value):
94        """
95            Interface for setting maximum time to spend executing
96            a single test
97        """
98        if not isinstance(value, int):
99            self.fatal('maxIndividualTestTime must set to a value of type int.')
100        self._maxIndividualTestTime = value
101        if self.maxIndividualTestTime > 0:
102            # The current implementation needs psutil on some platforms to set
103            # a timeout per test. Check it's available.
104            # See lit.util.killProcessAndChildren()
105            supported, errormsg = self.maxIndividualTestTimeIsSupported
106            if not supported:
107                self.fatal('Setting a timeout per test not supported. ' +
108                           errormsg)
109        elif self.maxIndividualTestTime < 0:
110            self.fatal('The timeout per test must be >= 0 seconds')
111
112    def load_config(self, config, path):
113        """load_config(config, path) - Load a config object from an alternate
114        path."""
115        if self.debug:
116            self.note('load_config from %r' % path)
117        config.load_from_path(path, self)
118        return config
119
120    def getBashPath(self):
121        """getBashPath - Get the path to 'bash'"""
122        if self.bashPath is not None:
123            return self.bashPath
124
125        self.bashPath = lit.util.which('bash', os.pathsep.join(self.path))
126        if self.bashPath is None:
127            self.bashPath = lit.util.which('bash')
128
129        if self.bashPath is None:
130            self.bashPath = ''
131
132        # Check whether the found version of bash is able to cope with paths in
133        # the host path format. If not, don't return it as it can't be used to
134        # run scripts. For example, WSL's bash.exe requires '/mnt/c/foo' rather
135        # than 'C:\\foo' or 'C:/foo'.
136        if self.isWindows and self.bashPath:
137            command = [self.bashPath, '-c',
138                       '[[ -f "%s" ]]' % self.bashPath.replace('\\', '\\\\')]
139            _, _, exitCode = lit.util.executeCommand(command)
140            if exitCode:
141                self.note('bash command failed: %s' % (
142                    ' '.join('"%s"' % c for c in command)))
143                self.bashPath = ''
144
145        if not self.bashPath:
146            self.warning('Unable to find a usable version of bash.')
147
148        return self.bashPath
149
150    def getToolsPath(self, dir, paths, tools):
151        if dir is not None and os.path.isabs(dir) and os.path.isdir(dir):
152            if not lit.util.checkToolsPath(dir, tools):
153                return None
154        else:
155            dir = lit.util.whichTools(tools, paths)
156
157        # bash
158        self.bashPath = lit.util.which('bash', dir)
159        if self.bashPath is None:
160            self.bashPath = ''
161
162        return dir
163
164    def _write_message(self, kind, message):
165        # Get the file/line where this message was generated.
166        f = inspect.currentframe()
167        # Step out of _write_message, and then out of wrapper.
168        f = f.f_back.f_back
169        file = os.path.abspath(inspect.getsourcefile(f))
170        line = inspect.getlineno(f)
171        sys.stderr.write('%s: %s:%d: %s: %s\n' % (self.progname, file, line,
172                                                  kind, message))
173        if self.isWindows:
174            # In a git bash terminal, the writes to sys.stderr aren't visible
175            # on screen immediately. Flush them here to avoid broken/misoredered
176            # output.
177            sys.stderr.flush()
178
179    def substitute(self, string):
180        """substitute - Interpolate params into a string"""
181        try:
182          return string % self.params
183        except KeyError as e:
184          key, = e.args
185          self.fatal("unable to find %r parameter, use '--param=%s=VALUE'" % (
186              key,key))
187
188    def note(self, message):
189        if not self.quiet:
190            self._write_message('note', message)
191
192    def warning(self, message):
193        if not self.quiet:
194            self._write_message('warning', message)
195        self.numWarnings += 1
196
197    def error(self, message):
198        self._write_message('error', message)
199        self.numErrors += 1
200
201    def fatal(self, message):
202        self._write_message('fatal', message)
203        sys.exit(2)
204