blob: 649259b2f87acb4c088399bb41788decc94d3f9b [file] [log] [blame]
#===----------------------------------------------------------------------===##
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
#===----------------------------------------------------------------------===##
import lit
import os
import pipes
class CxxStandardLibraryTest(lit.formats.TestFormat):
"""
Lit test format for the C++ Standard Library conformance test suite.
This test format is based on top of the ShTest format -- it basically
creates a shell script performing the right operations (compile/link/run)
based on the extension of the test file it encounters. It supports files
with the following extensions:
FOO.pass.cpp - Compiles, links and runs successfully
FOO.pass.mm - Same as .pass.cpp, but for Objective-C++
FOO.run.fail.cpp - Compiles and links successfully, but fails at runtime
FOO.compile.pass.cpp - Compiles successfully, link and run not attempted
FOO.compile.fail.cpp - Does not compile successfully. These tests run with
clang-verify.
FOO.link.pass.cpp - Compiles and links successfully, run not attempted
FOO.link.fail.cpp - Compiles successfully, but fails to link
FOO.sh.cpp - A builtin lit Shell test
FOO.fail.cpp - Does not compile successfully -- run with clang-verify
if any expected-meow appears in the file, otherwise
just test that compilation fails. This is supported
only for backwards compatibility with the test suite
FOO.fail.mm - Same as .fail.cpp, but for Objective-C++
The test format operates by assuming that each test's configuration provides
the following substitutions, which it will reuse in the shell scripts it
constructs:
%{cxx} - A command that can be used to invoke the compiler
%{compile_flags} - Flags to use when compiling a test case
%{link_flags} - Flags to use when linking a test case
%{exec} - A command to prefix the execution of executables
Note that when building an executable (as opposed to only compiling a source
file), both %{compile_flags} and %{link_flags} will be used in the same
command line. In other words, the test format doesn't perform separate
compilation and linking steps in this case.
In addition to everything that's supported in Lit ShTests, this test format
also understands the following directives inside test files:
// FILE_DEPENDENCIES: file, directory, /path/to/file
This directive expresses that the test requires the provided files
or directories in order to run. An example is a test that requires
some test input stored in a data file. When a test file contains
such a directive, this test format will collect them and make them
available in a special %{file_dependencies} substitution. The intent
is that if one needs to e.g. execute tests on a remote host, the
%{exec} substitution could use %{file_dependencies} to know which
files and directories to copy to the remote host.
// ADDITIONAL_COMPILE_FLAGS: flag1, flag2, flag3
This directive will cause the provided flags to be added to the
%{compile_flags} substitution for the test that contains it. This
allows adding special compilation flags without having to use a
.sh.cpp test, which would be more powerful but perhaps overkill.
Design note:
This test format never implicitly disables a type of test. For example,
we could be tempted to automatically mark `.compile.fail.cpp` tests as
UNSUPPORTED when clang-verify isn't supported by the compiler. However,
this sort of logic has been known to cause tests to be ignored in the
past, so we favour having tests mark themselves as unsupported explicitly.
This test format still needs work in the following areas:
- It doesn't support the MODULES_DEFINES from the old test format, but
that can be achieved easily with ADDITIONAL_COMPILE_FLAGS.
- It is unknown how well it works on Windows yet.
"""
def getTestsInDirectory(self, testSuite, pathInSuite, litConfig, localConfig):
SUPPORTED_SUFFIXES = ['.pass.cpp', '.run.fail.cpp',
'.pass.mm',
'.compile.pass.cpp', '.compile.fail.cpp',
'.link.pass.cpp', '.link.fail.cpp',
'.sh.cpp',
'.fail.cpp', '.fail.mm']
sourcePath = testSuite.getSourcePath(pathInSuite)
for filename in os.listdir(sourcePath):
# Ignore dot files and excluded tests.
if filename.startswith('.') or filename in localConfig.excludes:
continue
filepath = os.path.join(sourcePath, filename)
if not os.path.isdir(filepath):
if any([filename.endswith(ext) for ext in SUPPORTED_SUFFIXES]):
yield lit.Test.Test(testSuite, pathInSuite + (filename,), localConfig)
def _checkSubstitutions(self, substitutions):
substitutions = [s for (s, _) in substitutions]
for s in ['%{cxx}', '%{compile_flags}', '%{link_flags}', '%{exec}']:
assert s in substitutions, "Required substitution {} was not provided".format(s)
# Determine whether -verify should be used for a given test. We use -verify
# if the compiler supports it and there's at least one -verify tag in the
# source file.
#
# This is only supported for backwards compatibility with .fail.cpp tests.
def _useVerify(self, test, litConfig):
VERIFY_TAGS = (b'expected-note', b'expected-remark',
b'expected-warning', b'expected-error',
b'expected-no-diagnostics')
with open(test.getSourcePath(), 'rb') as f:
contents = f.read()
testContainsTags = any(tag in contents for tag in VERIFY_TAGS)
command = "echo | %{cxx} -xc++ - -Werror -fsyntax-only -Xclang -verify-ignore-unexpected"
result = lit.TestRunner.executeShTest(test, litConfig,
useExternalSh=True,
preamble_commands=[command])
compilerSupportsVerify = result.code != lit.Test.FAIL
return compilerSupportsVerify and testContainsTags
def execute(self, test, litConfig):
self._checkSubstitutions(test.config.substitutions)
VERIFY_FLAGS = '-Xclang -verify -Xclang -verify-ignore-unexpected=note -ferror-limit=0'
filename = test.path_in_suite[-1]
if filename.endswith('.sh.cpp'):
steps = [ ] # The steps are already in the script
return self._executeShTest(test, litConfig, steps)
elif filename.endswith('.compile.pass.cpp'):
steps = [
"%dbg(COMPILED WITH) %{cxx} %s %{compile_flags} -fsyntax-only"
]
return self._executeShTest(test, litConfig, steps)
elif filename.endswith('.compile.fail.cpp'):
steps = [
"%dbg(COMPILED WITH) %{cxx} %s %{compile_flags} -fsyntax-only " + VERIFY_FLAGS
]
return self._executeShTest(test, litConfig, steps)
elif filename.endswith('.link.pass.cpp'):
steps = [
"%dbg(COMPILED WITH) %{cxx} %s %{compile_flags} %{link_flags} -o %t.exe"
]
return self._executeShTest(test, litConfig, steps)
elif filename.endswith('.link.fail.cpp'):
steps = [
"%dbg(COMPILED WITH) %{cxx} %s %{compile_flags} -c -o %t.o",
"%dbg(LINKED WITH) ! %{cxx} %t.o %{link_flags} -o %t.exe"
]
return self._executeShTest(test, litConfig, steps)
elif filename.endswith('.run.fail.cpp'):
steps = [
"%dbg(COMPILED WITH) %{cxx} %s %{compile_flags} %{link_flags} -o %t.exe",
"%dbg(EXECUTED AS) %{exec} ! %t.exe"
]
return self._executeShTest(test, litConfig, steps, fileDependencies=['%t.exe'])
# Make sure to check these ones last, since they will match other
# suffixes above too.
elif filename.endswith('.pass.cpp') or filename.endswith('.pass.mm'):
steps = [
"%dbg(COMPILED WITH) %{cxx} %s %{compile_flags} %{link_flags} -o %t.exe",
"%dbg(EXECUTED AS) %{exec} %t.exe"
]
return self._executeShTest(test, litConfig, steps, fileDependencies=['%t.exe'])
# This is the same as .compile.fail.cpp, provided for backwards
# compatibility with the test suite.
elif filename.endswith('.fail.cpp') or filename.endswith('.fail.mm'):
if self._useVerify(test, litConfig):
steps = [
"%dbg(COMPILED WITH) %{cxx} %s %{compile_flags} -fsyntax-only " + VERIFY_FLAGS
]
else:
steps = [
"%dbg(COMPILED WITH) ! %{cxx} %s %{compile_flags} -fsyntax-only"
]
return self._executeShTest(test, litConfig, steps)
else:
return lit.Test.Result(lit.Test.FAIL, "Unknown test suffix for '{}'".format(filename))
# Utility function to add compile flags in lit.local.cfg files.
def addCompileFlags(self, config, *flags):
string = ' '.join(flags)
config.substitutions = [(s, x + ' ' + string) if s == '%{compile_flags}' else (s, x) for (s, x) in config.substitutions]
# Modified version of lit.TestRunner.executeShTest to handle custom parsers correctly.
def _executeShTest(self, test, litConfig, steps, fileDependencies=None):
recursiveExpansionLimit = 10 # TODO: Use the value in litConfig once we set it.
if test.config.unsupported:
return lit.Test.Result(lit.Test.UNSUPPORTED, 'Test is unsupported')
additionalCompileFlags = []
fileDependencies = fileDependencies or []
parsers = [
lit.TestRunner.IntegratedTestKeywordParser('FILE_DEPENDENCIES:',
lit.TestRunner.ParserKind.LIST,
initial_value=fileDependencies),
lit.TestRunner.IntegratedTestKeywordParser('ADDITIONAL_COMPILE_FLAGS:',
lit.TestRunner.ParserKind.LIST,
initial_value=additionalCompileFlags)
]
script = list(steps)
parsed = lit.TestRunner.parseIntegratedTestScript(test, additional_parsers=parsers,
require_script=not script)
if isinstance(parsed, lit.Test.Result):
return parsed
script += parsed
if litConfig.noExecute:
return lit.Test.Result(lit.Test.PASS)
# Add compile flags specified with ADDITIONAL_COMPILE_FLAGS.
self.addCompileFlags(test.config, *additionalCompileFlags)
tmpDir, tmpBase = lit.TestRunner.getTempPaths(test)
useExternalSh = True
substitutions = lit.TestRunner.getDefaultSubstitutions(test, tmpDir, tmpBase,
normalize_slashes=useExternalSh)
# Perform substitutions inside FILE_DEPENDENCIES lines (or injected dependencies).
# This allows using variables like %t in file dependencies. Also note that we really
# need to resolve %{file_dependencies} now, because otherwise we won't be able to
# make all paths absolute below.
fileDependencies = lit.TestRunner.applySubstitutions(fileDependencies, substitutions,
recursion_limit=recursiveExpansionLimit)
# Add the %{file_dependencies} substitution before we perform substitutions
# inside the script.
testDir = os.path.dirname(test.getSourcePath())
fileDependencies = [f if os.path.isabs(f) else os.path.join(testDir, f) for f in fileDependencies]
substitutions.append(('%{file_dependencies}', ' '.join(map(pipes.quote, fileDependencies))))
# Perform substitution in the script itself.
script = lit.TestRunner.applySubstitutions(script, substitutions,
recursion_limit=recursiveExpansionLimit)
return lit.TestRunner._runShTest(test, litConfig, useExternalSh, script, tmpBase)