util_string_tests.cpp raw

   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/strencodings.h>
   6  #include <util/string.h>
   7  
   8  #include <boost/test/unit_test.hpp>
   9  #include <test/util/common.h>
  10  #include <tinyformat.h>
  11  
  12  using namespace util;
  13  using util::detail::CheckNumFormatSpecifiers;
  14  
  15  BOOST_AUTO_TEST_SUITE(util_string_tests)
  16  
  17  template <unsigned NumArgs>
  18  void TfmFormatZeroes(const std::string& fmt)
  19  {
  20      std::apply([&](auto... args) {
  21          (void)tfm::format(tfm::RuntimeFormat{fmt}, args...);
  22      }, std::array<int, NumArgs>{});
  23  }
  24  
  25  // Helper to allow compile-time sanity checks while providing the number of
  26  // args directly. Normally PassFmt<sizeof...(Args)> would be used.
  27  template <unsigned NumArgs>
  28  void PassFmt(ConstevalFormatString<NumArgs> fmt)
  29  {
  30      // Execute compile-time check again at run-time to get code coverage stats
  31      BOOST_CHECK_NO_THROW(CheckNumFormatSpecifiers<NumArgs>(fmt.fmt));
  32  
  33      // If ConstevalFormatString didn't throw above, make sure tinyformat doesn't
  34      // throw either for the same format string and parameter count combination.
  35      // Proves that we have some extent of protection from runtime errors
  36      // (tinyformat may still throw for some type mismatches).
  37      BOOST_CHECK_NO_THROW(TfmFormatZeroes<NumArgs>(fmt.fmt));
  38  }
  39  template <unsigned WrongNumArgs>
  40  void FailFmtWithError(const char* wrong_fmt, std::string_view error)
  41  {
  42      BOOST_CHECK_EXCEPTION(CheckNumFormatSpecifiers<WrongNumArgs>(wrong_fmt), const char*, HasReason{error});
  43  }
  44  
  45  BOOST_AUTO_TEST_CASE(ConstevalFormatString_NumSpec)
  46  {
  47      PassFmt<0>("");
  48      PassFmt<0>("%%");
  49      PassFmt<1>("%s");
  50      PassFmt<1>("%c");
  51      PassFmt<0>("%%s");
  52      PassFmt<0>("s%%");
  53      PassFmt<1>("%%%s");
  54      PassFmt<1>("%s%%");
  55      PassFmt<0>(" 1$s");
  56      PassFmt<1>("%1$s");
  57      PassFmt<1>("%1$s%1$s");
  58      PassFmt<2>("%2$s");
  59      PassFmt<2>("%2$s 4$s %2$s");
  60      PassFmt<129>("%129$s 999$s %2$s");
  61      PassFmt<1>("%02d");
  62      PassFmt<1>("%+2s");
  63      PassFmt<1>("%.6i");
  64      PassFmt<1>("%5.2f");
  65      PassFmt<1>("%5.f");
  66      PassFmt<1>("%.f");
  67      PassFmt<1>("%#x");
  68      PassFmt<1>("%1$5i");
  69      PassFmt<1>("%1$-5i");
  70      PassFmt<1>("%1$.5i");
  71      // tinyformat accepts almost any "type" spec, even '%', or '_', or '\n'.
  72      PassFmt<1>("%123%");
  73      PassFmt<1>("%123%s");
  74      PassFmt<1>("%_");
  75      PassFmt<1>("%\n");
  76  
  77      PassFmt<2>("%*c");
  78      PassFmt<2>("%+*c");
  79      PassFmt<2>("%.*f");
  80      PassFmt<3>("%*.*f");
  81      PassFmt<3>("%2$*3$d");
  82      PassFmt<3>("%2$*3$.9d");
  83      PassFmt<3>("%2$.*3$d");
  84      PassFmt<3>("%2$9.*3$d");
  85      PassFmt<3>("%2$+9.*3$d");
  86      PassFmt<4>("%3$*2$.*4$f");
  87  
  88      // Make sure multiple flag characters "- 0+" are accepted
  89      PassFmt<3>("'%- 0+*.*f'");
  90      PassFmt<3>("'%1$- 0+*3$.*2$f'");
  91  
  92      auto err_mix{"Format specifiers must be all positional or all non-positional!"};
  93      FailFmtWithError<1>("%s%1$s", err_mix);
  94      FailFmtWithError<2>("%2$*d", err_mix);
  95      FailFmtWithError<2>("%*2$d", err_mix);
  96      FailFmtWithError<2>("%.*3$d", err_mix);
  97      FailFmtWithError<2>("%2$.*d", err_mix);
  98  
  99      auto err_num{"Format specifier count must match the argument count!"};
 100      FailFmtWithError<1>("", err_num);
 101      FailFmtWithError<0>("%s", err_num);
 102      FailFmtWithError<2>("%s", err_num);
 103      FailFmtWithError<0>("%1$s", err_num);
 104      FailFmtWithError<2>("%1$s", err_num);
 105      FailFmtWithError<1>("%*c", err_num);
 106  
 107      auto err_0_pos{"Positional format specifier must have position of at least 1"};
 108      FailFmtWithError<1>("%$s", err_0_pos);
 109      FailFmtWithError<1>("%$", err_0_pos);
 110      FailFmtWithError<0>("%0$", err_0_pos);
 111      FailFmtWithError<0>("%0$s", err_0_pos);
 112      FailFmtWithError<2>("%2$*$d", err_0_pos);
 113      FailFmtWithError<2>("%2$*0$d", err_0_pos);
 114      FailFmtWithError<3>("%3$*2$.*$f", err_0_pos);
 115      FailFmtWithError<3>("%3$*2$.*0$f", err_0_pos);
 116  
 117      auto err_term{"Format specifier incorrectly terminated by end of string"};
 118      FailFmtWithError<1>("%", err_term);
 119      FailFmtWithError<1>("%9", err_term);
 120      FailFmtWithError<1>("%9.", err_term);
 121      FailFmtWithError<1>("%9.9", err_term);
 122      FailFmtWithError<1>("%*", err_term);
 123      FailFmtWithError<1>("%+*", err_term);
 124      FailFmtWithError<1>("%.*", err_term);
 125      FailFmtWithError<1>("%9.*", err_term);
 126      FailFmtWithError<1>("%1$", err_term);
 127      FailFmtWithError<1>("%1$9", err_term);
 128      FailFmtWithError<2>("%1$*2$", err_term);
 129      FailFmtWithError<2>("%1$.*2$", err_term);
 130      FailFmtWithError<2>("%1$9.*2$", err_term);
 131  
 132      // Non-parity between tinyformat and ConstevalFormatString.
 133      // tinyformat throws but ConstevalFormatString does not.
 134      BOOST_CHECK_EXCEPTION(tfm::format(ConstevalFormatString<1>{"%n"}, 0), tfm::format_error,
 135          HasReason{"tinyformat: %n conversion spec not supported"});
 136      BOOST_CHECK_EXCEPTION(tfm::format(ConstevalFormatString<2>{"%*s"}, "hi", "hi"), tfm::format_error,
 137          HasReason{"tinyformat: Cannot convert from argument type to integer for use as variable width or precision"});
 138      BOOST_CHECK_EXCEPTION(tfm::format(ConstevalFormatString<2>{"%.*s"}, "hi", "hi"), tfm::format_error,
 139          HasReason{"tinyformat: Cannot convert from argument type to integer for use as variable width or precision"});
 140  
 141      // Ensure that tinyformat throws if format string contains wrong number
 142      // of specifiers. PassFmt relies on this to verify tinyformat successfully
 143      // formats the strings, and will need to be updated if tinyformat is changed
 144      // not to throw on failure.
 145      BOOST_CHECK_EXCEPTION(TfmFormatZeroes<2>("%s"), tfm::format_error,
 146          HasReason{"tinyformat: Not enough conversion specifiers in format string"});
 147      BOOST_CHECK_EXCEPTION(TfmFormatZeroes<1>("%s %s"), tfm::format_error,
 148          HasReason{"tinyformat: Too many conversion specifiers in format string"});
 149  }
 150  
 151  BOOST_AUTO_TEST_CASE(case_insensitive_equal_test)
 152  {
 153      BOOST_CHECK(!CaseInsensitiveEqual("A", "B"));
 154      BOOST_CHECK(!CaseInsensitiveEqual("A", "b"));
 155      BOOST_CHECK(!CaseInsensitiveEqual("a", "B"));
 156      BOOST_CHECK(!CaseInsensitiveEqual("B", "A"));
 157      BOOST_CHECK(!CaseInsensitiveEqual("B", "a"));
 158      BOOST_CHECK(!CaseInsensitiveEqual("b", "A"));
 159      BOOST_CHECK(!CaseInsensitiveEqual("A", "AA"));
 160      BOOST_CHECK(CaseInsensitiveEqual("A-A", "a-a"));
 161      BOOST_CHECK(CaseInsensitiveEqual("A", "A"));
 162      BOOST_CHECK(CaseInsensitiveEqual("A", "a"));
 163      BOOST_CHECK(CaseInsensitiveEqual("a", "a"));
 164      BOOST_CHECK(CaseInsensitiveEqual("B", "b"));
 165      BOOST_CHECK(CaseInsensitiveEqual("ab", "aB"));
 166      BOOST_CHECK(CaseInsensitiveEqual("Ab", "aB"));
 167      BOOST_CHECK(CaseInsensitiveEqual("AB", "ab"));
 168  
 169      // Use a character with value > 127
 170      // to ensure we don't trigger implicit-integer-sign-change
 171      BOOST_CHECK(!CaseInsensitiveEqual("a", "\xe4"));
 172  }
 173  
 174  BOOST_AUTO_TEST_CASE(line_reader_test)
 175  {
 176      {
 177          // Check three lines terminated by \n and \r\n, preserving whitespace
 178          std::string_view input = "once upon a time\n there was a dog \r\nwho liked food\n";
 179          LineReader reader(input, /*max_line_length=*/128);
 180          BOOST_CHECK_EQUAL(reader.Consumed(), 0);
 181          BOOST_CHECK_EQUAL(reader.Remaining(), 51);
 182          std::optional<std::string> line1{reader.ReadLine()};
 183          BOOST_CHECK_EQUAL(reader.Consumed(), 17);
 184          BOOST_CHECK_EQUAL(reader.Remaining(), 34);
 185          std::optional<std::string> line2{reader.ReadLine()};
 186          BOOST_CHECK_EQUAL(reader.Consumed(), 36);
 187          BOOST_CHECK_EQUAL(reader.Remaining(), 15);
 188          std::optional<std::string> line3{reader.ReadLine()};
 189          std::optional<std::string> line4{reader.ReadLine()};
 190          BOOST_CHECK(line1);
 191          BOOST_CHECK(line2);
 192          BOOST_CHECK(line3);
 193          BOOST_CHECK(!line4);
 194          BOOST_CHECK_EQUAL(line1.value(), "once upon a time");
 195          BOOST_CHECK_EQUAL(line2.value(), " there was a dog ");
 196          BOOST_CHECK_EQUAL(line3.value(), "who liked food");
 197          BOOST_CHECK_EQUAL(reader.Consumed(), 51);
 198          BOOST_CHECK_EQUAL(reader.Remaining(), 0);
 199      }
 200      {
 201          // Do not exceed max_line_length + 1 while searching for \n
 202          // Test with 22-character line + \n + 23-character line + \n
 203          std::string_view input = "once upon a time there\nwas a dog who liked tea\n";
 204  
 205          LineReader reader1(input, /*max_line_length=*/22);
 206          // First line is exactly the length of max_line_length
 207          BOOST_CHECK_EQUAL(reader1.ReadLine(), "once upon a time there");
 208          // Second line is +1 character too long
 209          BOOST_CHECK_EXCEPTION(reader1.ReadLine(), std::runtime_error, HasReason{"max_line_length exceeded by LineReader"});
 210  
 211          // Increase max_line_length by 1
 212          LineReader reader2(input, /*max_line_length=*/23);
 213          // Both lines fit within limit
 214          BOOST_CHECK_EQUAL(reader2.ReadLine(), "once upon a time there");
 215          BOOST_CHECK_EQUAL(reader2.ReadLine(), "was a dog who liked tea");
 216          // End of buffer reached
 217          BOOST_CHECK(!reader2.ReadLine());
 218      }
 219      {
 220          // Empty lines are empty
 221          std::string_view input = "\n";
 222          LineReader reader(input, /*max_line_length=*/1024);
 223          BOOST_CHECK_EQUAL(reader.ReadLine(), "");
 224          BOOST_CHECK(!reader.ReadLine());
 225      }
 226      {
 227          // Empty buffers are null
 228          std::string_view input;
 229          LineReader reader(input, /*max_line_length=*/1024);
 230          BOOST_CHECK(!reader.ReadLine());
 231      }
 232      {
 233          // Don't trim any more than \r\n
 234          std::string_view input = "\r\r\n";
 235          LineReader reader(input, /*max_line_length=*/1024);
 236          BOOST_CHECK_EQUAL(reader.ReadLine(), "\r");
 237          BOOST_CHECK(!reader.ReadLine());
 238      }
 239      {
 240          // Even one character is too long, if it's not \n
 241          std::string_view input = "ab\n";
 242          LineReader reader(input, /*max_line_length=*/1);
 243          // First line is +1 character too long
 244          BOOST_CHECK_EXCEPTION(reader.ReadLine(), std::runtime_error, HasReason{"max_line_length exceeded by LineReader"});
 245      }
 246      {
 247          std::string_view input = "a\nb\n";
 248          LineReader reader(input, /*max_line_length=*/1);
 249          BOOST_CHECK_EQUAL(reader.ReadLine(), "a");
 250          BOOST_CHECK_EQUAL(reader.ReadLine(), "b");
 251          BOOST_CHECK(!reader.ReadLine());
 252      }
 253      {
 254          // If ReadLine fails, the iterator is reset and we can ReadLength instead
 255          std::string_view input = "a\nbaboon\n";
 256          LineReader reader(input, /*max_line_length=*/1);
 257          BOOST_CHECK_EQUAL(reader.ReadLine(), "a");
 258          // "baboon" is too long
 259          BOOST_CHECK_EXCEPTION(reader.ReadLine(), std::runtime_error, HasReason{"max_line_length exceeded by LineReader"});
 260          BOOST_CHECK_EQUAL(reader.ReadLength(1), "b");
 261          BOOST_CHECK_EQUAL(reader.ReadLength(1), "a");
 262          BOOST_CHECK_EQUAL(reader.ReadLength(2), "bo");
 263          // "on" is too long
 264          BOOST_CHECK_EXCEPTION(reader.ReadLine(), std::runtime_error, HasReason{"max_line_length exceeded by LineReader"});
 265          BOOST_CHECK_EQUAL(reader.ReadLength(1), "o");
 266          BOOST_CHECK_EQUAL(reader.ReadLine(), "n"); // now the remainder of the buffer fits in one line
 267          BOOST_CHECK(!reader.ReadLine());
 268      }
 269      {
 270          // The end of the buffer (EOB) does not count as end of line \n
 271          std::string_view input = "once upon a time there";
 272  
 273          LineReader reader(input, /*max_line_length=*/22);
 274          // First line is exactly the length of max_line_length, but that doesn't matter because \n is missing
 275          BOOST_CHECK(!reader.ReadLine());
 276          // Data can still be read using ReadLength
 277          BOOST_CHECK_EQUAL(reader.ReadLength(22), "once upon a time there");
 278          // End of buffer reached
 279          BOOST_CHECK_EQUAL(reader.Remaining(), 0);
 280      }
 281      {
 282          // Read specific number of bytes regardless of max_line_length or \n unless buffer is too short
 283          std::string_view input = "once upon a time\n there was a dog \r\nwho liked food";
 284          LineReader reader(input, /*max_line_length=*/1);
 285          BOOST_CHECK_EQUAL(reader.ReadLength(0), "");
 286          BOOST_CHECK_EQUAL(reader.ReadLength(3), "onc");
 287          BOOST_CHECK_EQUAL(reader.ReadLength(8), "e upon a");
 288          BOOST_CHECK_EQUAL(reader.ReadLength(8), " time\n t");
 289          BOOST_CHECK_EXCEPTION(reader.ReadLength(128), std::runtime_error, HasReason{"Not enough data in buffer"});
 290          // After the error the iterator is reset so we can try again
 291          BOOST_CHECK_EQUAL(reader.ReadLength(31), "here was a dog \r\nwho liked food");
 292          // End of buffer reached
 293          BOOST_CHECK_EQUAL(reader.Remaining(), 0);
 294      }
 295  }
 296  
 297  BOOST_AUTO_TEST_SUITE_END()
 298