CMakeLists.txt 6.7 KB

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