LCOV - code coverage report
Current view: top level - src/test - util_string_tests.cpp (source / functions) Coverage Total Hit
Test: total_coverage.info Lines: 100.0 % 193 193
Test Date: 2026-04-27 06:58:15 Functions: 100.0 % 28 28
Branches: 45.7 % 598 273

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

Generated by: LCOV version 2.0-1