| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| |
| #include "string_util.h" |
| |
| #include <gtest/gtest.h> |
| |
| #include <initializer_list> |
| #include <list> |
| #include <map> |
| #include <string> |
| #include <unordered_map> |
| #include <unordered_set> |
| |
| TEST(StringUtil, ToLower) { |
| std::map<std::string, std::string> cases{ |
| {"ABC", "abc"}, |
| {"AbC", "abc"}, |
| {"abc", "abc"}, |
| }; |
| for (auto iter = cases.begin(); iter != cases.end(); iter++) { |
| std::string input = iter->first; |
| input = util::ToLower(input); |
| ASSERT_EQ(input, iter->second); |
| } |
| } |
| |
| TEST(StringUtil, Trim) { |
| std::map<std::string, std::string> cases{ |
| {"abc", "abc"}, {" abc ", "abc"}, {"\t\tabc\t\t", "abc"}, {"\t\tabc\n\n", "abc"}, |
| {"\n\nabc\n\n", "abc"}, {" a b", "a b"}, {"a \tb\t \n", "a \tb"}, |
| }; |
| for (auto iter = cases.begin(); iter != cases.end(); iter++) { |
| std::string input = iter->first; |
| std::string output = util::Trim(input, " \t\n"); |
| ASSERT_EQ(output, iter->second); |
| } |
| } |
| |
| TEST(StringUtil, Split) { |
| std::vector<std::string> expected = {"a", "b", "c", "d"}; |
| std::vector<std::string> array = util::Split("a,b,c,d", ","); |
| ASSERT_EQ(expected, array); |
| array = util::Split("a,b,,c,d,", ","); |
| ASSERT_EQ(expected, array); |
| array = util::Split(",a,b,c,d,", ","); |
| ASSERT_EQ(expected, array); |
| array = util::Split("a b c d ", " "); |
| ASSERT_EQ(expected, array); |
| array = util::Split("a\tb\nc\t\nd ", " \t\n"); |
| ASSERT_EQ(expected, array); |
| |
| ASSERT_EQ(util::Split("a", " "), std::vector<std::string>{"a"}); |
| ASSERT_EQ(util::Split("a bc", " "), (std::vector<std::string>{"a", "bc"})); |
| ASSERT_EQ(util::Split("a b c def gh ", " "), (std::vector<std::string>{"a", "b", "c", "def", "gh"})); |
| ASSERT_EQ(util::Split(" hello;hi,,; one ;;; two,, ", " ,;"), |
| (std::vector<std::string>{"hello", "hi", "one", "two"})); |
| } |
| |
| TEST(StringUtil, TokenizeRedisProtocol) { |
| std::vector<std::string> expected = {"this", "is", "a", "test"}; |
| auto array = util::TokenizeRedisProtocol("*4\r\n$4\r\nthis\r\n$2\r\nis\r\n$1\r\na\r\n$4\r\ntest\r\n"); |
| ASSERT_EQ(expected, array); |
| } |
| |
| TEST(StringUtil, StartsEndsWith) { |
| ASSERT_TRUE(util::StartsWith("has_prefix_is_true", "has_prefix")); |
| ASSERT_FALSE(util::StartsWith("has_prefix_is_false", "_has_prefix")); |
| ASSERT_TRUE(util::StartsWith("has_prefix", "has_prefix")); |
| ASSERT_FALSE(util::StartsWith("has", "has_prefix")); |
| ASSERT_TRUE(util::EndsWith("has_suffix_is_true", "_is_true")); |
| ASSERT_FALSE(util::EndsWith("has_suffix_is_false", "has_suffix")); |
| ASSERT_TRUE(util::EndsWith("has_suffix", "has_suffix")); |
| ASSERT_FALSE(util::EndsWith("has", "has_suffix")); |
| ASSERT_TRUE(util::StartsWithICase("has_prefix_is_true", "has_PREfix")); |
| ASSERT_FALSE(util::StartsWithICase("has_prefix_is_false", "_has_prefix")); |
| ASSERT_TRUE(util::EndsWithICase("has_suffix_IS_true", "_is_true")); |
| ASSERT_FALSE(util::EndsWithICase("has_suffix_is_false", "has_suffix")); |
| } |
| |
| TEST(StringUtil, ValidateGlob) { |
| const auto expect_ok = [](std::string_view glob) { |
| const auto result = util::ValidateGlob(glob); |
| EXPECT_TRUE(result.IsOK()) << glob << ": " << result.Msg(); |
| }; |
| |
| const auto expect_error = [](std::string_view glob, std::string_view expected_error) { |
| const auto result = util::ValidateGlob(glob); |
| EXPECT_FALSE(result.IsOK()); |
| EXPECT_EQ(result.Msg(), expected_error) << glob; |
| }; |
| |
| expect_ok("a"); |
| expect_ok("\\*"); |
| expect_ok("\\?"); |
| expect_ok("\\["); |
| expect_ok("\\]"); |
| expect_ok("a*"); |
| expect_ok("a?"); |
| expect_ok("[ab]"); |
| expect_ok("[^ab]"); |
| expect_ok("[a-c]"); |
| // Surprisingly valid: this accepts the characters {a, b, c, e, f, g, -} |
| expect_ok("[a-c-e-g]"); |
| expect_ok("[^a-c]"); |
| expect_ok("[-]"); |
| expect_ok("[\\]]"); |
| expect_ok("[\\\\]"); |
| expect_ok("[\\?]"); |
| expect_ok("[\\*]"); |
| expect_ok("[\\[]"); |
| |
| expect_error("[", "Unterminated [ group"); |
| expect_error("]", "Unmatched unescaped ]"); |
| expect_error("[a", "Unterminated [ group"); |
| expect_error("\\", "Trailing unescaped backslash"); |
| |
| // Weird case: we open a character class, with the range 'a' to ']', but then never close it |
| expect_error("[a-]", "Unterminated [ group"); |
| expect_ok("[a-]]"); |
| } |
| |
| TEST(StringUtil, StringMatch) { |
| /* Some basic tests */ |
| EXPECT_TRUE(util::StringMatch("a", "a")); |
| EXPECT_FALSE(util::StringMatch("a", "b")); |
| EXPECT_FALSE(util::StringMatch("a", "aa")); |
| EXPECT_FALSE(util::StringMatch("a", "")); |
| EXPECT_TRUE(util::StringMatch("", "")); |
| EXPECT_FALSE(util::StringMatch("", "a")); |
| EXPECT_TRUE(util::StringMatch("*", "")); |
| EXPECT_TRUE(util::StringMatch("*", "a")); |
| |
| /* Simple character class tests */ |
| EXPECT_TRUE(util::StringMatch("[a]", "a")); |
| EXPECT_FALSE(util::StringMatch("[a]", "b")); |
| EXPECT_FALSE(util::StringMatch("[^a]", "a")); |
| EXPECT_TRUE(util::StringMatch("[^a]", "b")); |
| EXPECT_TRUE(util::StringMatch("[ab]", "a")); |
| EXPECT_TRUE(util::StringMatch("[ab]", "b")); |
| EXPECT_FALSE(util::StringMatch("[ab]", "c")); |
| EXPECT_TRUE(util::StringMatch("[^ab]", "c")); |
| EXPECT_TRUE(util::StringMatch("[a-c]", "b")); |
| EXPECT_FALSE(util::StringMatch("[a-c]", "d")); |
| |
| /* Corner cases in character class parsing */ |
| EXPECT_TRUE(util::StringMatch("[a-c-e-g]", "-")); |
| EXPECT_FALSE(util::StringMatch("[a-c-e-g]", "d")); |
| EXPECT_TRUE(util::StringMatch("[a-c-e-g]", "f")); |
| |
| /* Escaping */ |
| EXPECT_TRUE(util::StringMatch("\\?", "?")); |
| EXPECT_FALSE(util::StringMatch("\\?", "a")); |
| EXPECT_TRUE(util::StringMatch("\\*", "*")); |
| EXPECT_FALSE(util::StringMatch("\\*", "a")); |
| EXPECT_TRUE(util::StringMatch("\\[", "[")); |
| EXPECT_TRUE(util::StringMatch("\\]", "]")); |
| EXPECT_TRUE(util::StringMatch("\\\\", "\\")); |
| EXPECT_TRUE(util::StringMatch("[\\.]", ".")); |
| EXPECT_TRUE(util::StringMatch("[\\-]", "-")); |
| EXPECT_TRUE(util::StringMatch("[\\[]", "[")); |
| EXPECT_TRUE(util::StringMatch("[\\]]", "]")); |
| EXPECT_TRUE(util::StringMatch("[\\\\]", "\\")); |
| EXPECT_TRUE(util::StringMatch("[\\?]", "?")); |
| EXPECT_TRUE(util::StringMatch("[\\*]", "*")); |
| |
| /* Simple wild cards */ |
| EXPECT_TRUE(util::StringMatch("?", "a")); |
| EXPECT_FALSE(util::StringMatch("?", "aa")); |
| EXPECT_FALSE(util::StringMatch("??", "a")); |
| EXPECT_TRUE(util::StringMatch("?x?", "axb")); |
| EXPECT_FALSE(util::StringMatch("?x?", "abx")); |
| EXPECT_FALSE(util::StringMatch("?x?", "xab")); |
| |
| /* Asterisk wild cards (backtracking) */ |
| EXPECT_FALSE(util::StringMatch("*??", "a")); |
| EXPECT_TRUE(util::StringMatch("*??", "ab")); |
| EXPECT_TRUE(util::StringMatch("*??", "abc")); |
| EXPECT_TRUE(util::StringMatch("*??", "abcd")); |
| EXPECT_FALSE(util::StringMatch("??*", "a")); |
| EXPECT_TRUE(util::StringMatch("??*", "ab")); |
| EXPECT_TRUE(util::StringMatch("??*", "abc")); |
| EXPECT_TRUE(util::StringMatch("??*", "abcd")); |
| EXPECT_FALSE(util::StringMatch("?*?", "a")); |
| EXPECT_TRUE(util::StringMatch("?*?", "ab")); |
| EXPECT_TRUE(util::StringMatch("?*?", "abc")); |
| EXPECT_TRUE(util::StringMatch("?*?", "abcd")); |
| EXPECT_TRUE(util::StringMatch("*b", "b")); |
| EXPECT_TRUE(util::StringMatch("*b", "ab")); |
| EXPECT_FALSE(util::StringMatch("*b", "ba")); |
| EXPECT_TRUE(util::StringMatch("*b", "bb")); |
| EXPECT_TRUE(util::StringMatch("*b", "abb")); |
| EXPECT_TRUE(util::StringMatch("*b", "bab")); |
| EXPECT_TRUE(util::StringMatch("*bc", "abbc")); |
| EXPECT_TRUE(util::StringMatch("*bc", "bc")); |
| EXPECT_TRUE(util::StringMatch("*bc", "bbc")); |
| EXPECT_TRUE(util::StringMatch("*bc", "bcbc")); |
| |
| /* Multiple asterisks (complex backtracking) */ |
| EXPECT_TRUE(util::StringMatch("*ac*", "abacadaeafag")); |
| EXPECT_TRUE(util::StringMatch("*ac*ae*ag*", "abacadaeafag")); |
| EXPECT_TRUE(util::StringMatch("*a*b*[bc]*[ef]*g*", "abacadaeafag")); |
| EXPECT_FALSE(util::StringMatch("*a*b*[ef]*[cd]*g*", "abacadaeafag")); |
| EXPECT_TRUE(util::StringMatch("*abcd*", "abcabcabcabcdefg")); |
| EXPECT_TRUE(util::StringMatch("*ab*cd*", "abcabcabcabcdefg")); |
| EXPECT_TRUE(util::StringMatch("*abcd*abcdef*", "abcabcdabcdeabcdefg")); |
| EXPECT_FALSE(util::StringMatch("*abcd*", "abcabcabcabcefg")); |
| EXPECT_FALSE(util::StringMatch("*ab*cd*", "abcabcabcabcefg")); |
| |
| /* Robustness to exponential blow-ups with lots of non-collapsible asterisks */ |
| EXPECT_TRUE( |
| util::StringMatch("?*?*?*?*?*?*?*?*?*?*?*?*?*?*?*?*?*?*?*?*a", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); |
| EXPECT_FALSE( |
| util::StringMatch("?*?*?*?*?*?*?*?*?*?*?*?*?*?*?*?*?*?*?*?*b", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); |
| } |
| |
| TEST(StringUtil, SplitGlob) { |
| using namespace std::string_literals; |
| |
| // Basic functionality: no escaped characters |
| EXPECT_EQ(util::SplitGlob(""), std::make_pair(""s, ""s)); |
| EXPECT_EQ(util::SplitGlob("string"), std::make_pair("string"s, ""s)); |
| EXPECT_EQ(util::SplitGlob("string*"), std::make_pair("string"s, "*"s)); |
| EXPECT_EQ(util::SplitGlob("*string"), std::make_pair(""s, "*string"s)); |
| EXPECT_EQ(util::SplitGlob("str*ing"), std::make_pair("str"s, "*ing"s)); |
| EXPECT_EQ(util::SplitGlob("string?"), std::make_pair("string"s, "?"s)); |
| EXPECT_EQ(util::SplitGlob("?string"), std::make_pair(""s, "?string"s)); |
| EXPECT_EQ(util::SplitGlob("ab[cd]ef"), std::make_pair("ab"s, "[cd]ef"s)); |
| |
| // Escaped characters; also tests that prefix is trimmed of backslashes |
| EXPECT_EQ(util::SplitGlob("str\\*ing*"), std::make_pair("str*ing"s, "*"s)); |
| EXPECT_EQ(util::SplitGlob("str\\?ing?"), std::make_pair("str?ing"s, "?"s)); |
| EXPECT_EQ(util::SplitGlob("str\\[ing[a]"), std::make_pair("str[ing"s, "[a]"s)); |
| } |
| |
| TEST(StringUtil, EscapeString) { |
| std::unordered_map<std::string, std::string> origin_to_escaped = { |
| {"abc", "abc"}, |
| {"abc\r\n", "abc\\r\\n"}, |
| {"\a\n\1foo\r", "\\a\\n\\x01foo\\r"}, |
| }; |
| |
| for (const auto &item : origin_to_escaped) { |
| const std::string &origin = item.first; |
| const std::string &escaped = item.second; |
| ASSERT_TRUE(util::EscapeString(origin) == escaped); |
| } |
| } |
| |
| TEST(StringUtil, RegexMatchExtractSSTFile) { |
| // Test for ExtractSSTFileNameFromError() in event_listener.cc |
| auto bg_error_str = {"Corruption: Corrupt or unsupported format_version: 1005 in /tmp/kvrocks/data/db/000038.sst", |
| "Corruption: Bad table magic number: expected 9863518390377041911, found 9863518390377041912 in " |
| "/tmp/kvrocks_db/data/db/000038.sst", |
| "Corruption: block checksum mismatch: stored = 3308200672, computed = 51173877, type = 4 in " |
| "/tmp/kvrocks_db/data/db/000038.sst offset 0 size 15715"}; |
| |
| for (const auto &str : bg_error_str) { |
| auto match_results = util::RegexMatch(str, ".*(/\\w*\\.sst).*"); |
| ASSERT_TRUE(match_results.size() == 2); |
| ASSERT_TRUE(match_results[1] == "/000038.sst"); |
| } |
| } |
| |
| TEST(StringUtil, SplitArguments) { |
| std::map<std::string, std::vector<std::string>> valid_cases = { |
| // With ' ' only |
| {"a b c", {"a", "b", "c"}}, |
| // Other whitespace characters should work |
| {"a\tb\nc\fd", {"a", "b", "c", "d"}}, |
| |
| // With double quote escape characters |
| {R"(hello "a b" c)", {"hello", "a b", "c"}}, |
| // With single quote escape characters |
| {R"('a b' c)", {"a b", "c"}}, |
| // With both single and double quote escape characters |
| {R"(a 'b c' " d e ")", {"a", "b c", " d e "}}, |
| // With both single and double quote escape characters |
| {R"(a " b c " 'd e')", {"a", " b c ", "d e"}}, |
| |
| // With the single quote escape characters |
| {R"('a\' b' c)", {"a' b", "c"}}, |
| {R"('a\n\t\r\'b' c)", {R"(a\n\t\r'b)", "c"}}, |
| |
| // With the double quote escape characters |
| {R"("a\"b" c)", {"a\"b", "c"}}, |
| {R"("a\n\t\qb\g" c)", {"a\n\tqbg", "c"}}, |
| |
| // Escape with the hex digits |
| {R"(\x61 \x62 \x63)", {R"(\x61)", R"(\x62)", R"(\x63)"}}, |
| {R"("a \x61\x62" "\x63")", {"a ab", "c"}}, |
| // '\' will be removed from '\xT0' because it's not v alid hex digit and a valid escape sequence |
| {R"("a \xT0\x62" "\x63")", {R"(a xT0b)", "c"}}, |
| {R"("a b\x6Fc" "d\x63e")", {"a boc", "dce"}}, |
| |
| }; |
| for (const auto &item : valid_cases) { |
| const std::string &input = item.first; |
| const std::vector<std::string> &expected = item.second; |
| auto result = util::SplitArguments(input); |
| ASSERT_TRUE(result.IsOK()); |
| ASSERT_EQ(result.GetValue(), expected); |
| } |
| |
| // invalid cases |
| std::map<std::string, std::string> invalid_cases = { |
| {R"(a "b c)", "unclosed quote string"}, |
| {R"(a 'b c)", "unclosed quote string"}, |
| {R"(a "b' c)", "unclosed quote string"}, |
| {R"(a 'b" c)", "unclosed quote string"}, |
| {R"(a b 'c\)", "unclosed quote string"}, |
| {R"(a b "c\)", "unexpected trailing escape character"}, |
| {R"(a b "c"d)", "the closed double quote must be followed by a space"}, |
| {R"(a 'b'c)", "the closed single quote must be followed by a space"}, |
| }; |
| for (const auto &item : invalid_cases) { |
| const std::string &input = item.first; |
| const std::string &expected_error = item.second; |
| auto result = util::SplitArguments(input); |
| ASSERT_FALSE(result.IsOK()); |
| ASSERT_EQ(result.Msg(), expected_error); |
| } |
| } |
| |
| TEST(StringUtil, StringJoin) { |
| std::vector<std::string> vec{"a", "b", "c"}; |
| std::list<std::string> lst{"a", "b", "c"}; |
| std::set<std::string> st{"a", "b", "c"}; |
| |
| auto func = [](const std::string &s) { return "[" + s + "]"; }; |
| |
| ASSERT_EQ(util::StringJoin(vec), "a, b, c"); |
| ASSERT_EQ(util::StringJoin(vec, "; "), "a; b; c"); |
| ASSERT_EQ(util::StringJoin(vec, func), "[a], [b], [c]"); |
| ASSERT_EQ(util::StringJoin(vec, func, "; "), "[a]; [b]; [c]"); |
| |
| ASSERT_EQ(util::StringJoin(lst), "a, b, c"); |
| ASSERT_EQ(util::StringJoin(lst, "; "), "a; b; c"); |
| ASSERT_EQ(util::StringJoin(lst, func), "[a], [b], [c]"); |
| ASSERT_EQ(util::StringJoin(lst, func, "; "), "[a]; [b]; [c]"); |
| |
| ASSERT_EQ(util::StringJoin(st), "a, b, c"); |
| ASSERT_EQ(util::StringJoin(st, "; "), "a; b; c"); |
| ASSERT_EQ(util::StringJoin(st, func), "[a], [b], [c]"); |
| ASSERT_EQ(util::StringJoin(st, func, "; "), "[a]; [b]; [c]"); |
| } |