From 1e0ee96b8d6cf852f8881799881fb7dae7dab1f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Tue, 3 Mar 2026 21:23:54 -0800 Subject: [PATCH] bx::vsnprintf fixes. Added more unit tests. (#372) --- src/string.cpp | 130 ++++++++++-- tests/run_test.cpp | 6 +- tests/vsnprintf_test.cpp | 432 ++++++++++++++++++++++++++++++++------- 3 files changed, 475 insertions(+), 93 deletions(-) diff --git a/src/string.cpp b/src/string.cpp index ec467f0..e5b27d7 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -722,11 +722,13 @@ namespace bx , base(10) , prec(INT32_MAX) , fill(' ') + , fmt('f') , bits(0) , left(false) , upper(false) , spec(false) , sign(false) + , space(false) { } @@ -734,11 +736,13 @@ namespace bx int32_t base; int32_t prec; char fill; + char fmt; uint8_t bits; bool left; bool upper; bool spec; bool sign; + bool space; }; static int32_t write(WriterI* _writer, const char* _str, int32_t _len, const Param& _param, Error* _err) @@ -746,14 +750,12 @@ namespace bx int32_t size = 0; int32_t len = (int32_t)strLen(_str, _len); - if (_param.width > 0) - { - len = min(_param.width, len); - } - const bool hasMinus = (NULL != _str && '-' == _str[0]); - const bool hasSign = _param.sign || hasMinus; - char sign = hasSign ? hasMinus ? '-' : '+' : '\0'; + const bool hasSign = _param.sign || _param.space || hasMinus; + char sign = '\0'; + if (hasMinus) { sign = '-'; } + else if (_param.sign) { sign = '+'; } + else if (_param.space) { sign = ' '; } const char* str = _str; if (hasMinus) @@ -837,6 +839,64 @@ namespace bx return write(_writer, _str.getPtr(), min(_param.prec, _str.getLength() ), _param, _err); } + static int32_t writeInteger(WriterI* _writer, const char* _str, int32_t _len, const Param& _param, Error* _err) + { + Param param = _param; + + char str[64]; + memCopy(str, _str, _len + 1); + + int32_t len = _len; + + if (param.prec != INT32_MAX) + { + param.fill = ' '; + + const int32_t signLen = (len > 0 && '-' == str[0]) ? 1 : 0; + const int32_t digitLen = len - signLen; + + if (param.prec > digitLen) + { + const int32_t zeros = min(param.prec - digitLen, int32_t(sizeof(str)) - len - 1); + memMove(&str[signLen + zeros], &str[signLen], digitLen + 1); + + for (int32_t ii = 0; ii < zeros; ++ii) + { + str[signLen + ii] = '0'; + } + + len += zeros; + } + + param.prec = INT32_MAX; + } + + if (param.spec + && 0 < len) + { + if (8 == param.base) + { + if (str[0] != '0') + { + memMove(&str[1], &str[0], len + 1); + str[0] = '0'; + len++; + } + } + else if (16 == param.base) + { + memMove(&str[2], &str[0], len + 1); + str[0] = '0'; + str[1] = param.upper ? 'X' : 'x'; + len += 2; + } + + param.spec = false; + } + + return write(_writer, str, len, param, _err); + } + static int32_t write(WriterI* _writer, int32_t _i, const Param& _param, Error* _err) { char str[33]; @@ -847,7 +907,14 @@ namespace bx return 0; } - return write(_writer, str, len, _param, _err); + if (0 == _param.prec + && 0 == _i) + { + str[0] = '\0'; + len = 0; + } + + return writeInteger(_writer, str, len, _param, _err); } static int32_t write(WriterI* _writer, int64_t _i, const Param& _param, Error* _err) @@ -860,7 +927,14 @@ namespace bx return 0; } - return write(_writer, str, len, _param, _err); + if (0 == _param.prec + && 0 == _i) + { + str[0] = '\0'; + len = 0; + } + + return writeInteger(_writer, str, len, _param, _err); } static int32_t write(WriterI* _writer, uint32_t _u, const Param& _param, Error* _err) @@ -873,7 +947,14 @@ namespace bx return 0; } - return write(_writer, str, len, _param, _err); + if (0 == _param.prec + && 0 == _u) + { + str[0] = '\0'; + len = 0; + } + + return writeInteger(_writer, str, len, _param, _err); } static int32_t write(WriterI* _writer, uint64_t _u, const Param& _param, Error* _err) @@ -886,7 +967,14 @@ namespace bx return 0; } - return write(_writer, str, len, _param, _err); + if (0 == _param.prec + && 0 == _u) + { + str[0] = '\0'; + len = 0; + } + + return writeInteger(_writer, str, len, _param, _err); } static int32_t write(WriterI* _writer, double _d, const Param& _param, Error* _err) @@ -902,7 +990,9 @@ namespace bx const char* dot = strFind(str, INT32_MAX, '.'); if (NULL != dot) { - const int32_t prec = INT32_MAX == _param.prec ? 6 : _param.prec; + const int32_t defaultPrec = ('g' == _param.fmt) ? 6 : 6; + int32_t prec = INT32_MAX == _param.prec ? defaultPrec : _param.prec; + const char* strEnd = str + len; const char* exponent = strFind(str, INT32_MAX, 'e'); const char* fracEnd = NULL != exponent ? exponent : strEnd; @@ -992,11 +1082,11 @@ namespace bx switch (ch) { default: - case ' ': param.fill = ' '; break; - case '-': param.left = true; break; - case '+': param.sign = true; break; - case '0': param.fill = '0'; break; - case '#': param.spec = true; break; + case ' ': param.space = true; break; + case '-': param.left = true; break; + case '+': param.sign = true; break; + case '0': param.fill = '0'; break; + case '#': param.spec = true; break; } read(&reader, ch, &err); @@ -1043,7 +1133,8 @@ namespace bx if ('*' == ch) { read(&reader, ch, &err); - param.prec = va_arg(_argList, int32_t); + int32_t prec = va_arg(_argList, int32_t); + param.prec = prec >= 0 ? prec : INT32_MAX; } else { @@ -1150,10 +1241,11 @@ namespace bx }; break; - case 'e': case 'E': + case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': param.upper = isUpper(ch); + param.fmt = toLower(ch); size += write(_writer, va_arg(_argList, double), param, _err); break; diff --git a/tests/run_test.cpp b/tests/run_test.cpp index 5c66ddb..873a158 100644 --- a/tests/run_test.cpp +++ b/tests/run_test.cpp @@ -79,11 +79,13 @@ int runAllTests(int32_t _argc, const char* _argv[]) ConfigData config; config.defaultColourMode = BX_PLATFORM_EMSCRIPTEN ? ColourMode::None - : ColourMode::PlatformDefault + : ColourMode::PlatformDefault ; config.showDurations = ShowDurations::Always; session.useConfigData(config); - return session.run(_argc, _argv); + const int32_t result = session.run(_argc, _argv); + + return result; } diff --git a/tests/vsnprintf_test.cpp b/tests/vsnprintf_test.cpp index 4cd0424..3c218cf 100644 --- a/tests/vsnprintf_test.cpp +++ b/tests/vsnprintf_test.cpp @@ -159,67 +159,78 @@ TEST_CASE("Format %f", "[string][printf]") REQUIRE(test("0.0039", "%.4f", 0.00390625) ); REQUIRE(test("0.003906", "%f", 0.00390625) ); - REQUIRE(testNotStdCompliant("-1.234567e-9", "%f", -1.234567e-9) ); + REQUIRE(test("-1.234567e-9", "%f", -1.234567e-9) ); - REQUIRE(testNotStdCompliant("-1e-9", "%.0f", -1.234567e-9) ); - REQUIRE(testNotStdCompliant("-1.2e-9", "%.1f", -1.234567e-9) ); - REQUIRE(testNotStdCompliant("-1.23e-9", "%.2f", -1.234567e-9) ); - REQUIRE(testNotStdCompliant("-1.234e-9", "%.3f", -1.234567e-9) ); - REQUIRE(testNotStdCompliant("-1.2345e-9", "%.4f", -1.234567e-9) ); - REQUIRE(testNotStdCompliant("-1.23456e-9", "%.5f", -1.234567e-9) ); - REQUIRE(testNotStdCompliant("-1.234567e-9", "%.6f", -1.234567e-9) ); - REQUIRE(testNotStdCompliant("-1.2345670e-9", "%.7f", -1.234567e-9) ); - REQUIRE(testNotStdCompliant("-1.23456700e-9", "%.8f", -1.234567e-9) ); - REQUIRE(testNotStdCompliant("-1.234567000e-9", "%.9f", -1.234567e-9) ); - REQUIRE(testNotStdCompliant("-1.2345670000e-9", "%.10f", -1.234567e-9) ); + REQUIRE(test("-1e-9", "%.0f", -1.234567e-9) ); + REQUIRE(test("-1.2e-9", "%.1f", -1.234567e-9) ); + REQUIRE(test("-1.23e-9", "%.2f", -1.234567e-9) ); + REQUIRE(test("-1.234e-9", "%.3f", -1.234567e-9) ); + REQUIRE(test("-1.2345e-9", "%.4f", -1.234567e-9) ); + REQUIRE(test("-1.23456e-9", "%.5f", -1.234567e-9) ); + REQUIRE(test("-1.234567e-9", "%.6f", -1.234567e-9) ); + REQUIRE(test("-1.2345670e-9", "%.7f", -1.234567e-9) ); + REQUIRE(test("-1.23456700e-9", "%.8f", -1.234567e-9) ); + REQUIRE(test("-1.234567000e-9", "%.9f", -1.234567e-9) ); + REQUIRE(test("-1.2345670000e-9", "%.10f", -1.234567e-9) ); - REQUIRE(testNotStdCompliant("3.141592", "%f", 3.1415926535897932) ); - REQUIRE(testNotStdCompliant("3.141592", "%F", 3.1415926535897932) ); - REQUIRE(testNotStdCompliant("3", "%.0f", 3.1415926535897932) ); - REQUIRE(testNotStdCompliant("3.1", "%.1f", 3.1415926535897932) ); - REQUIRE(testNotStdCompliant("3.14", "%.2f", 3.1415926535897932) ); - REQUIRE(testNotStdCompliant("3.141", "%.3f", 3.1415926535897932) ); - REQUIRE(testNotStdCompliant("3.1415", "%.4f", 3.1415926535897932) ); - REQUIRE(testNotStdCompliant("3.14159", "%.5f", 3.1415926535897932) ); - REQUIRE(testNotStdCompliant("3.141592", "%.6f", 3.1415926535897932) ); - REQUIRE(testNotStdCompliant("3.1415926", "%.7f", 3.1415926535897932) ); - REQUIRE(testNotStdCompliant("3.14159265", "%.8f", 3.1415926535897932) ); - REQUIRE(testNotStdCompliant("3.141592653", "%.9f", 3.1415926535897932) ); - REQUIRE(testNotStdCompliant("3.1415926535", "%.10f", 3.1415926535897932) ); - REQUIRE(testNotStdCompliant("3.14159265358", "%.11f", 3.1415926535897932) ); - REQUIRE(testNotStdCompliant("3.141592653589", "%.12f", 3.1415926535897932) ); - REQUIRE(testNotStdCompliant("3.1415926535897", "%.13f", 3.1415926535897932) ); - REQUIRE(testNotStdCompliant("3.14159265358979", "%.14f", 3.1415926535897932) ); - REQUIRE(testNotStdCompliant("3.141592653589793", "%.15f", 3.1415926535897932) ); - REQUIRE(testNotStdCompliant("3.1415926535897930", "%.16f", 3.1415926535897932) ); - REQUIRE(testNotStdCompliant("3.1415926535897930", "%.16F", 3.1415926535897932) ); + REQUIRE(test("3.141592", "%f", 3.1415926535897932) ); + REQUIRE(test("3.141592", "%F", 3.1415926535897932) ); + REQUIRE(test("3", "%.0f", 3.1415926535897932) ); + REQUIRE(test("3.1", "%.1f", 3.1415926535897932) ); + REQUIRE(test("3.14", "%.2f", 3.1415926535897932) ); + REQUIRE(test("3.141", "%.3f", 3.1415926535897932) ); + REQUIRE(test("3.1415", "%.4f", 3.1415926535897932) ); + REQUIRE(test("3.14159", "%.5f", 3.1415926535897932) ); + REQUIRE(test("3.141592", "%.6f", 3.1415926535897932) ); + REQUIRE(test("3.1415926", "%.7f", 3.1415926535897932) ); + REQUIRE(test("3.14159265", "%.8f", 3.1415926535897932) ); + REQUIRE(test("3.141592653", "%.9f", 3.1415926535897932) ); + REQUIRE(test("3.1415926535", "%.10f", 3.1415926535897932) ); + REQUIRE(test("3.14159265358", "%.11f", 3.1415926535897932) ); + REQUIRE(test("3.141592653589", "%.12f", 3.1415926535897932) ); + REQUIRE(test("3.1415926535897", "%.13f", 3.1415926535897932) ); + REQUIRE(test("3.14159265358979", "%.14f", 3.1415926535897932) ); + REQUIRE(test("3.141592653589793", "%.15f", 3.1415926535897932) ); + REQUIRE(test("3.1415926535897930", "%.16f", 3.1415926535897932) ); + REQUIRE(test("3.1415926535897930", "%.16F", 3.1415926535897932) ); - REQUIRE(testNotStdCompliant("-3.141592e-9", "%f", -3.1415926535897932e-9) ); - REQUIRE(testNotStdCompliant("-3.141592E-9", "%F", -3.1415926535897932e-9) ); - REQUIRE(testNotStdCompliant("-3e-9", "%.0f", -3.1415926535897932e-9) ); - REQUIRE(testNotStdCompliant("-3.1e-9", "%.1f", -3.1415926535897932e-9) ); - REQUIRE(testNotStdCompliant("-3.14e-9", "%.2f", -3.1415926535897932e-9) ); - REQUIRE(testNotStdCompliant("-3.141e-9", "%.3f", -3.1415926535897932e-9) ); - REQUIRE(testNotStdCompliant("-3.1415e-9", "%.4f", -3.1415926535897932e-9) ); - REQUIRE(testNotStdCompliant("-3.14159e-9", "%.5f", -3.1415926535897932e-9) ); - REQUIRE(testNotStdCompliant("-3.141592e-9", "%.6f", -3.1415926535897932e-9) ); - REQUIRE(testNotStdCompliant("-3.1415926e-9", "%.7f", -3.1415926535897932e-9) ); - REQUIRE(testNotStdCompliant("-3.14159265e-9", "%.8f", -3.1415926535897932e-9) ); - REQUIRE(testNotStdCompliant("-3.141592653e-9", "%.9f", -3.1415926535897932e-9) ); - REQUIRE(testNotStdCompliant("-3.1415926535e-9", "%.10f", -3.1415926535897932e-9) ); - REQUIRE(testNotStdCompliant("-3.14159265358e-9", "%.11f", -3.1415926535897932e-9) ); - REQUIRE(testNotStdCompliant("-3.141592653589e-9", "%.12f", -3.1415926535897932e-9) ); - REQUIRE(testNotStdCompliant("-3.1415926535897e-9", "%.13f", -3.1415926535897932e-9) ); - REQUIRE(testNotStdCompliant("-3.14159265358979e-9", "%.14f", -3.1415926535897932e-9) ); - REQUIRE(testNotStdCompliant("-3.141592653589793e-9", "%.15f", -3.1415926535897932e-9) ); - REQUIRE(testNotStdCompliant("-3.1415926535897930e-9", "%.16f", -3.1415926535897932e-9) ); - REQUIRE(testNotStdCompliant("-3.1415926535897930E-9", "%.16F", -3.1415926535897932e-9) ); + REQUIRE(test("-3.141592e-9", "%f", -3.1415926535897932e-9) ); + REQUIRE(test("-3.141592E-9", "%F", -3.1415926535897932e-9) ); + REQUIRE(test("-3e-9", "%.0f", -3.1415926535897932e-9) ); + REQUIRE(test("-3.1e-9", "%.1f", -3.1415926535897932e-9) ); + REQUIRE(test("-3.14e-9", "%.2f", -3.1415926535897932e-9) ); + REQUIRE(test("-3.141e-9", "%.3f", -3.1415926535897932e-9) ); + REQUIRE(test("-3.1415e-9", "%.4f", -3.1415926535897932e-9) ); + REQUIRE(test("-3.14159e-9", "%.5f", -3.1415926535897932e-9) ); + REQUIRE(test("-3.141592e-9", "%.6f", -3.1415926535897932e-9) ); + REQUIRE(test("-3.1415926e-9", "%.7f", -3.1415926535897932e-9) ); + REQUIRE(test("-3.14159265e-9", "%.8f", -3.1415926535897932e-9) ); + REQUIRE(test("-3.141592653e-9", "%.9f", -3.1415926535897932e-9) ); + REQUIRE(test("-3.1415926535e-9", "%.10f", -3.1415926535897932e-9) ); + REQUIRE(test("-3.14159265358e-9", "%.11f", -3.1415926535897932e-9) ); + REQUIRE(test("-3.141592653589e-9", "%.12f", -3.1415926535897932e-9) ); + REQUIRE(test("-3.1415926535897e-9", "%.13f", -3.1415926535897932e-9) ); + REQUIRE(test("-3.14159265358979e-9", "%.14f", -3.1415926535897932e-9) ); + REQUIRE(test("-3.141592653589793e-9", "%.15f", -3.1415926535897932e-9) ); + REQUIRE(test("-3.1415926535897930e-9", "%.16f", -3.1415926535897932e-9) ); + REQUIRE(test("-3.1415926535897930E-9", "%.16F", -3.1415926535897932e-9) ); - REQUIRE(testNotStdCompliant("1e-12", "%f", 1e-12)); + REQUIRE(test("1e-12", "%f", 1e-12)); REQUIRE(test("0.00390625", "%.8f", 0.00390625) ); REQUIRE(test("-0.00390625", "%.8f", -0.00390625) ); REQUIRE(test("1.50000000000000000", "%.17f", 1.5) ); + + REQUIRE(test("-3.000000", "%f", -3.0) ); + REQUIRE(test("-0.000000", "%f", -0.) ); + REQUIRE(test("4.1", "%.1f", 4.1) ); + REQUIRE(test("0.00", "%.2f", 1e-4) ); + REQUIRE(test("-5.20", "%+4.2f", -5.2) ); + REQUIRE(test("0.0 ", "%-10.1f", 0.) ); + REQUIRE(test("-8.8888888800", "%.10f", -8.88888888) ); + REQUIRE(test("880.0888888800", "%.10f", 880.08888888) ); + REQUIRE(test("100056789.0", "%.1f", 100056789.0) ); + REQUIRE(test(" 0", "% .0f", 0.1) ); } TEST_CASE("Format %d, %i, %o, %u, %x", "[string][printf]") @@ -259,22 +270,15 @@ TEST_CASE("Format %d, %i, %o, %u, %x", "[string][printf]") REQUIRE(test("000000000000edcb5433", "%020x", -0x1234abcd) ); REQUIRE(test("000000000000EDCB5433", "%020X", -0x1234abcd) ); - REQUIRE(testNotStdCompliant("0xf", "0x%01x", -1) ); - REQUIRE(testNotStdCompliant("0xff", "0x%02x", -1) ); - REQUIRE(testNotStdCompliant("0xfff", "0x%03x", -1) ); - REQUIRE(testNotStdCompliant("0xffff", "0x%04x", -1) ); - REQUIRE(testNotStdCompliant("0xfffff", "0x%05x", -1) ); - REQUIRE(testNotStdCompliant("0xffffff", "0x%06x", -1) ); - REQUIRE(testNotStdCompliant("0xfffffff", "0x%07x", -1) ); - REQUIRE(testNotStdCompliant("0xffffffff", "0x%08x", -1) ); + REQUIRE(test("0xffffffff", "0x%01x", -1) ); + REQUIRE(test("0xffffffff", "0x%08x", -1) ); - REQUIRE(test(" -1", "% 4i", -1) ); REQUIRE(test(" -1", "% 4i", -1) ); REQUIRE(test(" 0", "% 4i", 0) ); REQUIRE(test(" 1", "% 4i", 1) ); REQUIRE(test(" 1", "% 4o", 1) ); REQUIRE(test(" +1", "%+4i", 1) ); - REQUIRE(testNotStdCompliant(" +1", "%+4o", 1) ); + REQUIRE(test(" +1", "%+4o", 1) ); REQUIRE(test(" +0", "%+4i", 0) ); REQUIRE(test(" -1", "%+4i", -1) ); REQUIRE(test("0001", "%04i", 1) ); @@ -295,6 +299,13 @@ TEST_CASE("Format %d, %i, %o, %u, %x", "[string][printf]") REQUIRE(test("18446744073709551615", "%" PRIu64, UINT64_MAX) ); REQUIRE(test("ffffffffffffffff", "%016" PRIx64, UINT64_MAX) ); + + REQUIRE(test("33 555", "%hi %ld", (short)33, 555l) ); + REQUIRE(test("20 0020", "%u %04u", 20u, 20u) ); + + REQUIRE(test("012 0x1e 0X3C", "%#o %#x %#X", 10u, 30u, 60u) ); + REQUIRE(test(" 12 1e 3C ", "%3o %2x %-3X", 10u, 30u, 60u) ); + REQUIRE(test("12 1e 3C", "%o %x %X", 10u, 30u, 60u) ); } TEST_CASE("Format modifiers", "[string][printf]") @@ -363,23 +374,15 @@ TEST_CASE("Format %g", "[string][printf]") REQUIRE(test(" 0.0123", "%7.4G", .0123) ); // REQUIRE(test("1.23e+05", "%.3g", 123000.25) ); // REQUIRE(test("1e+05", "%.0g", 123000.25) ); + REQUIRE(test("1", "%.0g", 1.2) ); } TEST_CASE("Format %c, %s, %S", "[string][printf]") { - REQUIRE(test("x", "%c", 'x') ); - REQUIRE(test("x ", "%-20c", 'x') ); - REQUIRE(test("hello ", "%-20s", "hello") ); REQUIRE(test(" hello", "%10s", "hello") ); REQUIRE(test("hello, world!", "%s, %s!", "hello", "world") ); - REQUIRE(testNotStdCompliant("h", "%1s", "hello") ); - REQUIRE(testNotStdCompliant("he", "%2s", "hello") ); - REQUIRE(testNotStdCompliant("hel", "%3s", "hello") ); - REQUIRE(testNotStdCompliant("hell", "%4s", "hello") ); - REQUIRE(testNotStdCompliant("hello", "%5s", "hello") ); - bx::StringView str("0hello1world2"); bx::StringView hello(str, 1, 5); bx::StringView world(str, 7, 5); @@ -420,3 +423,288 @@ TEST_CASE("Invalid", "[string][printf]") REQUIRE(0 == bx::snprintf(temp, sizeof(temp), "%-03.0", 1) ); REQUIRE(0 == bx::snprintf(temp, sizeof(temp), "%-03.0t", 1) ); } + +TEST_CASE("Format %s width does not truncate", "[string][printf]") +{ + SECTION("Width smaller than string length must NOT truncate") + { + REQUIRE(test("hello", "%1s", "hello") ); + REQUIRE(test("hello", "%5s", "hello") ); + REQUIRE(test(" hello", "%6s", "hello") ); + REQUIRE(test(" hello", "%7s", "hello") ); + } + + SECTION("Left-aligned, width smaller than string length") + { + REQUIRE(test("hello", "%-1s", "hello") ); + REQUIRE(test("hello ", "%-6s", "hello") ); + REQUIRE(test("hello ", "%-7s", "hello") ); + } +} + +TEST_CASE("Format %s precision truncates", "[string][printf]") +{ + SECTION("Precision DOES truncate strings") + { + REQUIRE(test("", "%.0s", "hello") ); + REQUIRE(test("h", "%.1s", "hello") ); + REQUIRE(test("he", "%.2s", "hello") ); + REQUIRE(test("hel", "%.3s", "hello") ); + REQUIRE(test("hell", "%.4s", "hello") ); + REQUIRE(test("hello", "%.5s", "hello") ); + REQUIRE(test("hello", "%.10s", "hello") ); + } +} + +TEST_CASE("Format %s width and precision combined", "[string][printf]") +{ + SECTION("Width pads, precision truncates") + { + REQUIRE(test(" h", "%10.1s", "hello") ); + REQUIRE(test(" he", "%10.2s", "hello") ); + REQUIRE(test(" hel", "%10.3s", "hello") ); + REQUIRE(test(" hello", "%10.5s", "hello") ); + } + + SECTION("Left-aligned with width and precision") + { + REQUIRE(test("h ", "%-10.1s", "hello") ); + REQUIRE(test("he ", "%-10.2s", "hello") ); + REQUIRE(test("hel ", "%-10.3s", "hello") ); + REQUIRE(test("hello ", "%-10.5s", "hello") ); + } + + SECTION("Width smaller than precision and string") + { + REQUIRE(test("hel", "%1.3s", "hello") ); + REQUIRE(test("hel", "%-1.3s", "hello") ); + } + + SECTION("Precision with * wildcard") + { + REQUIRE(test("hel", "%.*s", 3, "hello") ); + REQUIRE(test("hello", "%.*s", 10, "hello") ); + } +} + +TEST_CASE("Format %s empty string", "[string][printf]") +{ + REQUIRE(test("", "%s", "") ); + REQUIRE(test(" ", "%10s", "") ); + REQUIRE(test(" ", "%-10s", "") ); + REQUIRE(test("", "%.0s", "hello") ); + REQUIRE(test(" ", "%10.0s", "hello") ); +} + +TEST_CASE("Format %d width does not truncate", "[string][printf]") +{ + SECTION("Width smaller than number of digits must NOT truncate") + { + REQUIRE(test("1337", "%1d", 1337) ); + REQUIRE(test("1337", "%4d", 1337) ); + REQUIRE(test(" 1337", "%5d", 1337) ); + REQUIRE(test(" 1337", "%6d", 1337) ); + + REQUIRE(test("-1337", "%1d", -1337) ); + REQUIRE(test("-1337", "%5d", -1337) ); + REQUIRE(test(" -1337", "%6d", -1337) ); + } +} + +TEST_CASE("Format %x width does not truncate", "[string][printf]") +{ + REQUIRE(test("abcd", "%1x", 0xabcd) ); + REQUIRE(test("abcd", "%4x", 0xabcd) ); + REQUIRE(test(" abcd", "%5x", 0xabcd) ); + REQUIRE(test("ABCD", "%1X", 0xabcd) ); +} + +TEST_CASE("Format %% percent literal", "[string][printf]") +{ + REQUIRE(test("%", "%%") ); + REQUIRE(test("100%", "100%%") ); + REQUIRE(test("%%", "%%%%") ); + REQUIRE(test("a%b", "a%%b") ); + REQUIRE(test("%42%", "%%%d%%", 42) ); +} + +TEST_CASE("Format %d edge values", "[string][printf]") +{ + REQUIRE(test("0", "%d", 0) ); + REQUIRE(test("2147483647", "%d", INT32_MAX) ); + REQUIRE(test("-2147483648", "%d", INT32_MIN) ); + REQUIRE(test("+2147483647", "%+d", INT32_MAX) ); + + REQUIRE(test("0", "%u", 0u) ); + REQUIRE(test("4294967295", "%u", UINT32_MAX) ); + + REQUIRE(test("0", "%x", 0) ); + REQUIRE(test("ffffffff", "%x", UINT32_MAX) ); + REQUIRE(test("FFFFFFFF", "%X", UINT32_MAX) ); + + REQUIRE(test("-100006789", "%d", -100006789) ); +} + +TEST_CASE("Format mixed specifiers", "[string][printf]") +{ + REQUIRE(test("hello 42 3.14 world", "%s %d %.2f %s", "hello", 42, 3.14, "world") ); + REQUIRE(test("a1b2c3", "a%db%dc%d", 1, 2, 3) ); + REQUIRE(test("0xff = 255", "0x%x = %u", 255, 255u) ); +} + +TEST_CASE("Format %c edge cases", "[string][printf]") +{ + REQUIRE(test("A", "%c", 'A') ); + REQUIRE(test(" A", "%2c", 'A') ); + REQUIRE(test(" A", "%10c", 'A') ); + REQUIRE(test("A ", "%-10c", 'A') ); +} + +TEST_CASE("misc", "[string][printf]") +{ + REQUIRE(test("53000atest-20 bit", "%u%u%ctest%d %s", 5, 3000, 'a', -20, "bit") ); + REQUIRE(test("0.33", "%.*f", 2, 0.33333333) ); + REQUIRE(test("1", "%.*d", -1, 1) ); + REQUIRE(test(" ", "% .0d", 0) ); + REQUIRE(test(" 00004", "%10.5d", 4) ); + REQUIRE(test("hi x", "%*sx", -3, "hi") ); + REQUIRE(test("0.33", "%.*g", 2, 0.33333333) ); +// REQUIRE(test("3.33e-01", "%.*e", 2, 0.33333333) ); + REQUIRE(test("", "") ); +} + +TEST_CASE("Integer precision", "[string][printf]") +{ + SECTION("Precision zero suppresses zero-value output") + { + REQUIRE(test("", "%.0d", 0) ); + REQUIRE(test("", "%.0i", 0) ); + REQUIRE(test("", "%.0u", 0u) ); + REQUIRE(test("", "%.0x", 0) ); + REQUIRE(test("", "%.0o", 0) ); + REQUIRE(test("", "%.d", 0) ); + REQUIRE(test("", "%.i", 0) ); + REQUIRE(test("", "%.u", 0u) ); + REQUIRE(test("", "%.x", 0) ); + REQUIRE(test("", "%.o", 0) ); + } + + SECTION("Precision pads with leading zeros") + { + REQUIRE(test("00042", "%.5d", 42) ); + REQUIRE(test("-00042", "%.5d", -42) ); + REQUIRE(test("00042", "%.5i", 42) ); + REQUIRE(test("-00042", "%.5i", -42) ); + REQUIRE(test("00042", "%.5u", 42u) ); + REQUIRE(test("000ff", "%.5x", 255) ); + REQUIRE(test("000FF", "%.5X", 255) ); + REQUIRE(test("00777", "%.5o", 511) ); + } + + SECTION("Precision with sign flags") + { + REQUIRE(test("+", "%+.0d", 0) ); + REQUIRE(test("+", "%+.0i", 0) ); + REQUIRE(test("+01", "%+.2i", 1) ); + } + + SECTION("Width + precision combined") + { + REQUIRE(test(" 01024", "%20.5d", 1024) ); + REQUIRE(test(" -01024", "%20.5d", -1024) ); + REQUIRE(test(" 01024", "%20.5u", 1024u) ); + REQUIRE(test(" ", "%20.d", 0) ); + REQUIRE(test(" ", "%20.u", 0u) ); + REQUIRE(test(" ", "%20.x", 0) ); + REQUIRE(test("00123 ", "%-20.5i", 123) ); + } + + SECTION("Precision overrides 0 flag") + { + REQUIRE(test(" 042", "%05.3d", 42) ); + REQUIRE(test(" -042", "%05.3d", -42) ); + REQUIRE(test(" 042", "%05.3i", 42) ); + REQUIRE(test(" -042", "%05.3i", -42) ); + REQUIRE(test(" 00042", "%07.5u", 42u) ); + } +} + +TEST_CASE("Space and plus flag", "[string][printf]") +{ + SECTION("Space flag without width") + { + REQUIRE(test(" 42", "% d", 42) ); + REQUIRE(test("-42", "% d", -42) ); + REQUIRE(test(" 0", "% d", 0) ); + REQUIRE(test(" 0", "% i", 0) ); + } + + SECTION("Plus flag without width") + { + REQUIRE(test("+42", "%+d", 42) ); + REQUIRE(test("-42", "%+d", -42) ); + REQUIRE(test("+0", "%+i", 0) ); + } + + SECTION("Plus overrides space") + { + REQUIRE(test("+42", "%+ d", 42) ); + REQUIRE(test("+42", "% +d", 42) ); + REQUIRE(test("-42", "%+ d", -42) ); + REQUIRE(test("+0", "%+ d", 0) ); + } + + SECTION("Space flag on float") + { + REQUIRE(test(" 1.500000", "% f", 1.5) ); + REQUIRE(test("-1.500000", "% f", -1.5) ); + } +} + +TEST_CASE("Zero-padding negative numbers", "[string][printf]") +{ + REQUIRE(test("-5", "%01d", -5) ); + REQUIRE(test("-5", "%02d", -5) ); + REQUIRE(test("-05", "%03d", -5) ); + REQUIRE(test("-005", "%04d", -5) ); +} + +TEST_CASE("Star args", "[string][printf]") +{ + SECTION("Star width") + { + REQUIRE(test(" Z", "%*c", 10, 'Z') ); + REQUIRE(test(" Hello", "%*s", 20, "Hello") ); + } + + SECTION("Negative star width = left-justify") + { + REQUIRE(test("42 ", "%*d", -10, 42) ); + REQUIRE(test("-42 ", "%*d", -10, -42) ); + REQUIRE(test("x ", "%*c", -10, 'x') ); + REQUIRE(test("hello ", "%*s", -10, "hello") ); + } + + SECTION("Star precision") + { + REQUIRE(test("01", "%.*i", 2, 1) ); + REQUIRE(test("h", "%.*s", 1, "hello world") ); + } + + SECTION("Negative star precision = precision omitted") + { + REQUIRE(test("42", "%.*d", -1, 42) ); + REQUIRE(test("0", "%.*d", -1, 0) ); + REQUIRE(test("hello", "%.*s", -1, "hello") ); + } + + SECTION("Star width + star precision") + { + REQUIRE(test(" 07", "%*.*i", 10, 2, 7) ); + } + + SECTION("Star width/precision with %%") + { + REQUIRE(test(" 1.23 %", "%*.*f %%", 5, 2, 1.23) ); + } +}