| // SPDX-License-Identifier: Apache-2.0 | |
| /** @file | |
| TextView unit tests. | |
| */ | |
| #include <iomanip> | |
| #include <iostream> | |
| #include <sstream> | |
| #include <string> | |
| #include <map> | |
| #include <unordered_map> | |
| #include <cmath> | |
| #include <algorithm> | |
| #include <iterator> | |
| #include "swoc/TextView.h" | |
| #include <catch2/catch_test_macros.hpp> | |
| using swoc::TextView; | |
| using namespace std::literals; | |
| using namespace swoc::literals; | |
| TEST_CASE("TextView Constructor", "[libswoc][TextView]") { | |
| static const std::string base = "Evil Dave Rulez!"; | |
| unsigned ux = base.size(); | |
| TextView tv(base); | |
| TextView b{base.data(), base.size()}; | |
| TextView c{std::string_view(base)}; | |
| TextView e{base.data(), 15}; | |
| TextView f(base.data(), 15); | |
| TextView u{base.data(), ux}; | |
| TextView g{base.data(), base.data() + base.size()}; // begin/end pointers. | |
| // Check the various forms of char pointers work unambiguously. | |
| TextView bob{"Bob"}; | |
| std::string dave("dave"); | |
| REQUIRE(bob == "Bob"_tv); // Attempt to verify @a bob isn't pointing at a temporary. | |
| char q[12] = "Bob"; | |
| TextView t_q{q}; | |
| REQUIRE(t_q.data() == q); // must point at @a q. | |
| char *qp = q; | |
| TextView t_qp{qp}; | |
| REQUIRE(t_qp.data() == qp); // verify pointer is not pointing at a temporary. | |
| char const *qcp = "Bob"; | |
| TextView t_qcp{qcp}; | |
| REQUIRE(t_qcp.data() == qcp); | |
| tv = "Delain"; // assign literal. | |
| REQUIRE(tv.size() == 6); | |
| tv = q; // Assign array. | |
| REQUIRE(tv.size() == sizeof(q) - 1); // trailing nul char dropped. | |
| tv = qp; // Assign pointer. | |
| REQUIRE(tv.data() == qp); | |
| tv = qcp; // Assign pointer to const. | |
| REQUIRE(tv.data() == qcp); | |
| tv = std::string_view(base); | |
| REQUIRE(tv.size() == base.size()); | |
| qp = nullptr; | |
| REQUIRE(TextView(qp).size() == 0); | |
| qcp = nullptr; | |
| REQUIRE(TextView(qcp).size() == 0); | |
| }; | |
| TEST_CASE("TextView Operations", "[libswoc][TextView]") { | |
| TextView tv{"Evil Dave Rulez"}; | |
| TextView tv_lower{"evil dave rulez"}; | |
| TextView nothing; | |
| size_t off; | |
| REQUIRE(tv.find('l') == 3); | |
| off = tv.find_if([](char c) { return c == 'D'; }); | |
| REQUIRE(off == tv.find('D')); | |
| REQUIRE(tv); | |
| REQUIRE(!tv == false); | |
| if (nothing) { | |
| REQUIRE(false); // bad operator bool on TextView | |
| } | |
| REQUIRE(!nothing == true); | |
| REQUIRE(nothing.empty() == true); | |
| REQUIRE(memcmp(tv, tv) == 0); | |
| REQUIRE(memcmp(tv, tv_lower) != 0); | |
| REQUIRE(strcmp(tv, tv) == 0); | |
| REQUIRE(strcmp(tv, tv_lower) != 0); | |
| REQUIRE(strcasecmp(tv, tv) == 0); | |
| REQUIRE(strcasecmp(tv, tv_lower) == 0); | |
| REQUIRE(strcasecmp(nothing, tv) != 0); | |
| // Check generic construction from a "string like" class. | |
| struct Stringy { | |
| char const * | |
| data() const { | |
| return _data; | |
| } | |
| size_t | |
| size() const { | |
| return _size; | |
| } | |
| char const *_data = nullptr; | |
| size_t _size; | |
| }; | |
| char const *stringy_text = "Evil Dave Rulez"; | |
| Stringy stringy{stringy_text, strlen(stringy_text)}; | |
| // Can construct directly. | |
| TextView from_stringy{stringy}; | |
| REQUIRE(0 == strcmp(from_stringy, stringy_text)); | |
| // Can assign directly. | |
| TextView assign_stringy; | |
| REQUIRE(assign_stringy.empty() == true); | |
| assign_stringy.assign(stringy); | |
| REQUIRE(0 == strcmp(assign_stringy, stringy_text)); | |
| // Pass as argument to TextView parameter. | |
| auto stringy_f = [&](TextView txt) -> bool { return 0 == strcmp(txt, stringy_text); }; | |
| REQUIRE(true == stringy_f(stringy)); | |
| REQUIRE(false == stringy_f(tv_lower)); | |
| } | |
| TEST_CASE("TextView Trimming", "[libswoc][TextView]") { | |
| TextView tv(" Evil Dave Rulz ..."); | |
| TextView tv2{"More Text1234567890"}; | |
| REQUIRE("Evil Dave Rulz ..." == TextView(tv).ltrim_if(&isspace)); | |
| REQUIRE(tv2 == TextView{tv2}.ltrim_if(&isspace)); | |
| REQUIRE("More Text" == TextView{tv2}.rtrim_if(&isdigit)); | |
| REQUIRE(" Evil Dave Rulz " == TextView(tv).rtrim('.')); | |
| REQUIRE("Evil Dave Rulz" == TextView(tv).trim(" .")); | |
| tv.assign("\r\n"); | |
| tv.rtrim_if([](char c) -> bool { return c == '\r' || c == '\n'; }); | |
| REQUIRE(tv.size() == 0); | |
| tv.assign("..."); | |
| tv.rtrim('.'); | |
| REQUIRE(tv.size() == 0); | |
| tv.assign(".,,.;."); | |
| tv.rtrim(";,."_tv); | |
| REQUIRE(tv.size() == 0); | |
| } | |
| TEST_CASE("TextView Find", "[libswoc][TextView]") { | |
| TextView addr{"172.29.145.87:5050"}; | |
| REQUIRE(addr.find(':') == 13); | |
| REQUIRE(addr.rfind(':') == 13); | |
| REQUIRE(addr.find('.') == 3); | |
| REQUIRE(addr.rfind('.') == 10); | |
| } | |
| TEST_CASE("TextView Affixes", "[libswoc][TextView]") { | |
| TextView s; // scratch. | |
| TextView tv1("0123456789;01234567890"); | |
| TextView prefix{tv1.prefix(10)}; | |
| REQUIRE("0123456789" == prefix); | |
| REQUIRE("90" == tv1.suffix(2)); | |
| REQUIRE("67890" == tv1.suffix(5)); | |
| REQUIRE("4567890" == tv1.suffix(7)); | |
| REQUIRE(tv1 == tv1.prefix(9999)); | |
| REQUIRE(tv1 == tv1.suffix(9999)); | |
| TextView tv2 = tv1.prefix_at(';'); | |
| REQUIRE(tv2 == "0123456789"); | |
| REQUIRE(tv1.prefix_at('z').empty()); | |
| REQUIRE(tv1.suffix_at('z').empty()); | |
| s = tv1; | |
| REQUIRE(s.remove_prefix(10) == ";01234567890"); | |
| s = tv1; | |
| REQUIRE(s.remove_prefix(9999).empty()); | |
| s = tv1; | |
| REQUIRE(s.remove_suffix(11) == "0123456789;"); | |
| s = tv1; | |
| s.remove_suffix(9999); | |
| REQUIRE(s.empty()); | |
| REQUIRE(s.data() == tv1.data()); | |
| TextView right{tv1}; | |
| TextView left{right.split_prefix_at(';')}; | |
| REQUIRE(right.size() == 11); | |
| REQUIRE(left.size() == 10); | |
| TextView tv3 = "abcdefg:gfedcba"; | |
| left = tv3; | |
| right = left.split_suffix_at(";:,"); | |
| REQUIRE(right.size() == 7); | |
| REQUIRE(left.size() == 7); | |
| REQUIRE(left == "abcdefg"); | |
| REQUIRE(right == "gfedcba"); | |
| TextView addr1{"[fe80::fc54:ff:fe60:d886]"}; | |
| TextView addr2{"[fe80::fc54:ff:fe60:d886]:956"}; | |
| TextView addr3{"192.168.1.1:5050"}; | |
| TextView host{"evil.dave.rulz"}; | |
| TextView t = addr1; | |
| ++t; | |
| REQUIRE("fe80::fc54:ff:fe60:d886]" == t); | |
| TextView a = t.take_prefix_at(']'); | |
| REQUIRE("fe80::fc54:ff:fe60:d886" == a); | |
| REQUIRE(t.empty()); | |
| t = addr2; | |
| ++t; | |
| a = t.take_prefix_at(']'); | |
| REQUIRE("fe80::fc54:ff:fe60:d886" == a); | |
| REQUIRE(':' == *t); | |
| ++t; | |
| REQUIRE("956" == t); | |
| t = addr3; | |
| TextView sf{t.suffix_at(':')}; | |
| REQUIRE("5050" == sf); | |
| REQUIRE(t == addr3); | |
| t = addr3; | |
| s = t.split_suffix(4); | |
| REQUIRE("5050" == s); | |
| REQUIRE("192.168.1.1" == t); | |
| t = addr3; | |
| s = t.split_suffix_at(':'); | |
| REQUIRE("5050" == s); | |
| REQUIRE("192.168.1.1" == t); | |
| t = addr3; | |
| s = t.split_suffix_at('Q'); | |
| REQUIRE(s.empty()); | |
| REQUIRE(t == addr3); | |
| t = addr3; | |
| s = t.take_suffix_at(':'); | |
| REQUIRE("5050" == s); | |
| REQUIRE("192.168.1.1" == t); | |
| t = addr3; | |
| s = t.take_suffix_at('Q'); | |
| REQUIRE(s == addr3); | |
| REQUIRE(t.empty()); | |
| REQUIRE(host.suffix_at('.') == "rulz"); | |
| REQUIRE(true == host.suffix_at(':').empty()); | |
| auto is_sep{[](char c) { return isspace(c) || ',' == c || ';' == c; }}; | |
| TextView token; | |
| t = ";; , ;;one;two,th:ree four,, ; ,,f-ive="sv; | |
| // Do an unrolled loop. | |
| REQUIRE(!t.ltrim_if(is_sep).empty()); | |
| REQUIRE(t.take_prefix_if(is_sep) == "one"); | |
| REQUIRE(!t.ltrim_if(is_sep).empty()); | |
| REQUIRE(t.take_prefix_if(is_sep) == "two"); | |
| REQUIRE(!t.ltrim_if(is_sep).empty()); | |
| REQUIRE(t.take_prefix_if(is_sep) == "th:ree"); | |
| REQUIRE(!t.ltrim_if(is_sep).empty()); | |
| REQUIRE(t.take_prefix_if(is_sep) == "four"); | |
| REQUIRE(!t.ltrim_if(is_sep).empty()); | |
| REQUIRE(t.take_prefix_if(is_sep) == "f-ive="); | |
| REQUIRE(t.empty()); | |
| // Simulate pulling off FQDN pieces in reverse order from a string_view. | |
| // Simulates operations in HostLookup.cc, where the use of string_view | |
| // necessitates this workaround of failures in the string_view API. | |
| std::string_view fqdn{"bob.ne1.corp.ngeo.com"}; | |
| TextView elt{TextView{fqdn}.take_suffix_at('.')}; | |
| REQUIRE(elt == "com"); | |
| fqdn.remove_suffix(std::min(fqdn.size(), elt.size() + 1)); | |
| // Unroll loop for testing. | |
| elt = TextView{fqdn}.take_suffix_at('.'); | |
| REQUIRE(elt == "ngeo"); | |
| fqdn.remove_suffix(std::min(fqdn.size(), elt.size() + 1)); | |
| elt = TextView{fqdn}.take_suffix_at('.'); | |
| REQUIRE(elt == "corp"); | |
| fqdn.remove_suffix(std::min(fqdn.size(), elt.size() + 1)); | |
| elt = TextView{fqdn}.take_suffix_at('.'); | |
| REQUIRE(elt == "ne1"); | |
| fqdn.remove_suffix(std::min(fqdn.size(), elt.size() + 1)); | |
| elt = TextView{fqdn}.take_suffix_at('.'); | |
| REQUIRE(elt == "bob"); | |
| fqdn.remove_suffix(std::min(fqdn.size(), elt.size() + 1)); | |
| elt = TextView{fqdn}.take_suffix_at('.'); | |
| REQUIRE(elt.empty()); | |
| // Do it again, TextView stle. | |
| t = "bob.ne1.corp.ngeo.com"; | |
| REQUIRE(t.rtrim('.').take_suffix_at('.') == "com"_tv); | |
| REQUIRE(t.rtrim('.').take_suffix_at('.') == "ngeo"_tv); | |
| REQUIRE(t.rtrim('.').take_suffix_at('.') == "corp"_tv); | |
| REQUIRE(t.take_suffix_at('.') == "ne1"_tv); | |
| REQUIRE(t.take_suffix_at('.') == "bob"_tv); | |
| REQUIRE(t.size() == 0); | |
| t = "bob.ne1.corp.ngeo.com"; | |
| REQUIRE(t.remove_suffix_at('.') == "bob.ne1.corp.ngeo"_tv); | |
| REQUIRE(t.remove_suffix_at('.') == "bob.ne1.corp"_tv); | |
| REQUIRE(t.remove_suffix_at('.') == "bob.ne1"_tv); | |
| REQUIRE(t.remove_suffix_at('.') == "bob"_tv); | |
| REQUIRE(t.remove_suffix_at('.').size() == 0); | |
| // Check some edge cases. | |
| fqdn = "."sv; | |
| token = TextView{fqdn}.take_suffix_at('.'); | |
| REQUIRE(token.size() == 0); | |
| REQUIRE(token.empty()); | |
| s = "."sv; | |
| REQUIRE(s.size() == 1); | |
| REQUIRE(s.rtrim('.').empty()); | |
| token = s.take_suffix_at('.'); | |
| REQUIRE(token.size() == 0); | |
| REQUIRE(token.empty()); | |
| s = "."sv; | |
| REQUIRE(s.size() == 1); | |
| REQUIRE(s.ltrim('.').empty()); | |
| token = s.take_prefix_at('.'); | |
| REQUIRE(token.size() == 0); | |
| REQUIRE(token.empty()); | |
| s = ".."sv; | |
| REQUIRE(s.size() == 2); | |
| token = s.take_suffix_at('.'); | |
| REQUIRE(token.size() == 0); | |
| REQUIRE(token.empty()); | |
| REQUIRE(s.size() == 1); | |
| // Check for subtle differences with trailing separator | |
| token = "one.ex"; | |
| auto name = token.take_prefix_at('.'); | |
| REQUIRE(name.size() > 0); | |
| REQUIRE(token.size() > 0); | |
| token = "one"; | |
| name = token.take_prefix_at('.'); | |
| REQUIRE(name.size() > 0); | |
| REQUIRE(token.size() == 0); | |
| REQUIRE(token.data() == name.end()); | |
| token = "one."; | |
| name = token.take_prefix_at('.'); | |
| REQUIRE(name.size() > 0); | |
| REQUIRE(token.size() == 0); | |
| REQUIRE(token.data() == name.end() + 1); | |
| auto is_not_alnum = [](char c) { return !isalnum(c); }; | |
| s = "file.cc"; | |
| REQUIRE(s.suffix_at('.') == "cc"); | |
| REQUIRE(s.suffix_if(is_not_alnum) == "cc"); | |
| REQUIRE(s.prefix_at('.') == "file"); | |
| REQUIRE(s.prefix_if(is_not_alnum) == "file"); | |
| s.remove_suffix_at('.'); | |
| REQUIRE(s == "file"); | |
| s = "file.cc.org.123"; | |
| REQUIRE(s.suffix_at('.') == "123"); | |
| REQUIRE(s.prefix_at('.') == "file"); | |
| s.remove_suffix_if(is_not_alnum); | |
| REQUIRE(s == "file.cc.org"); | |
| s.remove_suffix_at('.'); | |
| REQUIRE(s == "file.cc"); | |
| s.remove_prefix_at('.'); | |
| REQUIRE(s == "cc"); | |
| s = "file.cc.org.123"; | |
| s.remove_prefix_if(is_not_alnum); | |
| REQUIRE(s == "cc.org.123"); | |
| s.remove_suffix_at('!'); | |
| REQUIRE(s.empty()); | |
| s = "file.cc.org"; | |
| s.remove_prefix_at('!'); | |
| REQUIRE(s == "file.cc.org"); | |
| static constexpr TextView ctv{"http://delain.nl/albums/Lucidity.html"}; | |
| static constexpr TextView ctv_scheme{ctv.prefix(4)}; | |
| static constexpr TextView ctv_stem{ctv.suffix(4)}; | |
| static constexpr TextView ctv_host{ctv.substr(7, 9)}; | |
| REQUIRE(ctv.starts_with("http"_tv) == true); | |
| REQUIRE(ctv.ends_with(".html") == true); | |
| REQUIRE(ctv.starts_with("https"_tv) == false); | |
| REQUIRE(ctv.ends_with(".jpg") == false); | |
| REQUIRE(ctv.starts_with_nocase("HttP"_tv) == true); | |
| REQUIRE(ctv.starts_with_nocase("HttP") == true); | |
| REQUIRE(ctv.starts_with("HttP") == false); | |
| REQUIRE(ctv.starts_with("http") == true); | |
| REQUIRE(ctv.starts_with('h') == true); | |
| REQUIRE(ctv.starts_with('H') == false); | |
| REQUIRE(ctv.starts_with_nocase('H') == true); | |
| REQUIRE(ctv.starts_with('q') == false); | |
| REQUIRE(ctv.starts_with_nocase('Q') == false); | |
| REQUIRE(ctv.ends_with("htML"_tv) == false); | |
| REQUIRE(ctv.ends_with_nocase("htML"_tv) == true); | |
| REQUIRE(ctv.ends_with("htML") == false); | |
| REQUIRE(ctv.ends_with_nocase("htML") == true); | |
| REQUIRE(ctv_scheme == "http"_tv); | |
| REQUIRE(ctv_stem == "html"_tv); | |
| REQUIRE(ctv_host == "delain.nl"_tv); | |
| // Checking that constexpr works for this constructor as long as npos isn't used. | |
| static constexpr TextView ctv2{"http://delain.nl/albums/Interlude.html", 38}; | |
| // This doesn't compile because it causes strlen to be called which isn't constexpr compatible. | |
| // static constexpr TextView ctv3 {"http://delain.nl/albums/Interlude.html", TextView::npos}; | |
| // This works because it's not constexpr. | |
| TextView ctv3{"http://delain.nl/albums/Interlude.html", TextView::npos}; | |
| REQUIRE(ctv2 == ctv3); | |
| }; | |
| TEST_CASE("TextView Formatting", "[libswoc][TextView]") { | |
| TextView a("01234567"); | |
| { | |
| std::ostringstream buff; | |
| buff << '|' << a << '|'; | |
| REQUIRE(buff.str() == "|01234567|"); | |
| } | |
| { | |
| std::ostringstream buff; | |
| buff << '|' << std::setw(5) << a << '|'; | |
| REQUIRE(buff.str() == "|01234567|"); | |
| } | |
| { | |
| std::ostringstream buff; | |
| buff << '|' << std::setw(12) << a << '|'; | |
| REQUIRE(buff.str() == "| 01234567|"); | |
| } | |
| { | |
| std::ostringstream buff; | |
| buff << '|' << std::setw(12) << std::right << a << '|'; | |
| REQUIRE(buff.str() == "| 01234567|"); | |
| } | |
| { | |
| std::ostringstream buff; | |
| buff << '|' << std::setw(12) << std::left << a << '|'; | |
| REQUIRE(buff.str() == "|01234567 |"); | |
| } | |
| { | |
| std::ostringstream buff; | |
| buff << '|' << std::setw(12) << std::right << std::setfill('_') << a << '|'; | |
| REQUIRE(buff.str() == "|____01234567|"); | |
| } | |
| { | |
| std::ostringstream buff; | |
| buff << '|' << std::setw(12) << std::left << std::setfill('_') << a << '|'; | |
| REQUIRE(buff.str() == "|01234567____|"); | |
| } | |
| } | |
| TEST_CASE("TextView Conversions", "[libswoc][TextView]") { | |
| TextView n = " 956783"; | |
| TextView n2 = n; | |
| TextView n3 = "031"; | |
| TextView n4 = "13f8q"; | |
| TextView n5 = "0x13f8"; | |
| TextView n6 = "0X13f8"; | |
| TextView n7 = "-2345679"; | |
| TextView n8 = "+2345679"; | |
| TextView x; | |
| n2.ltrim_if(&isspace); | |
| REQUIRE(956783 == svtoi(n)); | |
| REQUIRE(956783 == svtoi(n2)); | |
| REQUIRE(956783 == svtoi(n2, &x)); | |
| REQUIRE(x.data() == n2.data()); | |
| REQUIRE(x.size() == n2.size()); | |
| REQUIRE(0x13f8 == svtoi(n4, &x, 16)); | |
| REQUIRE(x == "13f8"); | |
| REQUIRE(0x13f8 == svtoi(n5)); | |
| REQUIRE(0x13f8 == svtoi(n6)); | |
| REQUIRE(25 == svtoi(n3)); | |
| REQUIRE(31 == svtoi(n3, nullptr, 10)); | |
| REQUIRE(-2345679 == svtoi(n7)); | |
| REQUIRE(-2345679 == svtoi(n7, &x)); | |
| REQUIRE(x == n7); | |
| REQUIRE(2345679 == svtoi(n8)); | |
| REQUIRE(2345679 == svtoi(n8, &x)); | |
| REQUIRE(x == n8); | |
| REQUIRE(0b10111 == svtoi("0b10111"_tv)); | |
| x = n4; | |
| REQUIRE(13 == swoc::svto_radix<10>(x)); | |
| REQUIRE(x.size() + 2 == n4.size()); | |
| x = n4; | |
| REQUIRE(0x13f8 == swoc::svto_radix<16>(x)); | |
| REQUIRE(x.size() + 4 == n4.size()); | |
| x = n4; | |
| REQUIRE(7 == swoc::svto_radix<4>(x)); | |
| REQUIRE(x.size() + 2 == n4.size()); | |
| x = n3; | |
| REQUIRE(31 == swoc::svto_radix<10>(x)); | |
| REQUIRE(x.size() == 0); | |
| x = n3; | |
| REQUIRE(25 == swoc::svto_radix<8>(x)); | |
| REQUIRE(x.size() == 0); | |
| // Check overflow conditions | |
| static constexpr auto UMAX = std::numeric_limits<uintmax_t>::max(); | |
| static constexpr auto IMAX = std::numeric_limits<intmax_t>::max(); | |
| static constexpr auto IMIN = std::numeric_limits<intmax_t>::min(); | |
| // One less than max. | |
| x.assign("18446744073709551614"); | |
| REQUIRE(UMAX - 1 == swoc::svto_radix<10>(x)); | |
| REQUIRE(x.size() == 0); | |
| // Exactly max. | |
| x.assign("18446744073709551615"); | |
| REQUIRE(UMAX == swoc::svto_radix<10>(x)); | |
| REQUIRE(x.size() == 0); | |
| x.assign("18446744073709551615"); | |
| CHECK(UMAX == svtou(x)); | |
| // Should overflow and clamp. | |
| x.assign("18446744073709551616"); | |
| REQUIRE(UMAX == swoc::svto_radix<10>(x)); | |
| REQUIRE(x.size() == 0); | |
| // Even more digits. | |
| x.assign("18446744073709551616123456789"); | |
| REQUIRE(UMAX == swoc::svto_radix<10>(x)); | |
| REQUIRE(x.size() == 0); | |
| // This is a special value - where N*10 > N while also overflowing. The final "1" causes this. | |
| // Be sure overflow is detected. | |
| x.assign("27381885734412615681"); | |
| REQUIRE(UMAX == swoc::svto_radix<10>(x)); | |
| x.assign("9223372036854775807"); | |
| CHECK(svtou(x) == IMAX); | |
| CHECK(svtoi(x) == IMAX); | |
| x.assign("9223372036854775808"); | |
| CHECK(svtou(x) == uintmax_t(IMAX) + 1); | |
| CHECK(svtoi(x) == IMAX); | |
| x.assign("-9223372036854775807"); | |
| CHECK(svtoi(x) == IMIN + 1); | |
| x.assign("-9223372036854775808"); | |
| CHECK(svtoi(x) == IMIN); | |
| x.assign("-9223372036854775809"); | |
| CHECK(svtoi(x) == IMIN); | |
| // floating point is never exact, so "good enough" is all that iisnts measureable. This checks the | |
| // value is within one epsilon (minimum change possible) of the compiler generated value. | |
| auto fcmp = [](double lhs, double rhs) { | |
| double tolerance = std::max({1.0, std::fabs(lhs), std::fabs(rhs)}) * std::numeric_limits<double>::epsilon(); | |
| return std::fabs(lhs - rhs) <= tolerance; | |
| }; | |
| REQUIRE(1.0 == swoc::svtod("1.0")); | |
| REQUIRE(2.0 == swoc::svtod("2.0")); | |
| REQUIRE(true == fcmp(0.1, swoc::svtod("0.1"))); | |
| REQUIRE(true == fcmp(0.1, swoc::svtod(".1"))); | |
| REQUIRE(true == fcmp(0.02, swoc::svtod("0.02"))); | |
| REQUIRE(true == fcmp(2.718281828, swoc::svtod("2.718281828"))); | |
| REQUIRE(true == fcmp(-2.718281828, swoc::svtod("-2.718281828"))); | |
| REQUIRE(true == fcmp(2.718281828, swoc::svtod("+2.718281828"))); | |
| REQUIRE(true == fcmp(0.004, swoc::svtod("4e-3"))); | |
| REQUIRE(true == fcmp(4e-3, swoc::svtod("4e-3"))); | |
| REQUIRE(true == fcmp(500000, swoc::svtod("5e5"))); | |
| REQUIRE(true == fcmp(5e5, swoc::svtod("5e+5"))); | |
| REQUIRE(true == fcmp(678900, swoc::svtod("6.789E5"))); | |
| REQUIRE(true == fcmp(6.789e5, swoc::svtod("6.789E+5"))); | |
| } | |
| TEST_CASE("TransformView", "[libswoc][TransformView]") { | |
| std::string_view source{"Evil Dave Rulz"}; | |
| std::string_view rot13("Rivy Qnir Ehym"); | |
| // Because, sadly, the type of @c tolower varies between compilers since @c noexcept | |
| // is part of the signature in C++17. | |
| swoc::TransformView<decltype(&tolower), std::string_view> xv1(&tolower, source); | |
| auto xv2 = swoc::transform_view_of(&tolower, source); | |
| // Rot13 transform | |
| auto rotter = swoc::transform_view_of( | |
| [](char c) { return isalpha(c) ? c > 'Z' ? ('a' + ((c - 'a' + 13) % 26)) : ('A' + ((c - 'A' + 13) % 26)) : c; }, source); | |
| auto identity = swoc::transform_view_of(source); | |
| TextView tv{source}; | |
| REQUIRE(xv1 == xv2); | |
| // Do this with inline post-fix increments. | |
| bool match_p = true; | |
| while (xv1) { | |
| if (*xv1++ != tolower(*tv++)) { | |
| match_p = false; | |
| break; | |
| } | |
| } | |
| REQUIRE(match_p); | |
| REQUIRE(xv1 != xv2); | |
| // Do this one with separate pre-fix increments. | |
| tv = source; | |
| match_p = true; | |
| while (xv2) { | |
| if (*xv2 != tolower(*tv)) { | |
| match_p = false; | |
| break; | |
| } | |
| ++xv2; | |
| ++tv; | |
| } | |
| REQUIRE(match_p); | |
| std::string check; | |
| std::copy(rotter.begin(), rotter.end(), std::back_inserter(check)); | |
| REQUIRE(check == rot13); | |
| check.clear(); | |
| for (auto c : identity) { | |
| check.push_back(c); | |
| } | |
| REQUIRE(check == source); | |
| check.clear(); | |
| check.append(rotter.begin(), rotter.end()); | |
| REQUIRE(check == rot13); | |
| } | |
| TEST_CASE("TextView compat", "[libswoc][TextView]") { | |
| struct Thing { | |
| int n = 0; | |
| }; | |
| std::map<TextView, Thing> map; | |
| std::unordered_map<TextView, Thing> umap; | |
| // This isn't rigorous, it's mainly testing compilation. | |
| map.insert({"bob"_tv, Thing{2}}); | |
| map["dave"] = Thing{3}; | |
| umap.insert({"bob"_tv, Thing{4}}); | |
| umap["dave"] = Thing{6}; | |
| REQUIRE(map["bob"].n == 2); | |
| REQUIRE(umap["dave"].n == 6); | |
| } | |
| TEST_CASE("TextView tokenizing", "[libswoc][TextView]") { | |
| TextView src = "alpha,bravo,,charlie"; | |
| auto tokens = {"alpha", "bravo", "", "charlie"}; | |
| for (auto token : tokens) { | |
| CAPTURE(token); | |
| REQUIRE(src.take_prefix_at(',') == token); | |
| } | |
| } |