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# Check if the source file skeleton compiles. 119expect_compile(check "") 120expect_compile(check-error "compilation_error" ERROR) 121 122# Formatting a wide character with a narrow format string is forbidden. 123expect_compile(wide-character-narrow-format-string "fmt::format(L\"{}\", L'a');") 124expect_compile(wide-character-narrow-format-string-error "fmt::format(\"{}\", L'a');" ERROR) 125 126# Formatting a wide string with a narrow format string is forbidden. 127expect_compile(wide-string-narrow-format-string "fmt::format(L\"{}\", L\"foo\");") 128expect_compile(wide-string-narrow-format-string-error "fmt::format(\"{}\", L\"foo\");" ERROR) 129 130# Formatting a narrow string with a wide format string is forbidden because 131# mixing UTF-8 with UTF-16/32 can result in an invalid output. 132expect_compile(narrow-string-wide-format-string "fmt::format(L\"{}\", L\"foo\");") 133expect_compile(narrow-string-wide-format-string-error "fmt::format(L\"{}\", \"foo\");" ERROR) 134 135expect_compile(cast-to-string " 136 struct S { 137 operator std::string() const { return std::string(); } 138 }; 139 fmt::format(\"{}\", std::string(S())); 140") 141expect_compile(cast-to-string-error " 142 struct S { 143 operator std::string() const { return std::string(); } 144 }; 145 fmt::format(\"{}\", S()); 146" ERROR) 147 148# Formatting a function 149expect_compile(format-function " 150 void (*f)(); 151 fmt::format(\"{}\", fmt::ptr(f)); 152") 153expect_compile(format-function-error " 154 void (*f)(); 155 fmt::format(\"{}\", f); 156" ERROR) 157 158# Formatting an unformattable argument should always be a compile time error 159expect_compile(format-lots-of-arguments-with-unformattable " 160 struct E {}; 161 fmt::format(\"\", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, E()); 162" ERROR) 163expect_compile(format-lots-of-arguments-with-function " 164 void (*f)(); 165 fmt::format(\"\", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, f); 166" ERROR) 167 168if (CMAKE_CXX_STANDARD GREATER_EQUAL 20) 169 # Compile-time argument type check 170 expect_compile(format-string-number-spec " 171 #ifdef FMT_HAS_CONSTEVAL 172 fmt::format(\"{:d}\", 42); 173 #endif 174 ") 175 expect_compile(format-string-number-spec-error " 176 #ifdef FMT_HAS_CONSTEVAL 177 fmt::format(\"{:d}\", \"I am not a number\"); 178 #else 179 #error 180 #endif 181 " ERROR) 182 expect_compile(print-string-number-spec-error " 183 #ifdef FMT_HAS_CONSTEVAL 184 fmt::print(\"{:d}\", \"I am not a number\"); 185 #else 186 #error 187 #endif 188 " ERROR) 189 expect_compile(print-stream-string-number-spec-error " 190 #ifdef FMT_HAS_CONSTEVAL 191 fmt::print(std::cout, \"{:d}\", \"I am not a number\"); 192 #else 193 #error 194 #endif 195 " ERROR) 196 197 # Compile-time argument name check 198 expect_compile(format-string-name " 199 #if defined(FMT_HAS_CONSTEVAL) && FMT_USE_NONTYPE_TEMPLATE_ARGS 200 using namespace fmt::literals; 201 fmt::print(\"{foo}\", \"foo\"_a=42); 202 #endif 203 ") 204 expect_compile(format-string-name-error " 205 #if defined(FMT_HAS_CONSTEVAL) && FMT_USE_NONTYPE_TEMPLATE_ARGS 206 using namespace fmt::literals; 207 fmt::print(\"{foo}\", \"bar\"_a=42); 208 #else 209 #error 210 #endif 211 " ERROR) 212endif () 213 214# Run all tests 215run_tests() 216