TestFmt.cpp (6518B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "gtest/gtest.h" 8 #include "fmt/format.h" 9 #include "fmt/xchar.h" 10 #include "nsTArray.h" 11 #include "nsFmtString.h" 12 #include "nsString.h" 13 #include "mozilla/Sprintf.h" 14 15 #define snprintf(...) 16 17 namespace tfformat { 18 #include "../glibc_printf_tests/tfformat.c" 19 } 20 21 #undef snprintf 22 23 nsCString PrintfToFmtFormat(const char* aPrintfFormatString) { 24 nsCString fmtFormat; 25 fmtFormat.AppendASCII("{"); 26 fmtFormat.AppendASCII(aPrintfFormatString); 27 // {fmt} uses < to left align, while printf traditionally use -. 28 // The order between the sign forcing ("+") or zero-padding ("0") and the 29 // alignment ("-" or "<") is also reversed. 30 fmtFormat.ReplaceSubstring("+-", "<+"); 31 fmtFormat.ReplaceSubstring("0-", "<0"); 32 // {fmt} doesn't support e.g. %4.f to denote 4 digits of integer part, and 33 // zero digit of fractional part. Simply replace them by the explicit form 34 // with a 0. 35 fmtFormat.ReplaceSubstring(".f", ".0f"); 36 fmtFormat.ReplaceSubstring(".F", ".0F"); 37 fmtFormat.ReplaceSubstring(".e", ".0e"); 38 fmtFormat.ReplaceSubstring(".E", ".0E"); 39 fmtFormat.ReplaceSubstring(".G", ".0G"); 40 fmtFormat.ReplaceSubstring(".g", ".0g"); 41 fmtFormat.ReplaceChar('%', ':'); 42 fmtFormat.AppendASCII("}"); 43 return fmtFormat; 44 } 45 46 TEST(Fmt, CrossCheckPrintf) 47 { 48 char bufGeckoPrintf[1024] = {}; 49 // - 1 because the last item is just a zero 50 for (uint32_t i = 2; i < std::size(tfformat::sprint_doubles) - 1; i++) { 51 if (strstr(tfformat::sprint_doubles[i].format_string, "#") || 52 strstr(tfformat::sprint_doubles[i].format_string, "a")) { 53 // Gecko's printf doesn't implement the '#' specifier nor 'a' conversion 54 // specifier (hex notation), but {fmt} does. 55 // Skip this test-case for the cross-check. 56 continue; 57 } 58 nsCString fmtFormat = 59 PrintfToFmtFormat(tfformat::sprint_doubles[i].format_string); 60 nsCString withFmt; 61 withFmt.AppendVfmt(fmtFormat.get(), fmt::make_format_args( 62 tfformat::sprint_doubles[i].value)); 63 SprintfBuf(bufGeckoPrintf, 1024, tfformat::sprint_doubles[i].format_string, 64 tfformat::sprint_doubles[i].value); 65 if (strcmp(bufGeckoPrintf, withFmt.get()) != 0) { 66 fprintf(stderr, "Conversion index %d: %lf, %s -> %s\n", i, 67 tfformat::sprint_doubles[i].value, 68 tfformat::sprint_doubles[i].format_string, fmtFormat.get()); 69 fprintf(stderr, "DIFF: |%s|\n vs. |%s|\n", bufGeckoPrintf, 70 withFmt.get()); 71 } 72 ASSERT_STREQ(bufGeckoPrintf, withFmt.get()); 73 } 74 } 75 76 TEST(Fmt, Sequences) 77 { 78 char bufFmt[1024] = {}; 79 { 80 nsTArray<int> array(4); 81 for (uint32_t i = 0; i < 4; i++) { 82 array.AppendElement(i); 83 } 84 auto [out, size] = 85 fmt::format_to(bufFmt, FMT_STRING("{}"), fmt::join(array, ", ")); 86 *out = 0; 87 ASSERT_STREQ("0, 1, 2, 3", bufFmt); 88 } 89 { 90 nsTArray<uint8_t> array(4); 91 for (uint32_t i = 0; i < 4; i++) { 92 array.AppendElement((123 * 5 * (i + 1)) % 255); 93 } 94 auto [out, size] = 95 fmt::format_to(bufFmt, FMT_STRING("{:#04x}"), fmt::join(array, ", ")); 96 *out = 0; 97 ASSERT_STREQ("0x69, 0xd2, 0x3c, 0xa5", bufFmt); 98 } 99 } 100 101 struct POD { 102 double mA; 103 uint64_t mB; 104 }; 105 106 struct POD2 { 107 double mA; 108 uint64_t mB; 109 }; 110 111 template <> 112 class fmt::formatter<POD> : public formatter<string_view> { 113 public: 114 auto format(const POD& aPod, format_context& aCtx) const { 115 std::string temp; 116 format_to(std::back_inserter(temp), FMT_STRING("POD: mA: {}, mB: {}"), 117 aPod.mA, aPod.mB); 118 return formatter<string_view>::format(temp, aCtx); 119 } 120 }; 121 122 auto format_as(POD2 aInstance) -> std::string { 123 return fmt::format(FMT_STRING("POD2: mA: {}, mB: {}"), aInstance.mA, 124 aInstance.mB); 125 } 126 127 TEST(Fmt, PodPrint) 128 { 129 char bufFmt[1024] = {}; 130 131 POD p{4.33, 8}; 132 POD2 p2{4.33, 8}; 133 { 134 auto [out, size] = fmt::format_to(bufFmt, FMT_STRING("{}"), p); 135 *out = 0; 136 ASSERT_STREQ("POD: mA: 4.33, mB: 8", bufFmt); 137 } 138 139 { 140 auto [out, size] = fmt::format_to(bufFmt, FMT_STRING("{:>30}"), p); 141 *out = 0; 142 ASSERT_STREQ(" POD: mA: 4.33, mB: 8", bufFmt); 143 } 144 145 { 146 auto [out, size] = fmt::format_to(bufFmt, FMT_STRING("{:>30}"), p2); 147 *out = 0; 148 ASSERT_STREQ(" POD2: mA: 4.33, mB: 8", bufFmt); 149 } 150 } 151 152 TEST(Fmt, nsString) 153 { 154 { 155 nsFmtCString str(FMT_STRING("{} {} {}"), 4, 4.3, " end"); 156 ASSERT_STREQ("4 4.3 end", str.get()); 157 } 158 { 159 nsFmtString str(FMT_STRING(u"Étonnant {} {} {}"), u"Étienne", 4, 4.3); 160 ASSERT_STREQ("Étonnant Étienne 4 4.3", NS_ConvertUTF16toUTF8(str).get()); 161 } 162 { 163 nsString str; 164 str.AppendFmt(FMT_STRING(u"Étonnant {} {} {}"), u"Étienne", 4, 4.3); 165 ASSERT_STREQ("Étonnant Étienne 4 4.3", NS_ConvertUTF16toUTF8(str).get()); 166 } 167 { 168 nsCString str; 169 str.AppendFmt(FMT_STRING("{} {} {}"), 4, 4.3, " end"); 170 ASSERT_STREQ("4 4.3 end", str.get()); 171 } 172 } 173 174 TEST(Fmt, Truncation) 175 { 176 char too_short_buf[16]; 177 const char* too_long_buf = "asdasdlkasjdashdkajhsdkhaksdjhasd"; 178 { 179 auto [out, truncated] = 180 fmt::format_to(too_short_buf, FMT_STRING("{}"), too_long_buf); 181 assert(truncated); 182 // Overwrite the last char for printing 183 too_short_buf[15] = 0; 184 fmt::print(FMT_STRING("{} {} {}\n"), too_short_buf, fmt::ptr(out), 185 truncated); 186 } 187 { 188 auto [out, size] = fmt::format_to_n(too_short_buf, sizeof(too_short_buf), 189 FMT_STRING("{}"), too_long_buf); 190 assert(size > sizeof(too_short_buf)); 191 too_short_buf[15] = 0; 192 fmt::print(FMT_STRING("{} {} {}\n"), too_short_buf, fmt::ptr(out), size); 193 } 194 } 195 196 TEST(Fmt, NullString) 197 { 198 char str[16]; 199 auto [out, size] = fmt::format_to(str, FMT_STRING("{}"), (char*)nullptr); 200 *out = 0; 201 ASSERT_STREQ("(null)", str); 202 } 203 204 // ASAN intercepts the underlying fwrite and crashes. 205 #if defined(__has_feature) 206 # if not(__has_feature(address_sanitizer)) 207 TEST(Fmt, IOError) 208 { 209 FILE* duped = fdopen(dup(fileno(stderr)), "w"); 210 fclose(duped); 211 // glibc will crash here on x86 Linux debug 212 # if defined(DEBUG) && defined(XP_LINUX) && !defined(__i386__) 213 fmt::println(duped, FMT_STRING("Hi {}"), 14); 214 # endif 215 } 216 # endif 217 #endif