xref: /netbsd-src/external/apache2/llvm/dist/libcxx/utils/libcxx/test/dsl.py (revision 4d6fc14bc9b0c5bf3e30be318c143ee82cadd108)
1#===----------------------------------------------------------------------===##
2#
3# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4# See https://llvm.org/LICENSE.txt for license information.
5# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6#
7#===----------------------------------------------------------------------===##
8
9import os
10import pickle
11import pipes
12import platform
13import re
14import tempfile
15
16import libcxx.test.format
17import lit
18import lit.LitConfig
19import lit.Test
20import lit.TestRunner
21import lit.util
22
23
24def _memoizeExpensiveOperation(extractCacheKey):
25  """
26  Allows memoizing a very expensive operation.
27
28  We pickle the cache key to make sure we store an immutable representation
29  of it. If we stored an object and the object was referenced elsewhere, it
30  could be changed from under our feet, which would break the cache.
31  """
32  def decorator(function):
33    cache = {}
34    def f(*args, **kwargs):
35      cacheKey = pickle.dumps(extractCacheKey(*args, **kwargs))
36      if cacheKey not in cache:
37        cache[cacheKey] = function(*args, **kwargs)
38      return cache[cacheKey]
39    return f
40  return decorator
41
42def _executeScriptInternal(test, commands):
43  """
44  Returns (stdout, stderr, exitCode, timeoutInfo)
45
46  TODO: This really should be easier to access from Lit itself
47  """
48  parsedCommands = libcxx.test.format.parseScript(test, preamble=commands)
49
50  litConfig = lit.LitConfig.LitConfig(
51    progname='lit',
52    path=[],
53    quiet=False,
54    useValgrind=False,
55    valgrindLeakCheck=False,
56    valgrindArgs=[],
57    noExecute=False,
58    debug=False,
59    isWindows=platform.system() == 'Windows',
60    params={})
61  _, tmpBase = libcxx.test.format._getTempPaths(test)
62  execDir = os.path.dirname(test.getExecPath())
63  for d in (execDir, os.path.dirname(tmpBase)):
64    if not os.path.exists(d):
65      os.makedirs(d)
66  res = lit.TestRunner.executeScriptInternal(test, litConfig, tmpBase, parsedCommands, execDir)
67  if isinstance(res, lit.Test.Result):
68    res = ('', '', 127, None)
69  return res
70
71def _makeConfigTest(config, testPrefix=''):
72  sourceRoot = os.path.join(config.test_exec_root, '__config_src__')
73  execRoot = os.path.join(config.test_exec_root, '__config_exec__')
74  suite = lit.Test.TestSuite('__config__', sourceRoot, execRoot, config)
75  if not os.path.exists(sourceRoot):
76    os.makedirs(sourceRoot)
77  tmp = tempfile.NamedTemporaryFile(dir=sourceRoot, delete=False, suffix='.cpp',
78                                    prefix=testPrefix)
79  tmp.close()
80  pathInSuite = [os.path.relpath(tmp.name, sourceRoot)]
81  class TestWrapper(lit.Test.Test):
82    def __enter__(self):       return self
83    def __exit__(self, *args): os.remove(tmp.name)
84  return TestWrapper(suite, pathInSuite, config)
85
86@_memoizeExpensiveOperation(lambda c, s: (c.substitutions, c.environment, s))
87def sourceBuilds(config, source):
88  """
89  Return whether the program in the given string builds successfully.
90
91  This is done by compiling and linking a program that consists of the given
92  source with the %{cxx} substitution, and seeing whether that succeeds.
93  """
94  with _makeConfigTest(config) as test:
95    with open(test.getSourcePath(), 'w') as sourceFile:
96      sourceFile.write(source)
97    out, err, exitCode, timeoutInfo = _executeScriptInternal(test, ['%{build}'])
98    _executeScriptInternal(test, ['rm %t.exe'])
99    return exitCode == 0
100
101@_memoizeExpensiveOperation(lambda c, p, args=None, testPrefix='': (c.substitutions, c.environment, p, args))
102def programOutput(config, program, args=None, testPrefix=''):
103  """
104  Compiles a program for the test target, run it on the test target and return
105  the output.
106
107  If the program fails to compile or run, None is returned instead. Note that
108  execution of the program is done through the %{exec} substitution, which means
109  that the program may be run on a remote host depending on what %{exec} does.
110  """
111  if args is None:
112    args = []
113  with _makeConfigTest(config, testPrefix=testPrefix) as test:
114    with open(test.getSourcePath(), 'w') as source:
115      source.write(program)
116    try:
117      _, _, exitCode, _ = _executeScriptInternal(test, ['%{build}'])
118      if exitCode != 0:
119        return None
120
121      out, err, exitCode, _ = _executeScriptInternal(test, ["%{{run}} {}".format(' '.join(args))])
122      if exitCode != 0:
123        return None
124
125      actualOut = re.search("command output:\n(.+)\n$", out, flags=re.DOTALL)
126      actualOut = actualOut.group(1) if actualOut else ""
127      return actualOut
128
129    finally:
130      _executeScriptInternal(test, ['rm %t.exe'])
131
132@_memoizeExpensiveOperation(lambda c, f: (c.substitutions, c.environment, f))
133def hasCompileFlag(config, flag):
134  """
135  Return whether the compiler in the configuration supports a given compiler flag.
136
137  This is done by executing the %{cxx} substitution with the given flag and
138  checking whether that succeeds.
139  """
140  with _makeConfigTest(config) as test:
141    out, err, exitCode, timeoutInfo = _executeScriptInternal(test, [
142      "%{{cxx}} -xc++ {} -Werror -fsyntax-only %{{flags}} %{{compile_flags}} {}".format(os.devnull, flag)
143    ])
144    return exitCode == 0
145
146@_memoizeExpensiveOperation(lambda c, l: (c.substitutions, c.environment, l))
147def hasAnyLocale(config, locales):
148  """
149  Return whether the runtime execution environment supports a given locale.
150  Different systems may use different names for a locale, so this function checks
151  whether any of the passed locale names is supported by setlocale() and returns
152  true if one of them works.
153
154  This is done by executing a program that tries to set the given locale using
155  %{exec} -- this means that the command may be executed on a remote host
156  depending on the %{exec} substitution.
157  """
158  program = """
159    #include <locale.h>
160    #include <stdio.h>
161    int main(int argc, char** argv) {
162      // For debugging purposes print which locales are (not) supported.
163      for (int i = 1; i < argc; i++) {
164        if (::setlocale(LC_ALL, argv[i]) != NULL) {
165          printf("%s is supported.\\n", argv[i]);
166          return 0;
167        }
168        printf("%s is not supported.\\n", argv[i]);
169      }
170      return 1;
171    }
172  """
173  return programOutput(config, program, args=[pipes.quote(l) for l in locales],
174                       testPrefix="check_locale_" + locales[0]) is not None
175
176@_memoizeExpensiveOperation(lambda c, flags='': (c.substitutions, c.environment, flags))
177def compilerMacros(config, flags=''):
178  """
179  Return a dictionary of predefined compiler macros.
180
181  The keys are strings representing macros, and the values are strings
182  representing what each macro is defined to.
183
184  If the optional `flags` argument (a string) is provided, these flags will
185  be added to the compiler invocation when generating the macros.
186  """
187  with _makeConfigTest(config) as test:
188    with open(test.getSourcePath(), 'w') as sourceFile:
189      # Make sure files like <__config> are included, since they can define
190      # additional macros.
191      sourceFile.write("#include <cstddef>")
192    unparsedOutput, err, exitCode, timeoutInfo = _executeScriptInternal(test, [
193      "%{{cxx}} %s -dM -E %{{flags}} %{{compile_flags}} {}".format(flags)
194    ])
195    parsedMacros = dict()
196    defines = (l.strip() for l in unparsedOutput.split('\n') if l.startswith('#define '))
197    for line in defines:
198      line = line[len('#define '):]
199      macro, _, value = line.partition(' ')
200      parsedMacros[macro] = value
201    return parsedMacros
202
203def featureTestMacros(config, flags=''):
204  """
205  Return a dictionary of feature test macros.
206
207  The keys are strings representing feature test macros, and the values are
208  integers representing the value of the macro.
209  """
210  allMacros = compilerMacros(config, flags)
211  return {m: int(v.rstrip('LlUu')) for (m, v) in allMacros.items() if m.startswith('__cpp_')}
212
213@_memoizeExpensiveOperation(lambda c: (c.substitutions, c.environment))
214def getHostTriple(config):
215  """
216  Returns the default triple of the compiler.
217
218  TODO: This shouldn't be necessary here - ideally the user would always pass
219        the triple as a parameter. This is done to support the legacy standalone
220        builds, which don't set the triple.
221  """
222  with _makeConfigTest(config) as test:
223    unparsedOutput, err, exitCode, timeoutInfo = _executeScriptInternal(test, [
224      "%{cxx} %{flags} %{compile_flags} -dumpmachine"
225    ])
226    output = re.search(r"# command output:\n(.+)\n", unparsedOutput).group(1)
227    return output
228
229def _appendToSubstitution(substitutions, key, value):
230  return [(k, v + ' ' + value) if k == key else (k, v) for (k, v) in substitutions]
231
232def _prependToSubstitution(substitutions, key, value):
233  return [(k, value + ' ' + v) if k == key else (k, v) for (k, v) in substitutions]
234
235
236class ConfigAction(object):
237  """
238  This class represents an action that can be performed on a Lit TestingConfig
239  object.
240
241  Examples of such actions are adding or modifying substitutions, Lit features,
242  etc. This class only provides the interface of such actions, and it is meant
243  to be subclassed appropriately to create new actions.
244  """
245  def applyTo(self, config):
246    """
247    Applies the action to the given configuration.
248
249    This should modify the configuration object in place, and return nothing.
250
251    If applying the action to the configuration would yield an invalid
252    configuration, and it is possible to diagnose it here, this method
253    should produce an error. For example, it should be an error to modify
254    a substitution in a way that we know for sure is invalid (e.g. adding
255    a compiler flag when we know the compiler doesn't support it). Failure
256    to do so early may lead to difficult-to-diagnose issues down the road.
257    """
258    pass
259
260  def pretty(self, config, litParams):
261    """
262    Returns a short and human-readable string describing what this action does.
263
264    This is used for logging purposes when running the test suite, so it should
265    be kept concise.
266    """
267    pass
268
269
270class AddFeature(ConfigAction):
271  """
272  This action defines the given Lit feature when running the test suite.
273
274  The name of the feature can be a string or a callable, in which case it is
275  called with the configuration to produce the feature name (as a string).
276  """
277  def __init__(self, name):
278    self._name = name
279
280  def _getName(self, config):
281    name = self._name(config) if callable(self._name) else self._name
282    if not isinstance(name, str):
283      raise ValueError("Lit feature did not resolve to a string (got {})".format(name))
284    return name
285
286  def applyTo(self, config):
287    config.available_features.add(self._getName(config))
288
289  def pretty(self, config, litParams):
290    return 'add Lit feature {}'.format(self._getName(config))
291
292
293class AddFlag(ConfigAction):
294  """
295  This action adds the given flag to the %{flags} substitution.
296
297  The flag can be a string or a callable, in which case it is called with the
298  configuration to produce the actual flag (as a string).
299  """
300  def __init__(self, flag):
301    self._getFlag = lambda config: flag(config) if callable(flag) else flag
302
303  def applyTo(self, config):
304    flag = self._getFlag(config)
305    assert hasCompileFlag(config, flag), "Trying to enable flag {}, which is not supported".format(flag)
306    config.substitutions = _appendToSubstitution(config.substitutions, '%{flags}', flag)
307
308  def pretty(self, config, litParams):
309    return 'add {} to %{{flags}}'.format(self._getFlag(config))
310
311
312class AddFlagIfSupported(ConfigAction):
313  """
314  This action adds the given flag to the %{flags} substitution, only if
315  the compiler supports the flag.
316
317  The flag can be a string or a callable, in which case it is called with the
318  configuration to produce the actual flag (as a string).
319  """
320  def __init__(self, flag):
321    self._getFlag = lambda config: flag(config) if callable(flag) else flag
322
323  def applyTo(self, config):
324    flag = self._getFlag(config)
325    if hasCompileFlag(config, flag):
326      config.substitutions = _appendToSubstitution(config.substitutions, '%{flags}', flag)
327
328  def pretty(self, config, litParams):
329    return 'add {} to %{{flags}}'.format(self._getFlag(config))
330
331
332class AddCompileFlag(ConfigAction):
333  """
334  This action adds the given flag to the %{compile_flags} substitution.
335
336  The flag can be a string or a callable, in which case it is called with the
337  configuration to produce the actual flag (as a string).
338  """
339  def __init__(self, flag):
340    self._getFlag = lambda config: flag(config) if callable(flag) else flag
341
342  def applyTo(self, config):
343    flag = self._getFlag(config)
344    assert hasCompileFlag(config, flag), "Trying to enable compile flag {}, which is not supported".format(flag)
345    config.substitutions = _appendToSubstitution(config.substitutions, '%{compile_flags}', flag)
346
347  def pretty(self, config, litParams):
348    return 'add {} to %{{compile_flags}}'.format(self._getFlag(config))
349
350
351class AddLinkFlag(ConfigAction):
352  """
353  This action appends the given flag to the %{link_flags} substitution.
354
355  The flag can be a string or a callable, in which case it is called with the
356  configuration to produce the actual flag (as a string).
357  """
358  def __init__(self, flag):
359    self._getFlag = lambda config: flag(config) if callable(flag) else flag
360
361  def applyTo(self, config):
362    flag = self._getFlag(config)
363    assert hasCompileFlag(config, flag), "Trying to enable link flag {}, which is not supported".format(flag)
364    config.substitutions = _appendToSubstitution(config.substitutions, '%{link_flags}', flag)
365
366  def pretty(self, config, litParams):
367    return 'append {} to %{{link_flags}}'.format(self._getFlag(config))
368
369
370class PrependLinkFlag(ConfigAction):
371  """
372  This action prepends the given flag to the %{link_flags} substitution.
373
374  The flag can be a string or a callable, in which case it is called with the
375  configuration to produce the actual flag (as a string).
376  """
377  def __init__(self, flag):
378    self._getFlag = lambda config: flag(config) if callable(flag) else flag
379
380  def applyTo(self, config):
381    flag = self._getFlag(config)
382    assert hasCompileFlag(config, flag), "Trying to enable link flag {}, which is not supported".format(flag)
383    config.substitutions = _prependToSubstitution(config.substitutions, '%{link_flags}', flag)
384
385  def pretty(self, config, litParams):
386    return 'prepend {} to %{{link_flags}}'.format(self._getFlag(config))
387
388
389class AddOptionalWarningFlag(ConfigAction):
390  """
391  This action adds the given warning flag to the %{compile_flags} substitution,
392  if it is supported by the compiler.
393
394  The flag can be a string or a callable, in which case it is called with the
395  configuration to produce the actual flag (as a string).
396  """
397  def __init__(self, flag):
398    self._getFlag = lambda config: flag(config) if callable(flag) else flag
399
400  def applyTo(self, config):
401    flag = self._getFlag(config)
402    # Use -Werror to make sure we see an error about the flag being unsupported.
403    if hasCompileFlag(config, '-Werror ' + flag):
404      config.substitutions = _appendToSubstitution(config.substitutions, '%{compile_flags}', flag)
405
406  def pretty(self, config, litParams):
407    return 'add {} to %{{compile_flags}}'.format(self._getFlag(config))
408
409
410class AddSubstitution(ConfigAction):
411  """
412  This action adds the given substitution to the Lit configuration.
413
414  The substitution can be a string or a callable, in which case it is called
415  with the configuration to produce the actual substitution (as a string).
416  """
417  def __init__(self, key, substitution):
418    self._key = key
419    self._getSub = lambda config: substitution(config) if callable(substitution) else substitution
420
421  def applyTo(self, config):
422    key = self._key
423    sub = self._getSub(config)
424    config.substitutions.append((key, sub))
425
426  def pretty(self, config, litParams):
427    return 'add substitution {} = {}'.format(self._key, self._getSub(config))
428
429
430class Feature(object):
431  """
432  Represents a Lit available feature that is enabled whenever it is supported.
433
434  A feature like this informs the test suite about a capability of the compiler,
435  platform, etc. Unlike Parameters, it does not make sense to explicitly
436  control whether a Feature is enabled -- it should be enabled whenever it
437  is supported.
438  """
439  def __init__(self, name, actions=None, when=lambda _: True):
440    """
441    Create a Lit feature for consumption by a test suite.
442
443    - name
444        The name of the feature. This is what will end up in Lit's available
445        features if the feature is enabled. This can be either a string or a
446        callable, in which case it is passed the TestingConfig and should
447        generate a string representing the name of the feature.
448
449    - actions
450        An optional list of ConfigActions to apply when the feature is supported.
451        An AddFeature action is always created regardless of any actions supplied
452        here -- these actions are meant to perform more than setting a corresponding
453        Lit feature (e.g. adding compiler flags). If 'actions' is a callable, it
454        is called with the current configuration object to generate the actual
455        list of actions.
456
457    - when
458        A callable that gets passed a TestingConfig and should return a
459        boolean representing whether the feature is supported in that
460        configuration. For example, this can use `hasCompileFlag` to
461        check whether the compiler supports the flag that the feature
462        represents. If omitted, the feature will always be considered
463        supported.
464    """
465    self._name = name
466    self._actions = [] if actions is None else actions
467    self._isSupported = when
468
469  def _getName(self, config):
470    name = self._name(config) if callable(self._name) else self._name
471    if not isinstance(name, str):
472      raise ValueError("Feature did not resolve to a name that's a string, got {}".format(name))
473    return name
474
475  def getActions(self, config):
476    """
477    Return the list of actions associated to this feature.
478
479    If the feature is not supported, an empty list is returned.
480    If the feature is supported, an `AddFeature` action is automatically added
481    to the returned list of actions, in addition to any actions provided on
482    construction.
483    """
484    if not self._isSupported(config):
485      return []
486    else:
487      actions = self._actions(config) if callable(self._actions) else self._actions
488      return [AddFeature(self._getName(config))] + actions
489
490  def pretty(self, config):
491    """
492    Returns the Feature's name.
493    """
494    return self._getName(config)
495
496
497def _str_to_bool(s):
498  """
499  Convert a string value to a boolean.
500
501  True values are "y", "yes", "t", "true", "on" and "1", regardless of capitalization.
502  False values are "n", "no", "f", "false", "off" and "0", regardless of capitalization.
503  """
504  trueVals = ["y", "yes", "t", "true", "on", "1"]
505  falseVals = ["n", "no", "f", "false", "off", "0"]
506  lower = s.lower()
507  if lower in trueVals:
508    return True
509  elif lower in falseVals:
510    return False
511  else:
512    raise ValueError("Got string '{}', which isn't a valid boolean".format(s))
513
514
515class Parameter(object):
516  """
517  Represents a parameter of a Lit test suite.
518
519  Parameters are used to customize the behavior of test suites in a user
520  controllable way. There are two ways of setting the value of a Parameter.
521  The first one is to pass `--param <KEY>=<VALUE>` when running Lit (or
522  equivalenlty to set `litConfig.params[KEY] = VALUE` somewhere in the
523  Lit configuration files. This method will set the parameter globally for
524  all test suites being run.
525
526  The second method is to set `config.KEY = VALUE` somewhere in the Lit
527  configuration files, which sets the parameter only for the test suite(s)
528  that use that `config` object.
529
530  Parameters can have multiple possible values, and they can have a default
531  value when left unspecified. They can also have any number of ConfigActions
532  associated to them, in which case the actions will be performed on the
533  TestingConfig if the parameter is enabled. Depending on the actions
534  associated to a Parameter, it may be an error to enable the Parameter
535  if some actions are not supported in the given configuration. For example,
536  trying to set the compilation standard to C++23 when `-std=c++23` is not
537  supported by the compiler would be an error.
538  """
539  def __init__(self, name, type, help, actions, choices=None, default=None):
540    """
541    Create a Lit parameter to customize the behavior of a test suite.
542
543    - name
544        The name of the parameter that can be used to set it on the command-line.
545        On the command-line, the parameter can be set using `--param <name>=<value>`
546        when running Lit. This must be non-empty.
547
548    - choices
549        An optional non-empty set of possible values for this parameter. If provided,
550        this must be anything that can be iterated. It is an error if the parameter
551        is given a value that is not in that set, whether explicitly or through a
552        default value.
553
554    - type
555        A callable that can be used to parse the value of the parameter given
556        on the command-line. As a special case, using the type `bool` also
557        allows parsing strings with boolean-like contents.
558
559    - help
560        A string explaining the parameter, for documentation purposes.
561        TODO: We should be able to surface those from the Lit command-line.
562
563    - actions
564        A callable that gets passed the parsed value of the parameter (either
565        the one passed on the command-line or the default one), and that returns
566        a list of ConfigAction to perform given the value of the parameter.
567        All the ConfigAction must be supported in the given configuration.
568
569    - default
570        An optional default value to use for the parameter when no value is
571        provided on the command-line. If the default value is a callable, it
572        is called with the TestingConfig and should return the default value
573        for the parameter. Whether the default value is computed or specified
574        directly, it must be in the 'choices' provided for that Parameter.
575    """
576    self._name = name
577    if len(self._name) == 0:
578      raise ValueError("Parameter name must not be the empty string")
579
580    if choices is not None:
581      self._choices = list(choices) # should be finite
582      if len(self._choices) == 0:
583        raise ValueError("Parameter '{}' must be given at least one possible value".format(self._name))
584    else:
585      self._choices = None
586
587    self._parse = lambda x: (_str_to_bool(x) if type is bool and isinstance(x, str)
588                                             else type(x))
589    self._help = help
590    self._actions = actions
591    self._default = default
592
593  def _getValue(self, config, litParams):
594    """
595    Return the value of the parameter given the configuration objects.
596    """
597    param = getattr(config, self.name, None)
598    param = litParams.get(self.name, param)
599    if param is None and self._default is None:
600      raise ValueError("Parameter {} doesn't have a default value, but it was not specified in the Lit parameters or in the Lit config".format(self.name))
601    getDefault = lambda: self._default(config) if callable(self._default) else self._default
602    value = self._parse(param) if param is not None else getDefault()
603    if self._choices and value not in self._choices:
604      raise ValueError("Got value '{}' for parameter '{}', which is not in the provided set of possible choices: {}".format(value, self.name, self._choices))
605    return value
606
607  @property
608  def name(self):
609    """
610    Return the name of the parameter.
611
612    This is the name that can be used to set the parameter on the command-line
613    when running Lit.
614    """
615    return self._name
616
617  def getActions(self, config, litParams):
618    """
619    Return the list of actions associated to this value of the parameter.
620    """
621    return self._actions(self._getValue(config, litParams))
622
623  def pretty(self, config, litParams):
624    """
625    Return a pretty representation of the parameter's name and value.
626    """
627    return "{}={}".format(self.name, self._getValue(config, litParams))
628