[libc++][format] Adds new test macros.
authorMark de Wever <koraq@xs4all.nl>
Sat, 17 Dec 2022 11:58:40 +0000 (12:58 +0100)
committerMark de Wever <koraq@xs4all.nl>
Wed, 18 Jan 2023 16:01:27 +0000 (17:01 +0100)
These macros make it easier to log additional information. This is
useful for formatting tests. It also properly disables additional
information when locales are disabled in libc++.

Reviewed By: #libc, ldionne

Differential Revision: https://reviews.llvm.org/D140651

libcxx/test/std/utilities/format/format.functions/escaped_output.ascii.pass.cpp
libcxx/test/std/utilities/format/format.functions/escaped_output.unicode.pass.cpp
libcxx/test/std/utilities/format/format.functions/format.locale.pass.cpp
libcxx/test/std/utilities/format/format.functions/format.pass.cpp
libcxx/test/std/utilities/format/format.functions/locale-specific_form.pass.cpp
libcxx/test/std/utilities/format/format.functions/vformat.locale.pass.cpp
libcxx/test/std/utilities/format/format.functions/vformat.pass.cpp
libcxx/test/std/utilities/format/format.tuple/format.functions.format.pass.cpp
libcxx/test/std/utilities/format/format.tuple/format.functions.vformat.pass.cpp
libcxx/test/support/assert_macros.h [new file with mode: 0644]

index 4145385..329124f 100644 (file)
@@ -24,6 +24,7 @@
 #include "test_macros.h"
 #include "make_string.h"
 #include "test_format_string.h"
+#include "assert_macros.h"
 
 #ifndef TEST_HAS_NO_LOCALIZATION
 #  include <iostream>
