/* 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 "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.AppendVfmt(fmtFormat.get(), fmt::make_format_args(
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<int> array(
4);
for (uint32_t i =
0; i <
4; i++) {
array.AppendElement(i);
}
auto [out, size] = fmt::format_to(bufFmt, "{}", fmt::join(array, ", "));
*out =
0;
ASSERT_STREQ("
0,
1,
2,
3", bufFmt);
}
{
nsTArray<uint8_t> array(
4);
for (uint32_t i =
0; i <
4; i++) {
array.AppendElement((
123 *
5 * (i +
1)) %
255);
}
auto [out, size] =
fmt::format_to(bufFmt, "{:#
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<POD> : public formatter<string_view> {
public:
auto format(const POD& aPod, format_context& aCtx) const {
std::string temp;
fmt::format_to(std::back_inserter(temp), "POD: mA: {}, mB: {}", aPod.mA,
aPod.mB);
return formatter<string_view>::format(temp, aCtx);
}
};
auto format_as(POD2 aInstance) -> std::string {
return fmt::format("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, "{}", p);
*out =
0;
ASSERT_STREQ("POD: mA:
4.
33, mB:
8", bufFmt);
}
{
auto [out, size] = fmt::format_to(bufFmt, "{:>
30}", p);
*out =
0;
ASSERT_STREQ(" POD: mA:
4.
33, mB:
8", bufFmt);
}
{
auto [out, size] = fmt::format_to(bufFmt, "{:>
30}", p2);
*out =
0;
ASSERT_STREQ(" POD2: mA:
4.
33, mB:
8", bufFmt);
}
}
TEST(Fmt, nsString)
{
{
nsFmtCString str("{} {} {}",
4,
4.
3, " end");
ASSERT_STREQ("
4 4.
3 end", str.get());
}
{
nsFmtString str(u"Étonnant {} {} {}", u"Étienne",
4,
4.
3);
ASSERT_STREQ("Étonnant Étienne
4 4.
3", NS_ConvertUTF16toUTF8(str).get());
}
{
nsString str;
str.AppendFmt(u"Étonnant {} {} {}", u"Étienne",
4,
4.
3);
ASSERT_STREQ("Étonnant Étienne
4 4.
3", NS_ConvertUTF16toUTF8(str).get());
}
{
nsCString str;
str.AppendFmt("{} {} {}",
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, "{}", too_long_buf);
assert(truncated);
// Overwrite the last char for printing
too_short_buf[
15] =
0;
fmt::print("{} {} {}\n", too_short_buf, fmt::ptr(out), truncated);
}
{
auto [out, size] = fmt::format_to_n(too_short_buf, sizeof(too_short_buf),
"{}", too_long_buf);
assert(size > sizeof(too_short_buf));
too_short_buf[
15] =
0;
fmt::print("{} {} {}\n", too_short_buf, fmt::ptr(out), size);
}
}
TEST(Fmt, NullString)
{
char str[
16];
auto [out, size] = fmt::format_to(str, "{}", (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);
// glibc will crash here on x86 Linux debug
# if defined(DEBUG) && defined(XP_LINUX) && !defined(__i386__)
fmt::println(duped, "Hi {}",
14);
# endif
}
# endif
#endif