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,
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.params = dict(params)
42        self.bashPath = None
43
44        # Configuration files to look for when discovering test suites.
45        self.config_prefix = config_prefix or 'lit'
46        self.suffixes = ['cfg.py', 'cfg']
47        self.config_names = ['%s.%s' % (self.config_prefix,x) for x in self.suffixes]
48        self.site_config_names = ['%s.site.%s' % (self.config_prefix,x) for x in self.suffixes]
49        self.local_config_names = ['%s.local.%s' % (self.config_prefix,x) for x in self.suffixes]
50
51        self.numErrors = 0
52        self.numWarnings = 0
53
54        self.valgrindArgs = []
55        if self.useValgrind:
56            self.valgrindArgs = ['valgrind', '-q', '--run-libc-freeres=no',
57                                 '--tool=memcheck', '--trace-children=yes',
58                                 '--error-exitcode=123']
59            if self.valgrindLeakCheck:
60                self.valgrindArgs.append('--leak-check=full')
61            else:
62                # The default is 'summary'.
63                self.valgrindArgs.append('--leak-check=no')
64            self.valgrindArgs.extend(self.valgrindUserArgs)
65
66        self.maxIndividualTestTime = maxIndividualTestTime
67        self.parallelism_groups = parallelism_groups
68        self.echo_all_commands = echo_all_commands
69
70    @property
71    def maxIndividualTestTime(self):
72        """
73            Interface for getting maximum time to spend executing
74            a single test
75        """
76        return self._maxIndividualTestTime
77
78    @property
79    def maxIndividualTestTimeIsSupported(self):
80        """
81            Returns a tuple (<supported> , <error message>)
82            where
83            `<supported>` is True if setting maxIndividualTestTime is supported
84                on the current host, returns False otherwise.
85            `<error message>` is an empty string if `<supported>` is True,
86                otherwise is contains a string describing why setting
87                maxIndividualTestTime is not supported.
88        """
89        return lit.util.killProcessAndChildrenIsSupported()
90
91    @maxIndividualTestTime.setter
92    def maxIndividualTestTime(self, value):
93        """
94            Interface for setting maximum time to spend executing
95            a single test
96        """
97        if not isinstance(value, int):
98            self.fatal('maxIndividualTestTime must set to a value of type int.')
99        self._maxIndividualTestTime = value
100        if self.maxIndividualTestTime > 0:
101            # The current implementation needs psutil on some platforms to set
102            # a timeout per test. Check it's available.
103            # See lit.util.killProcessAndChildren()
104            supported, errormsg = self.maxIndividualTestTimeIsSupported
105            if not supported:
106                self.fatal('Setting a timeout per test not supported. ' +
107                           errormsg)
108        elif self.maxIndividualTestTime < 0:
109            self.fatal('The timeout per test must be >= 0 seconds')
110
111    def load_config(self, config, path):
112        """load_config(config, path) - Load a config object from an alternate
113        path."""
114        if self.debug:
115            self.note('load_config from %r' % path)
116        config.load_from_path(path, self)
117        return config
118
119    def getBashPath(self):
120        """getBashPath - Get the path to 'bash'"""
121        if self.bashPath is not None:
122            return self.bashPath
123
124        self.bashPath = lit.util.which('bash', os.pathsep.join(self.path))
125        if self.bashPath is None:
126            self.bashPath = lit.util.which('bash')
127
128        if self.bashPath is None:
129            self.bashPath = ''
130
131        # Check whether the found version of bash is able to cope with paths in
132        # the host path format. If not, don't return it as it can't be used to
133        # run scripts. For example, WSL's bash.exe requires '/mnt/c/foo' rather
134        # than 'C:\\foo' or 'C:/foo'.
135        if self.isWindows and self.bashPath:
136            command = [self.bashPath, '-c',
137                       '[[ -f "%s" ]]' % self.bashPath.replace('\\', '\\\\')]
138            _, _, exitCode = lit.util.executeCommand(command)
139            if exitCode:
140                self.note('bash command failed: %s' % (
141                    ' '.join('"%s"' % c for c in command)))
142                self.bashPath = ''
143
144        if not self.bashPath:
145            self.warning('Unable to find a usable version of bash.')
146
147        return self.bashPath
148
149    def getToolsPath(self, dir, paths, tools):
150        if dir is not None and os.path.isabs(dir) and os.path.isdir(dir):
151            if not lit.util.checkToolsPath(dir, tools):
152                return None
153        else:
154            dir = lit.util.whichTools(tools, paths)
155
156        # bash
157        self.bashPath = lit.util.which('bash', dir)
158        if self.bashPath is None:
159            self.bashPath = ''
160
161        return dir
162
163    def _write_message(self, kind, message):
164        # Get the file/line where this message was generated.
165        f = inspect.currentframe()
166        # Step out of _write_message, and then out of wrapper.
167        f = f.f_back.f_back
168        file = os.path.abspath(inspect.getsourcefile(f))
169        line = inspect.getlineno(f)
170        sys.stderr.write('%s: %s:%d: %s: %s\n' % (self.progname, file, line,
171                                                  kind, message))
172
173    def note(self, message):
174        if not self.quiet:
175            self._write_message('note', message)
176
177    def warning(self, message):
178        if not self.quiet:
179            self._write_message('warning', message)
180        self.numWarnings += 1
181
182    def error(self, message):
183        self._write_message('error', message)
184        self.numErrors += 1
185
186    def fatal(self, message):
187        self._write_message('fatal', message)
188        sys.exit(2)
189