@@ -35,19 +36,9 @@ auto test_format = []<class CharT, class... Args>(
                        std::basic_string_view<CharT> expected, test_format_string<CharT, Args...> fmt, Args&&... args) {
   {
     std::basic_string<CharT> out = std::format(fmt, std::forward<Args>(args)...);
-#ifndef TEST_HAS_NO_LOCALIZATION
-    if (out != expected) {
-      if constexpr (std::same_as<CharT, char>)
-        std::cerr << "\nFormat string   " << fmt.get() << "\nExpected output " << expected << "\nActual output   "
-                  << out << '\n';
-#  ifndef TEST_HAS_NO_WIDE_CHARACTERS
-      else
-        std::wcerr << L"\nFormat string   " << fmt.get() << L"\nExpected output " << expected << L"\nActual output   "
-                   << out << L'\n';
-#  endif // TEST_HAS_NO_WIDE_CHARACTERS
-    }
-#endif // TEST_HAS_NO_LOCALIZATION
-    assert(out == expected);
+    TEST_REQUIRE(out == expected,
+                 test_concat_message(
+                     "\nFormat string   ", fmt.get(), "\nExpected output ", expected, "\nActual output   ", out, '\n'));
   }
 #ifndef TEST_HAS_NO_LOCALIZATION
   {
index 3514919..c940bcb 100644 (file)
@@ -30,6 +30,7 @@
 #include "test_macros.h"
 #include "make_string.h"
 #include "test_format_string.h"
+#include "assert_macros.h"
 
 #ifndef TEST_HAS_NO_LOCALIZATION
 #  include <iostream>
@@ -41,19 +42,9 @@ auto test_format = []<class CharT, class... Args>(
                        std::basic_string_view<CharT> expected, test_format_string<CharT, Args...> fmt, Args&&... args) {
   {
     std::basic_string<CharT> out = std::format(fmt, std::forward<Args>(args)...);
-#ifndef TEST_HAS_NO_LOCALIZATION
-    if (out != expected) {
-      if constexpr (std::same_as<CharT, char>)
-        std::cerr << "\nFormat string   " << fmt.get() << "\nExpected output " << expected << "\nActual output   "
-                  << out << '\n';
-#  ifndef TEST_HAS_NO_WIDE_CHARACTERS
-      else
-        std::wcerr << L"\nFormat string   " << fmt.get() << L"\nExpected output " << expected << L"\nActual output   "
-                   << out << L'\n';
-#  endif // TEST_HAS_NO_WIDE_CHARACTERS
-    }
-#endif // TEST_HAS_NO_LOCALIZATION
-    assert(out == expected);
+    TEST_REQUIRE(out == expected,
+                 test_concat_message(
+                     "\nFormat string   ", fmt.get(), "\nExpected output ", expected, "\nActual output   ", out, '\n'));
   }
 #ifndef TEST_HAS_NO_LOCALIZATION
   {
index 76d4b35..ab34558 100644 (file)
 #include "format_tests.h"
 #include "string_literal.h"
 #include "test_format_string.h"
+#include "assert_macros.h"
 
 auto test =
     []<class CharT, class... Args>(
         std::basic_string_view<CharT> expected, test_format_string<CharT, Args...> fmt, Args&&... args) constexpr {
       std::basic_string<CharT> out = std::format(std::locale(), fmt, std::forward<Args>(args)...);
-      if constexpr (std::same_as<CharT, char>)
-        if (out != expected)
-          std::cerr << "\nFormat string   " << fmt.get() << "\nExpected output " << expected << "\nActual output   "
-                    << out << '\n';
-      assert(out == expected);
+      TEST_REQUIRE(
+          out == expected,
+          test_concat_message(
+              "\nFormat string   ", fmt.get(), "\nExpected output ", expected, "\nActual output   ", out, '\n'));
     };
 
 auto test_exception = []<class CharT, class... Args>(std::string_view, std::basic_string_view<CharT>, Args&&...) {
index 2710078..ca9d1d9 100644 (file)
 #include "format_tests.h"
 #include "string_literal.h"
 #include "test_format_string.h"
-
-#ifndef TEST_HAS_NO_LOCALIZATION
-#  include <iostream>
-#  include <type_traits>
-#endif
+#include "assert_macros.h"
 
 auto test =
     []<class CharT, class... Args>(
         std::basic_string_view<CharT> expected, test_format_string<CharT, Args...> fmt, Args&&... args) constexpr {
       std::basic_string<CharT> out = std::format(fmt, std::forward<Args>(args)...);
-#ifndef TEST_HAS_NO_LOCALIZATION
-      if constexpr (std::same_as<CharT, char>)
-        if (out != expected)
-          std::cerr << "\nFormat string   " << fmt.get() << "\nExpected output " << expected << "\nActual output   "
-                    << out << '\n';
-#endif
-      assert(out == expected);
+      TEST_REQUIRE(
+          out == expected,
+          test_concat_message(
+              "\nFormat string   ", fmt.get(), "\nExpected output ", expected, "\nActual output   ", out, '\n'));
     };
 
 auto test_exception = []<class CharT, class... Args>(std::string_view, std::basic_string_view<CharT>, Args&&...) {
index 7f3f195..86d56da 100644 (file)
@@ -93,6 +93,7 @@
 #include "format_tests.h"
 #include "string_literal.h"
 #include "test_format_string.h"
+#include "assert_macros.h"
 
 #define STR(S) MAKE_STRING(CharT, S)
 #define SV(S) MAKE_STRING_VIEW(CharT, S)
@@ -127,11 +128,9 @@ void test(std::basic_string_view<CharT> expected, test_format_string<CharT, Args
   // *** format ***
   {
     std::basic_string<CharT> out = std::format(fmt, std::forward<Args>(args)...);
-    if constexpr (std::same_as<CharT, char>)
-      if (out != expected)
-        std::cerr << "\nFormat string   " << fmt.get() << "\nExpected output " << expected << "\nActual output   "
-                  << out << '\n';
-    assert(out == expected);
+    TEST_REQUIRE(out == expected,
+                 test_concat_message(
+                     "\nFormat string   ", fmt.get(), "\nExpected output ", expected, "\nActual output   ", out, '\n'));
   }
   // *** vformat ***
   {
index 11bf67f..90c56df 100644 (file)
 #include "test_macros.h"
 #include "format_tests.h"
 #include "string_literal.h"
+#include "assert_macros.h"
 
 auto test = []<class CharT, class... Args>(
                 std::basic_string_view<CharT> expected, std::basic_string_view<CharT> fmt, Args&&... args) constexpr {
   std::basic_string<CharT> out = std::vformat(std::locale(), fmt, std::make_format_args<context_t<CharT>>(args...));
-  assert(out == expected);
+  TEST_REQUIRE(
+      out == expected,
+      test_concat_message("\nFormat string   ", fmt, "\nExpected output ", expected, "\nActual output   ", out, '\n'));
 };
 
 auto test_exception =
@@ -37,10 +40,13 @@ auto test_exception =
         [[maybe_unused]] Args&&... args) {
 #ifndef TEST_HAS_NO_EXCEPTIONS
       try {
-        (void)std::vformat(std::locale(), fmt, std::make_format_args<context_t<CharT>>(args...));
-        assert(false);
+        TEST_IGNORE_NODISCARD std::vformat(std::locale(), fmt, std::make_format_args<context_t<CharT>>(args...));
+        TEST_FAIL(test_concat_message("\nFormat string   ", fmt, "\nDidn't throw an exception.\n"));
       } catch ([[maybe_unused]] const std::format_error& e) {
-        LIBCPP_ASSERT(e.what() == what);
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            test_concat_message(
+                "\nFormat string   ", fmt, "\nExpected exception ", what, "\nActual exception   ", e.what(), '\n'));
         return;
       }
       assert(false);
index d115615..236d83e 100644 (file)
 #include "test_macros.h"
 #include "format_tests.h"
 #include "string_literal.h"
+#include "assert_macros.h"
 
 auto test = []<class CharT, class... Args>(
                 std::basic_string_view<CharT> expected, std::basic_string_view<CharT> fmt, Args&&... args) constexpr {
   std::basic_string<CharT> out = std::vformat(fmt, std::make_format_args<context_t<CharT>>(args...));
-  assert(out == expected);
+  TEST_REQUIRE(
+      out == expected,
+      test_concat_message("\nFormat string   ", fmt, "\nExpected output ", expected, "\nActual output   ", out, '\n'));
 };
 
 auto test_exception =
@@ -37,9 +40,13 @@ auto test_exception =
 #ifndef TEST_HAS_NO_EXCEPTIONS
       try {
         TEST_IGNORE_NODISCARD std::vformat(fmt, std::make_format_args<context_t<CharT>>(args...));
-        assert(false);
+        TEST_FAIL(test_concat_message("\nFormat string   ", fmt, "\nDidn't throw an exception.\n"));
       } catch ([[maybe_unused]] const std::format_error& e) {
-        LIBCPP_ASSERT(e.what() == what);
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            test_concat_message(
+                "\nFormat string   ", fmt, "\nExpected exception ", what, "\nActual exception   ", e.what(), '\n'));
+
         return;
       }
       assert(false);
index 72f8430..d6f9bd3 100644 (file)
 #include "format.functions.tests.h"
 #include "test_format_string.h"
 #include "test_macros.h"
-
-#ifndef TEST_HAS_NO_LOCALIZATION
-#  include <iostream>
-#  include <concepts>
-#endif
+#include "assert_macros.h"
 
 auto test = []<class CharT, class... Args>(
                 std::basic_string_view<CharT> expected, test_format_string<CharT, Args...> fmt, Args&&... args) {
   std::basic_string<CharT> out = std::format(fmt, std::forward<Args>(args)...);
-#ifndef TEST_HAS_NO_LOCALIZATION
-  if constexpr (std::same_as<CharT, char>)
-    if (out != expected)
-      std::cerr << "\nFormat string   " << fmt.get() << "\nExpected output " << expected << "\nActual output   " << out
-                << '\n';
-#endif // TEST_HAS_NO_LOCALIZATION
-  assert(out == expected);
+  TEST_REQUIRE(
+      out == expected,
+      test_concat_message("\nFormat string   ", fmt, "\nExpected output ", expected, "\nActual output   ", out, '\n'));
 };
 
 auto test_exception = []<class CharT, class... Args>(std::string_view, std::basic_string_view<CharT>, Args&&...) {
index 7c9540c..6853134 100644 (file)
 
 #include "test_macros.h"
 #include "format.functions.tests.h"
-
-#ifndef TEST_HAS_NO_LOCALIZATION
-#  include <iostream>
-#  include <concepts>
-#endif
+#include "assert_macros.h"
 
 auto test = []<class CharT, class... Args>(
                 std::basic_string_view<CharT> expected, std::basic_string_view<CharT> fmt, Args&&... args) {
   std::basic_string<CharT> out = std::vformat(fmt, std::make_format_args<context_t<CharT>>(args...));
-#ifndef TEST_HAS_NO_LOCALIZATION
-  if constexpr (std::same_as<CharT, char>)
-    if (out != expected)
-      std::cerr << "\nFormat string   " << fmt << "\nExpected output " << expected << "\nActual output   " << out
-                << '\n';
-#endif // TEST_HAS_NO_LOCALIZATION
-  assert(out == expected);
+  TEST_REQUIRE(
+      out == expected,
+      test_concat_message("\nFormat string   ", fmt, "\nExpected output ", expected, "\nActual output   ", out, '\n'));
 };
 
 auto test_exception =
@@ -55,22 +47,13 @@ auto test_exception =
 #ifndef TEST_HAS_NO_EXCEPTIONS
       try {
         TEST_IGNORE_NODISCARD std::vformat(fmt, std::make_format_args<context_t<CharT>>(args...));
-#  if !defined(TEST_HAS_NO_LOCALIZATION)
-        if constexpr (std::same_as<CharT, char>)
-          std::cerr << "\nFormat string   " << fmt << "\nDidn't throw an exception.\n";
-#  endif //  !defined(TEST_HAS_NO_LOCALIZATION
-        assert(false);
+        TEST_FAIL(test_concat_message("\nFormat string   ", fmt, "\nDidn't throw an exception.\n"));
       } catch ([[maybe_unused]] const std::format_error& e) {
-#  if defined(_LIBCPP_VERSION)
-#    if !defined(TEST_HAS_NO_LOCALIZATION)
-        if constexpr (std::same_as<CharT, char>) {
-          if (e.what() != what)
-            std::cerr << "\nFormat string   " << fmt << "\nExpected exception " << what << "\nActual exception   "
-                      << e.what() << '\n';
-        }
-#    endif // !defined(TEST_HAS_NO_LOCALIZATION
-        assert(e.what() == what);
-#  endif   // defined(_LIBCPP_VERSION)
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            test_concat_message(
+                "\nFormat string   ", fmt, "\nExpected exception ", what, "\nActual exception   ", e.what(), '\n'));
+
         return;
       }
       assert(false);
diff --git a/libcxx/test/support/assert_macros.h b/libcxx/test/support/assert_macros.h
new file mode 100644 (file)
index 0000000..5d5d810
--- /dev/null
@@ -0,0 +1,106 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef TEST_SUPPORT_ASSERT_MACROS_H
+#define TEST_SUPPORT_ASSERT_MACROS_H
+
+// Contains a set of validation macros.
+//
+// Note these test were added after C++20 was well supported by the compilers
+// used. To make the implementation simple the macros require C++20 or newer.
+// It's not expected that existing tests start to use these new macros.
+//
+// These macros are an alternative to using assert. The differences are:
+// - The assert message isn't localized.
+// - It's possible to log additional information. This is useful when the
+//   function asserting is a helper function. In these cases the assertion
+//   failure contains to little information to find the issue. For example, in
+//   the format functions, the really useful information is the actual output,
+//   the expected output, and the format string used. These macros allow
+//   logging additional arguments.
+
+#include "test_macros.h"
+
+#include <cstdio>
+#include <cstdlib>
+
+#ifndef TEST_HAS_NO_LOCALIZATION
+#  include <sstream>
+#endif
+
+#if TEST_STD_VER > 17
+
+#  ifndef TEST_HAS_NO_LOCALIZATION
+template <class T>
+concept test_char_streamable = requires(T&& value) { std::stringstream{} << std::forward<T>(value); };
+#  endif
+
+// If possible concatenates message for the assertion function, else returns a
+// default message. Not being able to stream is not considered and error. For
+// example, streaming to std::wcerr doesn't work properly in the CI. Therefore
+// the formatting tests should only stream to std::string_string.
+template <class... Args>
+std::string test_concat_message([[maybe_unused]] Args&&... args) {
+#  ifndef TEST_HAS_NO_LOCALIZATION
+  if constexpr ((test_char_streamable<Args> && ...)) {
+    std::stringstream sstr;
+    ((sstr << std::forward<Args>(args)), ...);
+    return sstr.str();
+  } else
+#  endif
+    return "Message discarded since it can't be streamed to std::cerr.\n";
+}
+
+#endif // TEST_STD_VER > 17
+
+// Logs the error and calls exit.
+//
+// It shows a generic assert like message including a custom message. This
+// message should end with a newline.
+[[noreturn]] void test_log_error(const char* condition, const char* file, int line, std::string&& message) {
+  const char* msg = condition ? "Assertion failure: " : "Unconditional failure:";
+  std::fprintf(stderr, "%s%s %s %d\n%s", msg, condition, file, line, message.c_str());
+  exit(EXIT_FAILURE);
+}
+
+inline void test_fail(const char* file, int line, std::string&& message) {
+  test_log_error("", file, line, std::move(message));
+}
+
+inline void test_require(bool condition, const char* condition_str, const char* file, int line, std::string&& message) {
+  if (condition)
+    return;
+
+  test_log_error(condition_str, file, line, std::move(message));
+}
+
+inline void test_libcpp_require(
+    [[maybe_unused]] bool condition,
+    [[maybe_unused]] const char* condition_str,
+    [[maybe_unused]] const char* file,
+    [[maybe_unused]] int line,
+    [[maybe_unused]] std::string&& message) {
+#if defined(_LIBCPP_VERSION)
+  test_require(condition, condition_str, file, line, std::move(message));
+#endif
+}
+
+// assert(false) replacement
+#define TEST_FAIL(MSG) ::test_fail(__FILE__, __LINE__, MSG)
+
+// assert replacement.
+#define TEST_REQUIRE(CONDITION, MSG) ::test_require(CONDITION, #CONDITION, __FILE__, __LINE__, MSG)
+
+// LIBCPP_ASSERT replacement
+//
+// This requirement is only tested when the test suite is used for libc++.
+// This allows checking libc++ specific requirements, for example the error
+// messages of exceptions.
+#define TEST_LIBCPP_REQUIRE(CONDITION, MSG) ::test_libcpp_require(CONDITION, #CONDITION, __FILE__, __LINE__, MSG)
+
+#endif // TEST_SUPPORT_ASSERT_MACROS_H