From c11fa45e49fa1ab0ce8c582c0cba5a3d02ecd40c Mon Sep 17 00:00:00 2001 From: Paul Adenot Date: Mon, 21 Oct 2024 12:54:56 +0000 Subject: [PATCH] Bug 1717448 - Add some tests for Gecko integration. r=glandium Differential Revision: https://phabricator.services.mozilla.com/D217299 --- mozglue/tests/gtest/TestFmt.cpp | 214 ++++++++++++++++++++++++++++++++ mozglue/tests/gtest/moz.build | 3 +- 2 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 mozglue/tests/gtest/TestFmt.cpp diff --git a/mozglue/tests/gtest/TestFmt.cpp b/mozglue/tests/gtest/TestFmt.cpp new file mode 100644 index 000000000000..c86f49a97b20 --- /dev/null +++ b/mozglue/tests/gtest/TestFmt.cpp @@ -0,0 +1,214 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "fmt/format.h" +#include "fmt/xchar.h" +#include "mozilla/ArrayUtils.h" +#include "nsTArray.h" +#include "nsFmtString.h" +#include "nsString.h" +#include "mozilla/Sprintf.h" + +#define snprintf(...) + +namespace tfformat { +#include "../glibc_printf_tests/tfformat.c" +} + +#undef snprintf + +nsCString PrintfToFmtFormat(const char* aPrintfFormatString) { + nsCString fmtFormat; + fmtFormat.AppendASCII("{"); + fmtFormat.AppendASCII(aPrintfFormatString); + // {fmt} uses < to left align, while printf traditionally use -. + // The order between the sign forcing ("+") or zero-padding ("0") and the + // alignment ("-" or "<") is also reversed. + fmtFormat.ReplaceSubstring("+-", "<+"); + fmtFormat.ReplaceSubstring("0-", "<0"); + // {fmt} doesn't support e.g. %4.f to denote 4 digits of integer part, and + // zero digit of fractional part. Simply replace them by the explicit form + // with a 0. + fmtFormat.ReplaceSubstring(".f", ".0f"); + fmtFormat.ReplaceSubstring(".F", ".0F"); + fmtFormat.ReplaceSubstring(".e", ".0e"); + fmtFormat.ReplaceSubstring(".E", ".0E"); + fmtFormat.ReplaceSubstring(".G", ".0G"); + fmtFormat.ReplaceSubstring(".g", ".0g"); + fmtFormat.ReplaceChar('%', ':'); + fmtFormat.AppendASCII("}"); + return fmtFormat; +} + +TEST(Fmt, CrossCheckPrintf) +{ + char bufGeckoPrintf[1024] = {}; + // - 1 because the last item is just a zero + for (uint32_t i = 2; i < std::size(tfformat::sprint_doubles) - 1; i++) { + if (strstr(tfformat::sprint_doubles[i].format_string, "#") || + strstr(tfformat::sprint_doubles[i].format_string, "a")) { + // Gecko's printf doesn't implement the '#' specifier nor 'a' conversion + // specifier (hex notation), but {fmt} does. + // Skip this test-case for the cross-check. + continue; + } + nsCString fmtFormat = + PrintfToFmtFormat(tfformat::sprint_doubles[i].format_string); + nsCString withFmt; + withFmt.AppendFmt(fmtFormat.get(), tfformat::sprint_doubles[i].value); + SprintfBuf(bufGeckoPrintf, 1024, tfformat::sprint_doubles[i].format_string, + tfformat::sprint_doubles[i].value); + if (strcmp(bufGeckoPrintf, withFmt.get()) != 0) { + fprintf(stderr, "Conversion index %d: %lf, %s -> %s\n", i, + tfformat::sprint_doubles[i].value, + tfformat::sprint_doubles[i].format_string, fmtFormat.get()); + fprintf(stderr, "DIFF: |%s|\n vs. |%s|\n", bufGeckoPrintf, + withFmt.get()); + } + ASSERT_STREQ(bufGeckoPrintf, withFmt.get()); + } +} + +TEST(Fmt, Sequences) +{ + char bufFmt[1024] = {}; + { + nsTArray array(4); + for (uint32_t i = 0; i < 4; i++) { + array.AppendElement(i); + } + auto [out, size] = + fmt::format_to(bufFmt, FMT_STRING("{}"), fmt::join(array, ", ")); + *out = 0; + ASSERT_STREQ("0, 1, 2, 3", bufFmt); + } + { + nsTArray array(4); + for (uint32_t i = 0; i < 4; i++) { + array.AppendElement((123 * 5 * (i + 1)) % 255); + } + auto [out, size] = + fmt::format_to(bufFmt, FMT_STRING("{:#04x}"), fmt::join(array, ", ")); + *out = 0; + ASSERT_STREQ("0x69, 0xd2, 0x3c, 0xa5", bufFmt); + } +} + +struct POD { + double mA; + uint64_t mB; +}; + +struct POD2 { + double mA; + uint64_t mB; +}; + +template <> +class fmt::formatter : public formatter { + public: + auto format(const POD& aPod, format_context& aCtx) const { + std::string temp; + format_to(std::back_inserter(temp), FMT_STRING("POD: mA: {}, mB: {}"), + aPod.mA, aPod.mB); + return formatter::format(temp, aCtx); + } +}; + +auto format_as(POD2 aInstance) -> std::string { + return fmt::format(FMT_STRING("POD2: mA: {}, mB: {}"), aInstance.mA, + aInstance.mB); +} + +TEST(Fmt, PodPrint) +{ + char bufFmt[1024] = {}; + + POD p{4.33, 8}; + POD2 p2{4.33, 8}; + { + auto [out, size] = fmt::format_to(bufFmt, FMT_STRING("{}"), p); + *out = 0; + ASSERT_STREQ("POD: mA: 4.33, mB: 8", bufFmt); + } + + { + auto [out, size] = fmt::format_to(bufFmt, FMT_STRING("{:>30}"), p); + *out = 0; + ASSERT_STREQ(" POD: mA: 4.33, mB: 8", bufFmt); + } + + { + auto [out, size] = fmt::format_to(bufFmt, FMT_STRING("{:>30}"), p2); + *out = 0; + ASSERT_STREQ(" POD2: mA: 4.33, mB: 8", bufFmt); + } +} + +TEST(Fmt, nsString) +{ + { + nsFmtCString str(FMT_STRING("{} {} {}"), 4, 4.3, " end"); + ASSERT_STREQ("4 4.3 end", str.get()); + } + { + nsFmtString str(FMT_STRING(u"Étonnant {} {} {}"), u"Étienne", 4, 4.3); + ASSERT_STREQ("Étonnant Étienne 4 4.3", NS_ConvertUTF16toUTF8(str).get()); + } + { + nsString str; + str.AppendFmt(FMT_STRING(u"Étonnant {} {} {}"), u"Étienne", 4, 4.3); + ASSERT_STREQ("Étonnant Étienne 4 4.3", NS_ConvertUTF16toUTF8(str).get()); + } + { + nsCString str; + str.AppendFmt(FMT_STRING("{} {} {}"), 4, 4.3, " end"); + ASSERT_STREQ("4 4.3 end", str.get()); + } +} + +TEST(Fmt, Truncation) +{ + char too_short_buf[16]; + const char* too_long_buf = "asdasdlkasjdashdkajhsdkhaksdjhasd"; + { + auto [out, truncated] = + fmt::format_to(too_short_buf, FMT_STRING("{}"), too_long_buf); + assert(truncated); + // Overwrite the last char for printing + too_short_buf[15] = 0; + fmt::print(FMT_STRING("{} {} {}\n"), too_short_buf, fmt::ptr(out), + truncated); + } + { + auto [out, size] = fmt::format_to_n(too_short_buf, sizeof(too_short_buf), + FMT_STRING("{}"), too_long_buf); + assert(size > sizeof(too_short_buf)); + too_short_buf[15] = 0; + fmt::print(FMT_STRING("{} {} {}\n"), too_short_buf, fmt::ptr(out), size); + } +} + +TEST(Fmt, NullString) +{ + char str[16]; + auto [out, size] = fmt::format_to(str, FMT_STRING("{}"), (char*)nullptr); + *out = 0; + ASSERT_STREQ("(null)", str); +} + +// ASAN intercepts the underlying fwrite and crashes. +#if defined(__has_feature) +# if not(__has_feature(address_sanitizer)) +TEST(Fmt, IOError) +{ + FILE* duped = fdopen(dup(fileno(stderr)), "w"); + fclose(duped); + fmt::println(duped, FMT_STRING("Hi {}"), 14); +} +# endif +#endif diff --git a/mozglue/tests/gtest/moz.build b/mozglue/tests/gtest/moz.build index 8f6ba9ad7b30..2bffce2687fe 100644 --- a/mozglue/tests/gtest/moz.build +++ b/mozglue/tests/gtest/moz.build @@ -2,12 +2,13 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. +SOURCES += ["TestFmt.cpp"] + if CONFIG["OS_ARCH"] == "WINNT": SOURCES += [ "TestNativeNtGTest.cpp", ] - if not CONFIG["MOZ_TSAN"]: SOURCES += [ "TestStackWalk.cpp",