1from __future__ import absolute_import
2import inspect
3import os
4import sys
5
6import lit70.Test
7import lit70.formats
8import lit70.TestingConfig
9import lit70.util
10
11# LitConfig must be a new style class for properties to work
12class LitConfig(object):
13    """LitConfig - Configuration data for a 'lit' test runner instance, shared
14    across all tests.
15
16    The LitConfig object is also used to communicate with client configuration
17    files, it is always passed in as the global variable 'lit' so that
18    configuration files can access common functionality and internal components
19    easily.
20    """
21
22    def __init__(self, progname, path, quiet,
23                 useValgrind, valgrindLeakCheck, valgrindArgs,
24                 noExecute, debug, isWindows, singleProcess,
25                 params, config_prefix = None,
26                 maxIndividualTestTime = 0,
27                 maxFailures = None,
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.singleProcess = singleProcess
41        self.isWindows = bool(isWindows)
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.maxFailures = maxFailures
69        self.parallelism_groups = parallelism_groups
70        self.echo_all_commands = echo_all_commands
71
72    @property
73    def maxIndividualTestTime(self):
74        """
75            Interface for getting maximum time to spend executing
76            a single test
77        """
78        return self._maxIndividualTestTime
79
80    @maxIndividualTestTime.setter
81    def maxIndividualTestTime(self, value):
82        """
83            Interface for setting maximum time to spend executing
84            a single test
85        """
86        if not isinstance(value, int):
87            self.fatal('maxIndividualTestTime must set to a value of type int.')
88        self._maxIndividualTestTime = value
89        if self.maxIndividualTestTime > 0:
90            # The current implementation needs psutil to set
91            # a timeout per test. Check it's available.
92            # See lit70.util.killProcessAndChildren()
93            try:
94                import psutil  # noqa: F401
95            except ImportError:
96                self.fatal("Setting a timeout per test requires the"
97                           " Python psutil module but it could not be"
98                           " found. Try installing it via pip or via"
99                           " your operating system's package manager.")
100        elif self.maxIndividualTestTime < 0:
101            self.fatal('The timeout per test must be >= 0 seconds')
102
103    def load_config(self, config, path):
104        """load_config(config, path) - Load a config object from an alternate
105        path."""
106        if self.debug:
107            self.note('load_config from %r' % path)
108        config.load_from_path(path, self)
109        return config
110
111    def getBashPath(self):
112        """getBashPath - Get the path to 'bash'"""
113        if self.bashPath is not None:
114            return self.bashPath
115
116        self.bashPath = lit70.util.which('bash', os.pathsep.join(self.path))
117        if self.bashPath is None:
118            self.bashPath = lit70.util.which('bash')
119
120        if self.bashPath is None:
121            self.bashPath = ''
122
123        return self.bashPath
124
125    def getToolsPath(self, dir, paths, tools):
126        if dir is not None and os.path.isabs(dir) and os.path.isdir(dir):
127            if not lit70.util.checkToolsPath(dir, tools):
128                return None
129        else:
130            dir = lit70.util.whichTools(tools, paths)
131
132        # bash
133        self.bashPath = lit70.util.which('bash', dir)
134        if self.bashPath is None:
135            self.bashPath = ''
136
137        return dir
138
139    def _write_message(self, kind, message):
140        # Get the file/line where this message was generated.
141        f = inspect.currentframe()
142        # Step out of _write_message, and then out of wrapper.
143        f = f.f_back.f_back
144        file,line,_,_,_ = inspect.getframeinfo(f)
145        location = '%s:%d' % (file, line)
146
147        sys.stderr.write('%s: %s: %s: %s\n' % (self.progname, location,
148                                               kind, message))
149
150    def note(self, message):
151        self._write_message('note', message)
152
153    def warning(self, message):
154        self._write_message('warning', message)
155        self.numWarnings += 1
156
157    def error(self, message):
158        self._write_message('error', message)
159        self.numErrors += 1
160
161    def fatal(self, message):
162        self._write_message('fatal', message)
163        sys.exit(2)
164