1import os
2import sys
3
4
5class CheriTestMode(object):
6    INCLUDE = "include"
7    EXCLUDE = "exclude"
8    ONLY = "only"
9
10
11class TestingConfig(object):
12    """"
13    TestingConfig - Information on the tests inside a suite.
14    """
15
16    @staticmethod
17    def fromdefaults(litConfig):
18        """
19        fromdefaults(litConfig) -> TestingConfig
20
21        Create a TestingConfig object with default values.
22        """
23        import lit-cheri.LitConfig
24        assert isinstance(litConfig, lit-cheri.LitConfig.LitConfig)
25        # Set the environment based on the command line arguments.
26        environment = {
27            'PATH' : os.pathsep.join(litConfig.path +
28                                     [os.environ.get('PATH','')]),
29            'LLVM_DISABLE_CRASH_REPORT' : '1',
30            }
31
32        pass_vars = ['LIBRARY_PATH', 'LD_LIBRARY_PATH', 'SYSTEMROOT', 'TERM',
33                     'CLANG', 'LD_PRELOAD', 'ASAN_OPTIONS', 'UBSAN_OPTIONS',
34                     'LSAN_OPTIONS', 'ADB', 'ANDROID_SERIAL', 'SSH_AUTH_SOCK',
35                     'SANITIZER_IGNORE_CVE_2016_2143', 'TMPDIR', 'TMP', 'TEMP',
36                     'TEMPDIR', 'AVRLIT_BOARD', 'AVRLIT_PORT',
37                     'FILECHECK_OPTS', 'VCINSTALLDIR', 'VCToolsinstallDir',
38                     'VSINSTALLDIR', 'WindowsSdkDir', 'WindowsSDKLibVersion']
39
40        if sys.platform == 'win32':
41            pass_vars.append('INCLUDE')
42            pass_vars.append('LIB')
43            pass_vars.append('PATHEXT')
44            environment['PYTHONBUFFERED'] = '1'
45
46        for var in pass_vars:
47            val = os.environ.get(var, '')
48            # Check for empty string as some variables such as LD_PRELOAD cannot be empty
49            # ('') for OS's such as OpenBSD.
50            if val:
51                environment[var] = val
52
53        # Set the default available features based on the LitConfig.
54        available_features = []
55        if litConfig.cheri_test_mode == CheriTestMode.EXCLUDE:
56            litConfig.warning("Not running CHERI tests (is this intended?)")
57        elif litConfig.cheri_test_mode not in (CheriTestMode.INCLUDE, CheriTestMode.ONLY):
58            litConfig.fatal("Invalid value for litConfig.cheri_test_mode " +
59                            litConfig.cheri_test_mode)
60        if litConfig.useValgrind:
61            available_features.append('valgrind')
62            if litConfig.valgrindLeakCheck:
63                available_features.append('vg_leak')
64
65        return TestingConfig(None,
66                             name = '<unnamed>',
67                             suffixes = set(),
68                             test_format = None,
69                             environment = environment,
70                             substitutions = [],
71                             unsupported = False,
72                             test_exec_root = None,
73                             test_source_root = None,
74                             excludes = [],
75                             cheri_test_mode = litConfig.cheri_test_mode,
76                             available_features = available_features,
77                             pipefail = True)
78
79    def load_from_path(self, path, litConfig):
80        """
81        load_from_path(path, litConfig)
82
83        Load the configuration module at the provided path into the given config
84        object.
85        """
86
87        # Load the config script data.
88        data = None
89        f = open(path)
90        try:
91            data = f.read()
92        except:
93            litConfig.fatal('unable to load config file: %r' % (path,))
94        f.close()
95
96        # Execute the config script to initialize the object.
97        cfg_globals = dict(globals())
98        cfg_globals['config'] = self
99        cfg_globals['lit_config'] = litConfig
100        cfg_globals['__file__'] = path
101        try:
102            exec(compile(data, path, 'exec'), cfg_globals, None)
103            if litConfig.debug:
104                litConfig.note('... loaded config %r' % path)
105        except SystemExit:
106            e = sys.exc_info()[1]
107            # We allow normal system exit inside a config file to just
108            # return control without error.
109            if e.args:
110                raise
111        except:
112            import traceback
113            litConfig.fatal(
114                'unable to parse config file %r, traceback: %s' % (
115                    path, traceback.format_exc()))
116        self.finish(litConfig)
117
118    def __init__(self, parent, name, suffixes, test_format,
119                 environment, substitutions, unsupported,
120                 test_exec_root, test_source_root, excludes,
121                 available_features, pipefail, limit_to_features = [],
122                 cheri_test_mode = CheriTestMode.INCLUDE,
123                 is_early = False, parallelism_group = None):
124        self.parent = parent
125        self.name = str(name)
126        self.suffixes = set(suffixes)
127        self.test_format = test_format
128        self.environment = dict(environment)
129        self.substitutions = list(substitutions)
130        self.unsupported = unsupported
131        self.test_exec_root = test_exec_root
132        self.test_source_root = test_source_root
133        self.excludes = set(excludes)
134        self.cheri_test_mode = cheri_test_mode
135        self.available_features = set(available_features)
136        self.pipefail = pipefail
137        # This list is used by TestRunner.py to restrict running only tests that
138        # require one of the features in this list if this list is non-empty.
139        # Configurations can set this list to restrict the set of tests to run.
140        self.limit_to_features = set(limit_to_features)
141        # Whether the suite should be tested early in a given run.
142        self.is_early = bool(is_early)
143        self.parallelism_group = parallelism_group
144        self._recursiveExpansionLimit = None
145
146    @property
147    def recursiveExpansionLimit(self):
148        return self._recursiveExpansionLimit
149
150    @recursiveExpansionLimit.setter
151    def recursiveExpansionLimit(self, value):
152        if value is not None and not isinstance(value, int):
153            raise ValueError('recursiveExpansionLimit must be either None or an integer (got <{}>)'.format(value))
154        if isinstance(value, int) and value < 0:
155            raise ValueError('recursiveExpansionLimit must be a non-negative integer (got <{}>)'.format(value))
156        self._recursiveExpansionLimit = value
157
158    def finish(self, litConfig):
159        """finish() - Finish this config object, after loading is complete."""
160
161        self.name = str(self.name)
162        self.suffixes = set(self.suffixes)
163        self.environment = dict(self.environment)
164        self.substitutions = list(self.substitutions)
165        if self.test_exec_root is not None:
166            # FIXME: This should really only be suite in test suite config
167            # files. Should we distinguish them?
168            self.test_exec_root = str(self.test_exec_root)
169        if self.test_source_root is not None:
170            # FIXME: This should really only be suite in test suite config
171            # files. Should we distinguish them?
172            self.test_source_root = str(self.test_source_root)
173        self.excludes = set(self.excludes)
174
175    @property
176    def root(self):
177        """root attribute - The root configuration for the test suite."""
178        if self.parent is None:
179            return self
180        else:
181            return self.parent.root
182
183class SubstituteCaptures:
184    """
185    Helper class to indicate that the substitutions contains backreferences.
186
187    This can be used as the following in lit-cheri.cfg to mark subsitutions as having
188    back-references::
189
190        config.substutions.append(('\b[^ ]*.cpp', SubstituteCaptures('\0.txt')))
191
192    """
193    def __init__(self, substitution):
194        self.substitution = substitution
195
196    def replace(self, pattern, replacement):
197        return self.substitution
198
199    def __str__(self):
200        return self.substitution
201
202    def __len__(self):
203        return len(self.substitution)
204
205    def __getitem__(self, item):
206        return self.substitution.__getitem__(item)
207
208