xref: /aosp_15_r20/external/fmtlib/test/compile-error-test/CMakeLists.txt (revision 5c90c05cd622c0a81b57953a4d343e0e489f2e08)
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