mirror of
https://github.com/fmtlib/fmt.git
synced 2024-12-01 22:20:06 +00:00
115ca96e0e
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
242 lines
7.4 KiB
CMake
242 lines
7.4 KiB
CMake
# Test if compile errors are produced where necessary.
|
|
|
|
cmake_minimum_required(VERSION 3.8...3.25)
|
|
project(compile-error-test CXX)
|
|
|
|
set(fmt_headers "
|
|
#include <fmt/format.h>
|
|
#include <fmt/xchar.h>
|
|
#include <fmt/ostream.h>
|
|
#include <iostream>
|
|
")
|
|
|
|
set(error_test_names "")
|
|
set(non_error_test_content "")
|
|
|
|
# For error tests (we expect them to produce compilation error):
|
|
# * adds a name of test into `error_test_names` list
|
|
# * generates a single source file (with the same name) for each test
|
|
# For non-error tests (we expect them to compile successfully):
|
|
# * adds a code segment as separate function to `non_error_test_content`
|
|
function (expect_compile name code_fragment)
|
|
cmake_parse_arguments(EXPECT_COMPILE "ERROR" "" "" ${ARGN})
|
|
string(MAKE_C_IDENTIFIER "${name}" test_name)
|
|
|
|
if (EXPECT_COMPILE_ERROR)
|
|
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/${test_name}.cc" "
|
|
${fmt_headers}
|
|
void ${test_name}() {
|
|
${code_fragment}
|
|
}
|
|
")
|
|
set(error_test_names_copy "${error_test_names}")
|
|
list(APPEND error_test_names_copy "${test_name}")
|
|
set(error_test_names "${error_test_names_copy}" PARENT_SCOPE)
|
|
else()
|
|
set(non_error_test_content "
|
|
${non_error_test_content}
|
|
void ${test_name}() {
|
|
${code_fragment}
|
|
}" PARENT_SCOPE)
|
|
endif()
|
|
endfunction ()
|
|
|
|
# Generates a source file for non-error test with `non_error_test_content` and
|
|
# CMake project file with all error and single non-error test targets.
|
|
function (run_tests)
|
|
set(cmake_targets "")
|
|
foreach(test_name IN LISTS error_test_names)
|
|
set(cmake_targets "
|
|
${cmake_targets}
|
|
add_library(test-${test_name} ${test_name}.cc)
|
|
target_link_libraries(test-${test_name} PRIVATE fmt::fmt)
|
|
")
|
|
endforeach()
|
|
|
|
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/non_error_test.cc" "
|
|
${fmt_headers}
|
|
${non_error_test_content}
|
|
")
|
|
set(cmake_targets "
|
|
${cmake_targets}
|
|
add_library(non-error-test non_error_test.cc)
|
|
target_link_libraries(non-error-test PRIVATE fmt::fmt)
|
|
")
|
|
|
|
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/CMakeLists.txt" "
|
|
cmake_minimum_required(VERSION 3.8...3.25)
|
|
project(tests CXX)
|
|
add_subdirectory(${FMT_DIR} fmt)
|
|
${cmake_targets}
|
|
")
|
|
|
|
set(build_directory "${CMAKE_CURRENT_BINARY_DIR}/test/build")
|
|
file(MAKE_DIRECTORY "${build_directory}")
|
|
execute_process(
|
|
COMMAND
|
|
"${CMAKE_COMMAND}"
|
|
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
|
|
"-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}"
|
|
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
|
|
"-DCMAKE_GENERATOR=${CMAKE_GENERATOR}"
|
|
"-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}"
|
|
"-DFMT_DIR=${FMT_DIR}"
|
|
"${CMAKE_CURRENT_BINARY_DIR}/test"
|
|
WORKING_DIRECTORY "${build_directory}"
|
|
RESULT_VARIABLE result_var
|
|
OUTPUT_VARIABLE output_var
|
|
ERROR_VARIABLE output_var)
|
|
if (NOT result_var EQUAL 0)
|
|
message(FATAL_ERROR "Unable to configure:\n${output_var}")
|
|
endif()
|
|
|
|
foreach(test_name IN LISTS error_test_names)
|
|
execute_process(
|
|
COMMAND
|
|
"${CMAKE_COMMAND}" --build "${build_directory}" --target "test-${test_name}"
|
|
WORKING_DIRECTORY "${build_directory}"
|
|
RESULT_VARIABLE result_var
|
|
OUTPUT_VARIABLE output_var
|
|
ERROR_QUIET)
|
|
if (result_var EQUAL 0)
|
|
message(SEND_ERROR "No compile error for \"${test_name}\":\n${output_var}")
|
|
endif ()
|
|
endforeach()
|
|
|
|
execute_process(
|
|
COMMAND
|
|
"${CMAKE_COMMAND}" --build "${build_directory}" --target "non-error-test"
|
|
WORKING_DIRECTORY "${build_directory}"
|
|
RESULT_VARIABLE result_var
|
|
OUTPUT_VARIABLE output_var
|
|
ERROR_VARIABLE output_var)
|
|
if (NOT result_var EQUAL 0)
|
|
message(SEND_ERROR "Compile error for combined non-error test:\n${output_var}")
|
|
endif ()
|
|
endfunction ()
|
|
|
|
|
|
# check if the source file skeleton compiles
|
|
expect_compile(check "")
|
|
expect_compile(check-error "compilation_error" ERROR)
|
|
|
|
# Formatting a wide character with a narrow format string is forbidden.
|
|
expect_compile(wide-character-narrow-format-string "fmt::format(L\"{}\", L'a');")
|
|
expect_compile(wide-character-narrow-format-string-error "fmt::format(\"{}\", L'a');" ERROR)
|
|
|
|
# Formatting a wide string with a narrow format string is forbidden.
|
|
expect_compile(wide-string-narrow-format-string "fmt::format(L\"{}\", L\"foo\");")
|
|
expect_compile(wide-string-narrow-format-string-error "fmt::format(\"{}\", L\"foo\");" ERROR)
|
|
|
|
# Formatting a narrow string with a wide format string is forbidden because
|
|
# mixing UTF-8 with UTF-16/32 can result in an invalid output.
|
|
expect_compile(narrow-string-wide-format-string "fmt::format(L\"{}\", L\"foo\");")
|
|
expect_compile(narrow-string-wide-format-string-error "fmt::format(L\"{}\", \"foo\");" ERROR)
|
|
|
|
expect_compile(cast-to-string "
|
|
struct S {
|
|
operator std::string() const { return std::string(); }
|
|
};
|
|
fmt::format(\"{}\", std::string(S()));
|
|
")
|
|
expect_compile(cast-to-string-error "
|
|
struct S {
|
|
operator std::string() const { return std::string(); }
|
|
};
|
|
fmt::format(\"{}\", S());
|
|
" ERROR)
|
|
|
|
# Formatting a function
|
|
expect_compile(format-function "
|
|
void (*f)();
|
|
fmt::format(\"{}\", fmt::ptr(f));
|
|
")
|
|
expect_compile(format-function-error "
|
|
void (*f)();
|
|
fmt::format(\"{}\", f);
|
|
" ERROR)
|
|
|
|
# Formatting an unformattable argument should always be a compile time error
|
|
expect_compile(format-lots-of-arguments-with-unformattable "
|
|
struct E {};
|
|
fmt::format(\"\", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, E());
|
|
" ERROR)
|
|
expect_compile(format-lots-of-arguments-with-function "
|
|
void (*f)();
|
|
fmt::format(\"\", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, f);
|
|
" ERROR)
|
|
|
|
# Check if user-defined literals are available
|
|
include(CheckCXXSourceCompiles)
|
|
set(CMAKE_REQUIRED_FLAGS ${CXX_STANDARD_FLAG})
|
|
check_cxx_source_compiles("
|
|
void operator\"\" _udl(long double);
|
|
int main() {}"
|
|
SUPPORTS_USER_DEFINED_LITERALS)
|
|
set(CMAKE_REQUIRED_FLAGS )
|
|
if (NOT SUPPORTS_USER_DEFINED_LITERALS)
|
|
set (SUPPORTS_USER_DEFINED_LITERALS OFF)
|
|
endif ()
|
|
|
|
# Make sure that compiler features detected in the header
|
|
# match the features detected in CMake.
|
|
if (SUPPORTS_USER_DEFINED_LITERALS)
|
|
set(supports_udl 1)
|
|
else ()
|
|
set(supports_udl 0)
|
|
endif ()
|
|
expect_compile(udl-check "
|
|
#if FMT_USE_USER_DEFINED_LITERALS != ${supports_udl}
|
|
# error
|
|
#endif
|
|
")
|
|
|
|
if (CMAKE_CXX_STANDARD GREATER_EQUAL 20)
|
|
# Compile-time argument type check
|
|
expect_compile(format-string-number-spec "
|
|
#ifdef FMT_HAS_CONSTEVAL
|
|
fmt::format(\"{:d}\", 42);
|
|
#endif
|
|
")
|
|
expect_compile(format-string-number-spec-error "
|
|
#ifdef FMT_HAS_CONSTEVAL
|
|
fmt::format(\"{:d}\", \"I am not a number\");
|
|
#else
|
|
#error
|
|
#endif
|
|
" ERROR)
|
|
expect_compile(print-string-number-spec-error "
|
|
#ifdef FMT_HAS_CONSTEVAL
|
|
fmt::print(\"{:d}\", \"I am not a number\");
|
|
#else
|
|
#error
|
|
#endif
|
|
" ERROR)
|
|
expect_compile(print-stream-string-number-spec-error "
|
|
#ifdef FMT_HAS_CONSTEVAL
|
|
fmt::print(std::cout, \"{:d}\", \"I am not a number\");
|
|
#else
|
|
#error
|
|
#endif
|
|
" ERROR)
|
|
|
|
# Compile-time argument name check
|
|
expect_compile(format-string-name "
|
|
#if defined(FMT_HAS_CONSTEVAL) && FMT_USE_NONTYPE_TEMPLATE_ARGS
|
|
using namespace fmt::literals;
|
|
fmt::print(\"{foo}\", \"foo\"_a=42);
|
|
#endif
|
|
")
|
|
expect_compile(format-string-name-error "
|
|
#if defined(FMT_HAS_CONSTEVAL) && FMT_USE_NONTYPE_TEMPLATE_ARGS
|
|
using namespace fmt::literals;
|
|
fmt::print(\"{foo}\", \"bar\"_a=42);
|
|
#else
|
|
#error
|
|
#endif
|
|
" ERROR)
|
|
endif ()
|
|
|
|
# Run all tests
|
|
run_tests()
|