1# Test if compile errors are produced where necessary. 2 3cmake_minimum_required(VERSION 3.8...3.25) 4project(compile-error-test CXX) 5 6set(fmt_headers " 7 #include <fmt/format.h> 8 #include <fmt/xchar.h> 9 #include <fmt/ostream.h> 10 #include <iostream> 11") 12 13set(error_test_names "") 14set(non_error_test_content "") 15 16# For error tests (we expect them to produce compilation error): 17# * adds a name of test into `error_test_names` list 18# * generates a single source file (with the same name) for each test 19# For non-error tests (we expect them to compile successfully): 20# * adds a code segment as separate function to `non_error_test_content` 21function (expect_compile name code_fragment) 22 cmake_parse_arguments(EXPECT_COMPILE "ERROR" "" "" ${ARGN}) 23 string(MAKE_C_IDENTIFIER "${name}" test_name) 24 25 if (EXPECT_COMPILE_ERROR) 26 file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/${test_name}.cc" " 27 ${fmt_headers} 28 void ${test_name}() { 29 ${code_fragment} 30 } 31 ") 32 set(error_test_names_copy "${error_test_names}") 33 list(APPEND error_test_names_copy "${test_name}") 34 set(error_test_names "${error_test_names_copy}" PARENT_SCOPE) 35 else() 36 set(non_error_test_content " 37 ${non_error_test_content} 38 void ${test_name}() { 39 ${code_fragment} 40 }" PARENT_SCOPE) 41 endif() 42endfunction () 43 44# Generates a source file for non-error test with `non_error_test_content` and 45# CMake project file with all error and single non-error test targets. 46function (run_tests) 47 set(cmake_targets "") 48 foreach(test_name IN LISTS error_test_names) 49 set(cmake_targets " 50 ${cmake_targets} 51 add_library(test-${test_name} ${test_name}.cc) 52 target_link_libraries(test-${test_name} PRIVATE fmt::fmt) 53 ") 54 endforeach() 55 56 file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/non_error_test.cc" " 57 ${fmt_headers} 58 ${non_error_test_content} 59 ") 60 set(cmake_targets " 61 ${cmake_targets} 62 add_library(non-error-test non_error_test.cc) 63 target_link_libraries(non-error-test PRIVATE fmt::fmt) 64 ") 65 66 file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/CMakeLists.txt" " 67 cmake_minimum_required(VERSION 3.8...3.25) 68 project(tests CXX) 69 add_subdirectory(${FMT_DIR} fmt) 70 ${cmake_targets} 71 ") 72 73 set(build_directory "${CMAKE_CURRENT_BINARY_DIR}/test/build") 74 file(MAKE_DIRECTORY "${build_directory}") 75 execute_process( 76 COMMAND 77 "${CMAKE_COMMAND}" 78 "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" 79 "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}" 80 "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" 81 "-DCMAKE_GENERATOR=${CMAKE_GENERATOR}" 82 "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}" 83 "-DFMT_DIR=${FMT_DIR}" 84 "${CMAKE_CURRENT_BINARY_DIR}/test" 85 WORKING_DIRECTORY "${build_directory}" 86 RESULT_VARIABLE result_var 87 OUTPUT_VARIABLE output_var 88 ERROR_VARIABLE output_var) 89 if (NOT result_var EQUAL 0) 90 message(FATAL_ERROR "Unable to configure:\n${output_var}") 91 endif() 92 93 foreach(test_name IN LISTS error_test_names) 94 execute_process( 95 COMMAND 96 "${CMAKE_COMMAND}" --build "${build_directory}" --target "test-${test_name}" 97 WORKING_DIRECTORY "${build_directory}" 98 RESULT_VARIABLE result_var 99 OUTPUT_VARIABLE output_var 100 ERROR_QUIET) 101 if (result_var EQUAL 0) 102 message(SEND_ERROR "No compile error for \"${test_name}\":\n${output_var}") 103 endif () 104 endforeach() 105 106 execute_process( 107 COMMAND 108 "${CMAKE_COMMAND}" --build "${build_directory}" --target "non-error-test" 109 WORKING_DIRECTORY "${build_directory}" 110 RESULT_VARIABLE result_var 111 OUTPUT_VARIABLE output_var 112 ERROR_VARIABLE output_var) 113 if (NOT result_var EQUAL 0) 114 message(SEND_ERROR "Compile error for combined non-error test:\n${output_var}") 115 endif () 116endfunction () 117 118 119# check if the source file skeleton compiles 120expect_compile(check "") 121expect_compile(check-error "compilation_error" ERROR) 122 123# Formatting a wide character with a narrow format string is forbidden. 124expect_compile(wide-character-narrow-format-string "fmt::format(L\"{}\", L'a');") 125expect_compile(wide-character-narrow-format-string-error "fmt::format(\"{}\", L'a');" ERROR) 126 127# Formatting a wide string with a narrow format string is forbidden. 128expect_compile(wide-string-narrow-format-string "fmt::format(L\"{}\", L\"foo\");") 129expect_compile(wide-string-narrow-format-string-error "fmt::format(\"{}\", L\"foo\");" ERROR) 130 131# Formatting a narrow string with a wide format string is forbidden because 132# mixing UTF-8 with UTF-16/32 can result in an invalid output. 133expect_compile(narrow-string-wide-format-string "fmt::format(L\"{}\", L\"foo\");") 134expect_compile(narrow-string-wide-format-string-error "fmt::format(L\"{}\", \"foo\");" ERROR) 135 136expect_compile(cast-to-string " 137 struct S { 138 operator std::string() const { return std::string(); } 139 }; 140 fmt::format(\"{}\", std::string(S())); 141") 142expect_compile(cast-to-string-error " 143 struct S { 144 operator std::string() const { return std::string(); } 145 }; 146 fmt::format(\"{}\", S()); 147" ERROR) 148 149# Formatting a function 150expect_compile(format-function " 151 void (*f)(); 152 fmt::format(\"{}\", fmt::ptr(f)); 153") 154expect_compile(format-function-error " 155 void (*f)(); 156 fmt::format(\"{}\", f); 157" ERROR) 158 159# Formatting an unformattable argument should always be a compile time error 160expect_compile(format-lots-of-arguments-with-unformattable " 161 struct E {}; 162 fmt::format(\"\", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, E()); 163" ERROR) 164expect_compile(format-lots-of-arguments-with-function " 165 void (*f)(); 166 fmt::format(\"\", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, f); 167" ERROR) 168 169# Check if user-defined literals are available 170include(CheckCXXSourceCompiles) 171set(CMAKE_REQUIRED_FLAGS ${CXX_STANDARD_FLAG}) 172check_cxx_source_compiles(" 173 void operator\"\" _udl(long double); 174 int main() {}" 175 SUPPORTS_USER_DEFINED_LITERALS) 176set(CMAKE_REQUIRED_FLAGS ) 177if (NOT SUPPORTS_USER_DEFINED_LITERALS) 178 set (SUPPORTS_USER_DEFINED_LITERALS OFF) 179endif () 180 181# Make sure that compiler features detected in the header 182# match the features detected in CMake. 183if (SUPPORTS_USER_DEFINED_LITERALS) 184 set(supports_udl 1) 185else () 186 set(supports_udl 0) 187endif () 188expect_compile(udl-check " 189 #if FMT_USE_USER_DEFINED_LITERALS != ${supports_udl} 190 # error 191 #endif 192") 193 194if (CMAKE_CXX_STANDARD GREATER_EQUAL 20) 195 # Compile-time argument type check 196 expect_compile(format-string-number-spec " 197 #ifdef FMT_HAS_CONSTEVAL 198 fmt::format(\"{:d}\", 42); 199 #endif 200 ") 201 expect_compile(format-string-number-spec-error " 202 #ifdef FMT_HAS_CONSTEVAL 203 fmt::format(\"{:d}\", \"I am not a number\"); 204 #else 205 #error 206 #endif 207 " ERROR) 208 expect_compile(print-string-number-spec-error " 209 #ifdef FMT_HAS_CONSTEVAL 210 fmt::print(\"{:d}\", \"I am not a number\"); 211 #else 212 #error 213 #endif 214 " ERROR) 215 expect_compile(print-stream-string-number-spec-error " 216 #ifdef FMT_HAS_CONSTEVAL 217 fmt::print(std::cout, \"{:d}\", \"I am not a number\"); 218 #else 219 #error 220 #endif 221 " ERROR) 222 223 # Compile-time argument name check 224 expect_compile(format-string-name " 225 #if defined(FMT_HAS_CONSTEVAL) && FMT_USE_NONTYPE_TEMPLATE_ARGS 226 using namespace fmt::literals; 227 fmt::print(\"{foo}\", \"foo\"_a=42); 228 #endif 229 ") 230 expect_compile(format-string-name-error " 231 #if defined(FMT_HAS_CONSTEVAL) && FMT_USE_NONTYPE_TEMPLATE_ARGS 232 using namespace fmt::literals; 233 fmt::print(\"{foo}\", \"bar\"_a=42); 234 #else 235 #error 236 #endif 237 " ERROR) 238endif () 239 240# Run all tests 241run_tests() 242