-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposed testing framework for Standard Library facilities #1
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,15 @@ | ||
// Copyright © 2024 Bret Brown | ||
// SPDX-License-Identifier: MIT | ||
|
||
#ifndef EXAMPLE_HXX | ||
#define EXAMPLE_HXX | ||
|
||
#include <type_traits> | ||
|
||
template <class T> | ||
constexpr bool foo() { | ||
static_assert(std::is_trivially_copyable_v<T>); | ||
return true; | ||
} | ||
|
||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,30 @@ | ||
add_executable(example.test) | ||
target_sources(example.test PRIVATE example.test.cxx) | ||
target_link_libraries(example.test PRIVATE example::example) | ||
# Setup the `lit` tool | ||
add_custom_command( | ||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/venv/bin/lit" | ||
COMMAND python3 -m venv "${CMAKE_CURRENT_BINARY_DIR}/venv" | ||
COMMAND "${CMAKE_CURRENT_BINARY_DIR}/venv/bin/pip" install --upgrade pip | ||
COMMAND "${CMAKE_CURRENT_BINARY_DIR}/venv/bin/pip" install --upgrade lit | ||
) | ||
|
||
# Setup the test suite configuration | ||
configure_file("${CMAKE_SOURCE_DIR}/test/support/lit.cfg.in" | ||
"${CMAKE_CURRENT_BINARY_DIR}/lit.cfg") | ||
|
||
add_custom_target(test-depends | ||
COMMAND true | ||
DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/venv/bin/lit" | ||
"${CMAKE_CURRENT_BINARY_DIR}/lit.cfg" | ||
example | ||
COMMENT "Setup the test dependencies" | ||
) | ||
|
||
add_test( | ||
NAME setup-tests | ||
COMMAND "${CMAKE_COMMAND}" --build "${CMAKE_BINARY_DIR}" --target test-depends | ||
) | ||
|
||
Comment on lines
+21
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is awkward. It looks like a CTest test cannot depend on a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no guarantee that ctest will run
But then again, that might be misleading a user to think the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CMake doesn't provide a way to indicate tests have dependencies so the prevailing practice is for tests to assume all targets part of the default target are built. To ensure |
||
add_test( | ||
NAME example.test | ||
COMMAND example.test | ||
COMMAND "${CMAKE_CURRENT_BINARY_DIR}/venv/bin/lit" -sv "${CMAKE_CURRENT_BINARY_DIR}" | ||
) | ||
set_tests_properties(example.test PROPERTIES DEPENDS setup-tests) |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// | ||
// This test gets built and run. The test succeeds if the program | ||
// terminates normally with a 0 exit code. The test fails if it | ||
// doesn't compile, link or if it exits with an error. | ||
// | ||
|
||
#include <cassert> | ||
#include <example.hxx> | ||
|
||
int main(int, char**) { | ||
assert(foo<int>()); | ||
|
||
return 0; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// | ||
// This test runs using clang-verify. This allows checking that specific | ||
// diagnostics are being emitted at compile-time. | ||
// | ||
// Clang-verify supports various directives like 'expected-error', | ||
// 'expected-warning', etc. The full set of directives supported and | ||
// how to use them is documented in https://clang.llvm.org/docs/InternalsManual.html#specifying-diagnostics. | ||
// | ||
|
||
#include <string> | ||
#include <example.hxx> | ||
|
||
void f() { | ||
foo<std::string>(); // expected-error@*:* {{static assertion failed due to requirement 'std::is_trivially_copyable_v}} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
// | ||
// This test doesn't run, it only compiles. | ||
// The test passes if the program compiles, and fails otherwise. | ||
// | ||
|
||
#include <example.hxx> | ||
|
||
static_assert(foo<int>()); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// | ||
// This test runs whatever shell commands are specified in the RUN commands | ||
// below. This provides a lot of flexibility for controlling how the test | ||
// gets built and run. | ||
// | ||
|
||
// RUN: %{cxx} %{flags} %s -DTRANSLATION_UNIT_1 -c -o %t.tu1.o | ||
// RUN: %{cxx} %{flags} %s -DTRANSLATION_UNIT_2 -c -o %t.tu2.o | ||
// RUN: %{cxx} %{flags} %t.tu1.o %t.tu2.o -o %t.exe | ||
// RUN: %t.exe | ||
|
||
#include <example.hxx> | ||
|
||
#ifdef TRANSLATION_UNIT_1 | ||
void f() { } | ||
#endif | ||
|
||
#ifdef TRANSLATION_UNIT_2 | ||
extern void f(); | ||
|
||
int main() { | ||
f(); | ||
} | ||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import site, os | ||
site.addsitedir(os.path.join('@CMAKE_SOURCE_DIR@', 'test', 'support')) | ||
import testformat | ||
|
||
config.name = 'Beman project test suite' | ||
config.test_format = testformat.CxxStandardLibraryTest() | ||
config.test_exec_root = '@CMAKE_CURRENT_BINARY_DIR@' | ||
config.test_source_root = '@CMAKE_CURRENT_SOURCE_DIR@' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Usage of CMAKE_CURRENT_BINARY_DIR and CMAKE_CURRENT_SOURCE_DIR is confusing here since these are unrelated to the location of So, this file would instead be...
And block()
# Setup the test suite configuration
set(LIT_TEST_EXEC_ROOT ${CMAKE_CURRENT_BINARY_DIR})
set(LIT_TEST_SOURCE_ROOT ${CMAKE_CURRENT_SOURCE_DIR})
configure_file("${CMAKE_SOURCE_DIR}/test/support/lit.cfg.in"
"${CMAKE_CURRENT_BINARY_DIR}/lit.cfg")
endblock() |
||
flags = [ | ||
'-std=c++20', | ||
'-isysroot @CMAKE_OSX_SYSROOT@' if '@CMAKE_OSX_SYSROOT@' else '', | ||
'-isystem {}'.format(os.path.join('@CMAKE_SOURCE_DIR@', 'src', 'example')), | ||
'@CMAKE_CXX_FLAGS@' | ||
] | ||
Comment on lines
+9
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When the project under test is more complex, I expect that there will be transitive usage requirements that need to get passed to the compilation flags here. Do those need to be repeated manually or is there a way to query cmake? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another thing that needs to be verified is cross-compilation support. |
||
config.substitutions = [ | ||
('%{cxx}', '@CMAKE_CXX_COMPILER@'), | ||
('%{flags}', ' '.join(filter(None, flags))) | ||
] | ||
config.available_features = [] | ||
if '@CMAKE_CXX_COMPILER_ID@' in ('Clang', 'AppleClang'): | ||
config.available_features.append('verify-support') |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import lit | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For information, I am working on some LLVM changes that would render this file unnecessary. Basically we would just install Lit and the Lit package would contain exactly the same test format as the one used by libc++. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where are you at with these changes? |
||
import os | ||
import re | ||
|
||
def _parseScript(test, preamble): | ||
""" | ||
Extract the script from a test, with substitutions applied. | ||
|
||
Returns a list of commands ready to be executed. | ||
|
||
- test | ||
The lit.Test to parse. | ||
|
||
- preamble | ||
A list of commands to perform before any command in the test. | ||
These commands can contain unexpanded substitutions, but they | ||
must not be of the form 'RUN:' -- they must be proper commands | ||
once substituted. | ||
""" | ||
# Get the default substitutions | ||
tmpDir, tmpBase = lit.TestRunner.getTempPaths(test) | ||
substitutions = lit.TestRunner.getDefaultSubstitutions(test, tmpDir, tmpBase) | ||
|
||
# Parse the test file, including custom directives | ||
scriptInTest = lit.TestRunner.parseIntegratedTestScript(test, require_script=not preamble) | ||
if isinstance(scriptInTest, lit.Test.Result): | ||
return scriptInTest | ||
|
||
script = preamble + scriptInTest | ||
return lit.TestRunner.applySubstitutions(script, substitutions, recursion_limit=10) | ||
|
||
class CxxStandardLibraryTest(lit.formats.FileBasedTest): | ||
def getTestsForPath(self, testSuite, pathInSuite, litConfig, localConfig): | ||
SUPPORTED_SUFFIXES = [ | ||
"[.]pass[.]cpp$", | ||
"[.]compile[.]pass[.]cpp$", | ||
"[.]sh[.][^.]+$", | ||
"[.]verify[.]cpp$", | ||
] | ||
|
||
sourcePath = testSuite.getSourcePath(pathInSuite) | ||
filename = os.path.basename(sourcePath) | ||
|
||
# Ignore dot files, excluded tests and tests with an unsupported suffix | ||
hasSupportedSuffix = lambda f: any([re.search(ext, f) for ext in SUPPORTED_SUFFIXES]) | ||
if filename.startswith(".") or filename in localConfig.excludes or not hasSupportedSuffix(filename): | ||
return | ||
|
||
yield lit.Test.Test(testSuite, pathInSuite, localConfig) | ||
|
||
def execute(self, test, litConfig): | ||
supportsVerify = "verify-support" in test.config.available_features | ||
filename = test.path_in_suite[-1] | ||
|
||
if re.search("[.]sh[.][^.]+$", filename): | ||
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 %{flags} -fsyntax-only"] | ||
return self._executeShTest(test, litConfig, steps) | ||
elif filename.endswith(".verify.cpp"): | ||
if not supportsVerify: | ||
return lit.Test.Result( | ||
lit.Test.UNSUPPORTED, | ||
"Test {} requires support for Clang-verify, which isn't supported by the compiler".format(test.getFullName()), | ||
) | ||
steps = ["%dbg(COMPILED WITH) %{cxx} %s %{flags} -fsyntax-only -Xclang -verify -Xclang -verify-ignore-unexpected=note -ferror-limit=0"] | ||
return self._executeShTest(test, litConfig, steps) | ||
elif filename.endswith(".pass.cpp"): | ||
steps = [ | ||
"%dbg(COMPILED WITH) %{cxx} %s %{flags} -o %t.exe", | ||
"%dbg(EXECUTED AS) %t.exe", | ||
] | ||
return self._executeShTest(test, litConfig, steps) | ||
else: | ||
return lit.Test.Result(lit.Test.UNRESOLVED, "Unknown test suffix for '{}'".format(filename)) | ||
|
||
def _executeShTest(self, test, litConfig, steps): | ||
if test.config.unsupported: | ||
return lit.Test.Result(lit.Test.UNSUPPORTED, "Test is unsupported") | ||
|
||
script = _parseScript(test, steps) | ||
if isinstance(script, lit.Test.Result): | ||
return script | ||
|
||
if litConfig.noExecute: | ||
return lit.Test.Result(lit.Test.XFAIL if test.isExpectedToFail() else lit.Test.PASS) | ||
else: | ||
_, tmpBase = lit.TestRunner.getTempPaths(test) | ||
useExternalSh = False | ||
return lit.TestRunner._runShTest(test, litConfig, useExternalSh, script, tmpBase) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using
assert
precludes test runs under the-DNDEBUG
flag. Wouldn't that pose a problem?