|
| 1 | +// Copyright (c) 2024-present The Bitcoin Core developers |
| 2 | +// Distributed under the MIT software license, see the accompanying |
| 3 | +// file COPYING or http://www.opensource.org/licenses/mit-license.php. |
| 4 | + |
| 5 | +#include <util/string.h> |
| 6 | + |
| 7 | +#include <boost/test/unit_test.hpp> |
| 8 | + |
| 9 | +using namespace util; |
| 10 | + |
| 11 | +BOOST_AUTO_TEST_SUITE(util_string_tests) |
| 12 | + |
| 13 | +// Helper to allow compile-time sanity checks while providing the number of |
| 14 | +// args directly. Normally PassFmt<sizeof...(Args)> would be used. |
| 15 | +template <unsigned NumArgs> |
| 16 | +inline void PassFmt(util::ConstevalFormatString<NumArgs> fmt) |
| 17 | +{ |
| 18 | + // This was already executed at compile-time, but is executed again at run-time to avoid -Wunused. |
| 19 | + decltype(fmt)::Detail_CheckNumFormatSpecifiers(fmt.fmt); |
| 20 | +} |
| 21 | +template <unsigned WrongNumArgs> |
| 22 | +inline void FailFmtWithError(std::string_view wrong_fmt, std::string_view error) |
| 23 | +{ |
| 24 | + using ErrType = const char*; |
| 25 | + auto check_throw{[error](const ErrType& str) { return str == error; }}; |
| 26 | + BOOST_CHECK_EXCEPTION(util::ConstevalFormatString<WrongNumArgs>::Detail_CheckNumFormatSpecifiers(wrong_fmt), ErrType, check_throw); |
| 27 | +} |
| 28 | + |
| 29 | +BOOST_AUTO_TEST_CASE(ConstevalFormatString_NumSpec) |
| 30 | +{ |
| 31 | + PassFmt<0>(""); |
| 32 | + PassFmt<0>("%%"); |
| 33 | + PassFmt<1>("%s"); |
| 34 | + PassFmt<0>("%%s"); |
| 35 | + PassFmt<0>("s%%"); |
| 36 | + PassFmt<1>("%%%s"); |
| 37 | + PassFmt<1>("%s%%"); |
| 38 | + PassFmt<0>(" 1$s"); |
| 39 | + PassFmt<1>("%1$s"); |
| 40 | + PassFmt<1>("%1$s%1$s"); |
| 41 | + PassFmt<2>("%2$s"); |
| 42 | + PassFmt<2>("%2$s 4$s %2$s"); |
| 43 | + PassFmt<129>("%129$s 999$s %2$s"); |
| 44 | + PassFmt<1>("%02d"); |
| 45 | + PassFmt<1>("%+2s"); |
| 46 | + PassFmt<1>("%.6i"); |
| 47 | + PassFmt<1>("%5.2f"); |
| 48 | + PassFmt<1>("%#x"); |
| 49 | + PassFmt<1>("%1$5i"); |
| 50 | + PassFmt<1>("%1$-5i"); |
| 51 | + PassFmt<1>("%1$.5i"); |
| 52 | + // tinyformat accepts almost any "type" spec, even '%', or '_', or '\n'. |
| 53 | + PassFmt<1>("%123%"); |
| 54 | + PassFmt<1>("%123%s"); |
| 55 | + PassFmt<1>("%_"); |
| 56 | + PassFmt<1>("%\n"); |
| 57 | + |
| 58 | + // The `*` specifier behavior is unsupported and can lead to runtime |
| 59 | + // errors when used in a ConstevalFormatString. Please refer to the |
| 60 | + // note in the ConstevalFormatString docs. |
| 61 | + PassFmt<1>("%*c"); |
| 62 | + PassFmt<2>("%2$*3$d"); |
| 63 | + PassFmt<1>("%.*f"); |
| 64 | + |
| 65 | + auto err_mix{"Format specifiers must be all positional or all non-positional!"}; |
| 66 | + FailFmtWithError<1>("%s%1$s", err_mix); |
| 67 | + |
| 68 | + auto err_num{"Format specifier count must match the argument count!"}; |
| 69 | + FailFmtWithError<1>("", err_num); |
| 70 | + FailFmtWithError<0>("%s", err_num); |
| 71 | + FailFmtWithError<2>("%s", err_num); |
| 72 | + FailFmtWithError<0>("%1$s", err_num); |
| 73 | + FailFmtWithError<2>("%1$s", err_num); |
| 74 | + |
| 75 | + auto err_0_pos{"Positional format specifier must have position of at least 1"}; |
| 76 | + FailFmtWithError<1>("%$s", err_0_pos); |
| 77 | + FailFmtWithError<1>("%$", err_0_pos); |
| 78 | + FailFmtWithError<0>("%0$", err_0_pos); |
| 79 | + FailFmtWithError<0>("%0$s", err_0_pos); |
| 80 | + |
| 81 | + auto err_term{"Format specifier incorrectly terminated by end of string"}; |
| 82 | + FailFmtWithError<1>("%", err_term); |
| 83 | + FailFmtWithError<1>("%1$", err_term); |
| 84 | +} |
| 85 | + |
| 86 | +BOOST_AUTO_TEST_SUITE_END() |
0 commit comments