| // Copyright 2020 The Chromium Authors | |
| // Use of this source code is governed by an MIT-style license that can be | |
| // found in the LICENSE file or at https://opensource.org/licenses/MIT. | |
| #include "third_party/liburlpattern/pattern.h" | |
| #include <algorithm> | |
| #include <iterator> | |
| #include <optional> | |
| #include <string> | |
| #include <string_view> | |
| #include <unordered_map> | |
| #include <utility> | |
| #include <vector> | |
| #include "base/types/expected.h" | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| #include "third_party/abseil-cpp/absl/status/status.h" | |
| #include "third_party/liburlpattern/options.h" | |
| #include "third_party/liburlpattern/parse.h" | |
| #include "third_party/liburlpattern/part.h" | |
| namespace { | |
| base::expected<std::string, absl::Status> PassThrough(std::string_view input) { | |
| return std::string(input); | |
| } | |
| base::expected<std::string, absl::Status> ToUpper(std::string_view input) { | |
| std::string output; | |
| std::ranges::transform(input, std::back_inserter(output), | |
| [](unsigned char c) { | |
| return static_cast<unsigned char>(std::toupper(c)); | |
| }); | |
| return base::ok(std::move(output)); | |
| } | |
| } // namespace | |
| namespace liburlpattern { | |
| void RunRegexTest(std::string_view input, | |
| std::string_view expected_regex, | |
| std::vector<std::string> expected_name_list, | |
| Options options = Options()) { | |
| auto result = Parse(input, PassThrough, options); | |
| ASSERT_TRUE(result.has_value()); | |
| auto& pattern = result.value(); | |
| std::vector<std::string> name_list; | |
| std::string regex = pattern.GenerateRegexString(&name_list); | |
| EXPECT_EQ(regex, expected_regex); | |
| EXPECT_EQ(name_list, expected_name_list); | |
| } | |
| // The following expected test case values were generated using path-to-regexp | |
| // 6.2.0. | |
| TEST(PatternRegexTest, Fixed) { | |
| RunRegexTest("/foo/bar", R"(^\/foo\/bar[\/#\?]?$)", {}); | |
| } | |
| TEST(PatternRegexTest, FixedWithModifier) { | |
| RunRegexTest("{/foo/bar}?", R"(^(?:\/foo\/bar)?[\/#\?]?$)", {}); | |
| } | |
| TEST(PatternRegexTest, Name) { | |
| RunRegexTest(":foo", R"(^([^\/#\?]+?)[\/#\?]?$)", {"foo"}); | |
| } | |
| TEST(PatternRegexTest, NameWithUnicode) { | |
| RunRegexTest(":fooßar", R"(^([^\/#\?]+?)[\/#\?]?$)", {"fooßar"}); | |
| } | |
| TEST(PatternRegexTest, NameWithOptionalModifier) { | |
| RunRegexTest(":foo?", R"(^([^\/#\?]+?)?[\/#\?]?$)", {"foo"}); | |
| } | |
| TEST(PatternRegexTest, NameWithZeroOrMoreModifier) { | |
| RunRegexTest(":foo*", R"(^((?:[^\/#\?]+?)*)[\/#\?]?$)", {"foo"}); | |
| } | |
| TEST(PatternRegexTest, NameWithOneOrMoreModifier) { | |
| RunRegexTest(":foo+", R"(^((?:[^\/#\?]+?)+)[\/#\?]?$)", {"foo"}); | |
| } | |
| TEST(PatternRegexTest, NameWithPrefix) { | |
| RunRegexTest("/foo/:bar", R"(^\/foo(?:\/([^\/#\?]+?))[\/#\?]?$)", {"bar"}); | |
| } | |
| TEST(PatternRegexTest, NameWithPrefixAndOptionalModifier) { | |
| RunRegexTest("/foo/:bar?", R"(^\/foo(?:\/([^\/#\?]+?))?[\/#\?]?$)", {"bar"}); | |
| } | |
| TEST(PatternRegexTest, NameWithPrefixAndOneOrMoreModifier) { | |
| RunRegexTest("/foo/:bar+", | |
| R"(^\/foo(?:\/((?:[^\/#\?]+?)(?:\/(?:[^\/#\?]+?))*))[\/#\?]?$)", | |
| {"bar"}); | |
| } | |
| TEST(PatternRegexTest, NameWithPrefixAndZeroOrMoreModifier) { | |
| RunRegexTest("/foo/:bar*", | |
| R"(^\/foo(?:\/((?:[^\/#\?]+?)(?:\/(?:[^\/#\?]+?))*))?[\/#\?]?$)", | |
| {"bar"}); | |
| } | |
| TEST(PatternRegexTest, Regex) { | |
| RunRegexTest("([a-z]+)", R"(^([a-z]+)[\/#\?]?$)", {"0"}); | |
| } | |
| TEST(PatternRegexTest, RegexFullWildcard) { | |
| RunRegexTest("(.*)", R"(^(.*)[\/#\?]?$)", {"0"}); | |
| } | |
| TEST(PatternRegexTest, Wildcard) { | |
| RunRegexTest("*", R"(^(.*)[\/#\?]?$)", {"0"}); | |
| } | |
| TEST(PatternRegexTest, RegexWithOptionalModifier) { | |
| RunRegexTest("([a-z]+)?", R"(^([a-z]+)?[\/#\?]?$)", {"0"}); | |
| } | |
| TEST(PatternRegexTest, WildcardWithOptionalModifier) { | |
| RunRegexTest("*?", R"(^(.*)?[\/#\?]?$)", {"0"}); | |
| } | |
| TEST(PatternRegexTest, RegexWithPrefix) { | |
| RunRegexTest("/foo/([a-z]+)", R"(^\/foo(?:\/([a-z]+))[\/#\?]?$)", {"0"}); | |
| } | |
| TEST(PatternRegexTest, WildcardWithPrefix) { | |
| RunRegexTest("/foo/*", R"(^\/foo(?:\/(.*))[\/#\?]?$)", {"0"}); | |
| } | |
| TEST(PatternRegexTest, RegexWithPrefixAndOptionalModifier) { | |
| RunRegexTest("/foo/([a-z]+)?", R"(^\/foo(?:\/([a-z]+))?[\/#\?]?$)", {"0"}); | |
| } | |
| TEST(PatternRegexTest, WildcardWithPrefixAndOptionalModifier) { | |
| RunRegexTest("/foo/*?", R"(^\/foo(?:\/(.*))?[\/#\?]?$)", {"0"}); | |
| } | |
| TEST(PatternRegexTest, RegexWithPrefixAndOneOrMoreModifier) { | |
| RunRegexTest("/foo/([a-z]+)+", | |
| R"(^\/foo(?:\/((?:[a-z]+)(?:\/(?:[a-z]+))*))[\/#\?]?$)", {"0"}); | |
| } | |
| TEST(PatternRegexTest, WildcardWithPrefixAndOneOrMoreModifier) { | |
| RunRegexTest("/foo/*+", R"(^\/foo(?:\/((?:.*)(?:\/(?:.*))*))[\/#\?]?$)", | |
| {"0"}); | |
| } | |
| TEST(PatternRegexTest, RegexWithPrefixAndZeroOrMoreModifier) { | |
| RunRegexTest("/foo/([a-z]+)*", | |
| R"(^\/foo(?:\/((?:[a-z]+)(?:\/(?:[a-z]+))*))?[\/#\?]?$)", {"0"}); | |
| } | |
| TEST(PatternRegexTest, WildcardWithPrefixAndZeroOrMoreModifier) { | |
| RunRegexTest("/foo/**", R"(^\/foo(?:\/((?:.*)(?:\/(?:.*))*))?[\/#\?]?$)", | |
| {"0"}); | |
| } | |
| TEST(PatternRegexTest, NameWithCustomRegex) { | |
| RunRegexTest(":foo([a-z]+)", R"(^([a-z]+)[\/#\?]?$)", {"foo"}); | |
| } | |
| TEST(PatternRegexTest, ManyGroups) { | |
| RunRegexTest("/([a-z])+/:foo/([a-z])+/:bar+", | |
| R"(^(?:\/((?:[a-z])(?:\/(?:[a-z]))*))(?:\/([^\/#\?]+?))(?:\/)" | |
| R"(((?:[a-z])(?:\/(?:[a-z]))*))(?:\/((?:[^\/#\?]+?)(?:\/)" | |
| R"((?:[^\/#\?]+?))*))[\/#\?]?$)", | |
| {"0", "foo", "1", "bar"}); | |
| } | |
| TEST(PatternRegexTest, StrictNameWithPrefixAndOneOrMoreModifier) { | |
| RunRegexTest("/foo/:bar+", | |
| R"(^\/foo(?:\/((?:[^\/#\?]+?)(?:\/(?:[^\/#\?]+?))*))$)", {"bar"}, | |
| {.strict = true}); | |
| } | |
| TEST(PatternRegexTest, NoEndNameWithPrefixAndOneOrMoreModifier) { | |
| RunRegexTest("/foo/:bar+", | |
| R"(^\/foo(?:\/((?:[^\/#\?]+?)(?:\/(?:[^\/#\?]+?))*))(?:[\/)" | |
| R"(#\?](?=[]|$))?(?=[\/#\?]|[]|$))", | |
| {"bar"}, {.end = false}); | |
| } | |
| TEST(PatternRegexTest, NoEndFixedWithTrailingDelimiter) { | |
| RunRegexTest("/foo/bar/", R"(^\/foo\/bar\/(?:[\/#\?](?=[]|$))?)", {}, | |
| {.end = false}); | |
| } | |
| TEST(PatternRegexTest, StrictNoEndFixedWithTrailingDelimiter) { | |
| RunRegexTest("/foo/bar/", R"(^\/foo\/bar\/)", {}, | |
| {.strict = true, .end = false}); | |
| } | |
| TEST(PatternRegexTest, StrictNoEndFixedWithoutTrailingDelimiter) { | |
| RunRegexTest("/foo/bar", R"(^\/foo\/bar(?=[\/#\?]|[]|$))", {}, | |
| {.strict = true, .end = false}); | |
| } | |
| TEST(PatternRegexTest, NoStartNameWithPrefixAndOneOrMoreModifier) { | |
| RunRegexTest("/foo/:bar+", | |
| R"(\/foo(?:\/((?:[^\/#\?]+?)(?:\/(?:[^\/#\?]+?))*))[\/#\?]?$)", | |
| {"bar"}, {.start = false}); | |
| } | |
| TEST(PatternRegexTest, EndsWithNameWithPrefixAndOneOrMoreModifier) { | |
| RunRegexTest("/foo/:bar+", | |
| R"(^\/foo(?:\/((?:[^\/#\?]+?)(?:\/(?:[^\/#\?]+?))*))[\/)" | |
| R"(#\?]?(?=[#]|$))", | |
| {"bar"}, {.ends_with = "#"}); | |
| } | |
| TEST(PatternRegexTest, EndsWithNoEndNameWithPrefixAndOneOrMoreModifier) { | |
| RunRegexTest("/foo/:bar+", | |
| R"(^\/foo(?:\/((?:[^\/#\?]+?)(?:\/(?:[^\/#\?]+?))*))(?:[\/)" | |
| R"(#\?](?=[#]|$))?(?=[\/#\?]|[#]|$))", | |
| {"bar"}, {.end = false, .ends_with = "#"}); | |
| } | |
| void RunPatternStringTest(std::string_view input, | |
| std::string_view expected_pattern_string) { | |
| auto result = Parse(input, PassThrough); | |
| ASSERT_TRUE(result.has_value()); | |
| auto& pattern = result.value(); | |
| std::string pattern_string = pattern.GeneratePatternString(); | |
| EXPECT_EQ(pattern_string, expected_pattern_string); | |
| // The computed pattern string should be valid and parse correctly. | |
| auto result2 = Parse(pattern_string, PassThrough); | |
| EXPECT_TRUE(result.has_value()); | |
| // The second Pattern object may or may not be identical to the first | |
| // due to normalization. For example, stripping the unnecessary grouping | |
| // from a `{foo}` term. | |
| // Computing a second pattern string should result in an identical | |
| // value, however. | |
| std::string pattern_string2 = result2.value().GeneratePatternString(); | |
| EXPECT_EQ(pattern_string2, pattern_string); | |
| } | |
| TEST(PatternStringTest, Fixed) { | |
| RunPatternStringTest("/foo/bar", "/foo/bar"); | |
| } | |
| TEST(PatternStringTest, Group) { | |
| RunPatternStringTest("/foo/{bar}", "/foo/bar"); | |
| } | |
| TEST(PatternStringTest, GroupWithRegexp) { | |
| RunPatternStringTest("/foo/{(bar)}", "/foo/{(bar)}"); | |
| } | |
| TEST(PatternStringTest, GroupWithPrefixAndRegexp) { | |
| RunPatternStringTest("/foo/{b(ar)}", "/foo/{b(ar)}"); | |
| } | |
| TEST(PatternStringTest, GroupWithDefaultPrefixAndRegexp) { | |
| RunPatternStringTest("/foo{/(bar)}", "/foo/(bar)"); | |
| } | |
| TEST(PatternStringTest, GroupWithRegexpAndSuffix) { | |
| RunPatternStringTest("/foo/{(ba)r}", "/foo/{(ba)r}"); | |
| } | |
| TEST(PatternStringTest, GroupWithDefaultPrefixRegexpAndSuffix) { | |
| RunPatternStringTest("/foo{/(ba)r}", "/foo{/(ba)r}"); | |
| } | |
| TEST(PatternStringTest, GroupWithQuestionModifier) { | |
| RunPatternStringTest("/foo/{bar}?", "/foo/{bar}?"); | |
| } | |
| TEST(PatternStringTest, GroupWithStarModifier) { | |
| RunPatternStringTest("/foo/{bar}*", "/foo/{bar}*"); | |
| } | |
| TEST(PatternStringTest, GroupWithPlusModifier) { | |
| RunPatternStringTest("/foo/{bar}+", "/foo/{bar}+"); | |
| } | |
| TEST(PatternStringTest, NamedGroup) { | |
| RunPatternStringTest("/foo/:bar", "/foo/:bar"); | |
| } | |
| TEST(PatternStringTest, SegmentWildcardWithoutName) { | |
| RunPatternStringTest("/foo/([^\\/#\\?]+?)", "/foo/([^\\/#\\?]+?)"); | |
| } | |
| TEST(PatternStringTest, NamedGroupWithRegexp) { | |
| RunPatternStringTest("/foo/:bar(baz)", "/foo/:bar(baz)"); | |
| } | |
| TEST(PatternStringTest, NamedGroupWithEquivalentRegexp) { | |
| RunPatternStringTest("/foo/:bar([^\\/#\\?]+?)", "/foo/:bar"); | |
| } | |
| TEST(PatternStringTest, NamedGroupWithWildcardEquivalentRegexp) { | |
| RunPatternStringTest("/foo/:bar(.*)", "/foo/:bar(.*)"); | |
| } | |
| TEST(PatternStringTest, NamedGroupWithQuestionModifier) { | |
| RunPatternStringTest("/foo/:bar?", "/foo/:bar?"); | |
| } | |
| TEST(PatternStringTest, NamedGroupWithStarModifier) { | |
| RunPatternStringTest("/foo/:bar*", "/foo/:bar*"); | |
| } | |
| TEST(PatternStringTest, NamedGroupWithPlusModifier) { | |
| RunPatternStringTest("/foo/:bar+", "/foo/:bar+"); | |
| } | |
| TEST(PatternStringTest, Regexp) { | |
| RunPatternStringTest("/foo/(bar)", "/foo/(bar)"); | |
| } | |
| TEST(PatternStringTest, RegexpWithQuestionModifier) { | |
| RunPatternStringTest("/foo/(bar)?", "/foo/(bar)?"); | |
| } | |
| TEST(PatternStringTest, RegexpWithStarModifier) { | |
| RunPatternStringTest("/foo/(bar)*", "/foo/(bar)*"); | |
| } | |
| TEST(PatternStringTest, RegexpWithPlusModifier) { | |
| RunPatternStringTest("/foo/(bar)+", "/foo/(bar)+"); | |
| } | |
| TEST(PatternStringTest, Wildcard) { | |
| RunPatternStringTest("/foo/*", "/foo/*"); | |
| } | |
| TEST(PatternStringTest, RegexpWildcardEquivalent) { | |
| RunPatternStringTest("/foo/(.*)", "/foo/*"); | |
| } | |
| TEST(PatternStringTest, RegexpEscapedNonPatternChar) { | |
| RunPatternStringTest("/foo/\\bar", "/foo/bar"); | |
| } | |
| TEST(PatternStringTest, RegexpEscapedPatternChar) { | |
| RunPatternStringTest("/foo/\\:bar", "/foo/\\:bar"); | |
| } | |
| TEST(PatternStringTest, RegexpEscapedPatternCharInPrefix) { | |
| RunPatternStringTest("/foo/{\\:bar(foo)}", "/foo/{\\:bar(foo)}"); | |
| } | |
| TEST(PatternStringTest, RegexpEscapedPatternCharInSuffix) { | |
| RunPatternStringTest("/foo/{(foo)\\:bar}", "/foo/{(foo)\\:bar}"); | |
| } | |
| TEST(PatternStringTest, RegexpFollowedByWildcard) { | |
| RunPatternStringTest("(foo)(.*)", "(foo)(.*)"); | |
| } | |
| TEST(PatternStringTest, RegexpWithOptionalModifierFollowedByWildcard) { | |
| RunPatternStringTest("(foo)?(.*)", "(foo)?*"); | |
| } | |
| TEST(PatternStringTest, RegexpWithSuffixModifierFollowedByWildcard) { | |
| RunPatternStringTest("{(foo)a}(.*)", "{(foo)a}(.*)"); | |
| } | |
| TEST(PatternStringTest, NamedGroupInGroupingFollowedByWildcard) { | |
| RunPatternStringTest("{:foo}(.*)", "{:foo}(.*)"); | |
| } | |
| TEST(PatternStringTest, NamedGroupInGroupingFollowedByRegexp) { | |
| RunPatternStringTest("{:foo}(bar)", "{:foo}(bar)"); | |
| } | |
| TEST(PatternStringTest, NamedGroupInGroupingFollowedByWildcardInGrouping) { | |
| RunPatternStringTest("{:foo}{(.*)}", "{:foo}(.*)"); | |
| } | |
| TEST(PatternStringTest, NamedGroupInGroupingFollowedByWildcardWithSuffix) { | |
| RunPatternStringTest("{:foo}{(.*)bar}", ":foo{*bar}"); | |
| } | |
| TEST(PatternStringTest, NamedGroupInGroupingFollowedByWildcardWithPrefix) { | |
| RunPatternStringTest("{:foo}{bar(.*)}", ":foo{bar*}"); | |
| } | |
| TEST(PatternStringTest, NamedGroupInGroupingFollowedByWildcardWithCustomName) { | |
| RunPatternStringTest("{:foo}:bar(.*)", ":foo:bar(.*)"); | |
| } | |
| TEST(PatternStringTest, | |
| NamedGroupInGroupingWithOptionalModifierFollowedByWildcard) { | |
| RunPatternStringTest("{:foo}?(.*)", ":foo?*"); | |
| } | |
| TEST(PatternStringTest, NamedGroupWithEscapedValidNameSuffix) { | |
| RunPatternStringTest("{:foo\\bar}", "{:foo\\bar}"); | |
| } | |
| TEST(PatternStringTest, NamedGroupWithEscapedInvalidNameSuffix) { | |
| RunPatternStringTest("{:foo\\.bar}", "{:foo.bar}"); | |
| } | |
| TEST(PatternStringTest, NamedGroupInGroupingFollowedByValidNameText) { | |
| RunPatternStringTest("{:foo}bar", "{:foo}bar"); | |
| } | |
| TEST(PatternStringTest, NamedGroupFollowedByEscapedValidNameText) { | |
| RunPatternStringTest(":foo\\bar", "{:foo}bar"); | |
| } | |
| TEST(PatternStringTest, NamedGroupInGroupingFollowedByNonAsciiValidNameText) { | |
| // U+00DF LATIN SMALL LETTER SHARP S. | |
| RunPatternStringTest("{:foo}\xC3\x9F", "{:foo}\xC3\x9F"); | |
| } | |
| TEST(PatternStringTest, NamedGroupFollowedByNonAsciiInvalidNameText) { | |
| // U+20AC EURO SIGN. | |
| RunPatternStringTest(":foo\xE2\x82\xAC", ":foo\xE2\x82\xAC"); | |
| } | |
| TEST(PatternStringTest, NamedGroupInGroupingFollowedByDigit) { | |
| // A digit cannot start a name, but it can continue one, so `:foo1` would be | |
| // read as a single `foo1` name. | |
| RunPatternStringTest("{:foo}1", "{:foo}1"); | |
| } | |
| TEST(PatternStringTest, NamedGroupFollowedByInvalidUtf8) { | |
| // Parse() rejects invalid UTF-8, but an EncodeCallback may return it, so | |
| // build the Pattern directly. An invalid byte never continues a name, even | |
| // when it is followed by a valid name code point. | |
| std::vector<Part> part_list; | |
| part_list.emplace_back(PartType::kSegmentWildcard, /*name=*/"foo", | |
| /*prefix=*/"", /*value=*/"", /*suffix=*/"", | |
| Modifier::kNone); | |
| part_list.emplace_back(PartType::kFixed, "\x80z", Modifier::kNone); | |
| Pattern pattern(std::move(part_list), Options(), "[^\\/#\\?]+?"); | |
| EXPECT_EQ(pattern.GeneratePatternString(), ":foo\x80z"); | |
| } | |
| TEST(PatternStringTest, NamedGroupWithRegexpFollowedByValidNameText) { | |
| RunPatternStringTest(":foo(baz)bar", ":foo(baz)bar"); | |
| } | |
| TEST(PatternStringTest, NamedGroupFollowedByEmptyGroupAndWildcard) { | |
| RunPatternStringTest(":foo{}(.*)", "{:foo}(.*)"); | |
| } | |
| TEST(PatternStringTest, NamedGroupFollowedByEmptyGroupAndValidNameText) { | |
| RunPatternStringTest(":foo{}bar", "{:foo}bar"); | |
| } | |
| TEST(PatternStringTest, | |
| NamedGroupFollowedByEmptyGroupWithOptionalModifierAndValidNameText) { | |
| RunPatternStringTest(":foo{}?bar", "{:foo}bar"); | |
| } | |
| TEST(PatternStringTest, NamedGroupWithRegexpFollowedByWildcard) { | |
| RunPatternStringTest(":foo(bar)(.*)", ":foo(bar)(.*)"); | |
| } | |
| TEST(PatternStringTest, NamedGroupWithRegexpAndValidNameSuffix) { | |
| RunPatternStringTest("{:foo(baz)bar}", "{:foo(baz)bar}"); | |
| } | |
| TEST(PatternStringTest, WildcardSlashAndWildcard) { | |
| RunPatternStringTest("*/*", "*/*"); | |
| } | |
| TEST(PatternStringTest, WildcardEscapedSlashAndWildcard) { | |
| // The backslash in the original input forces the `/` to not be an | |
| // implicit prefix for the second `*`. The generated pattern string | |
| // must similarly prevent the implicit prefix from occuring. This | |
| // is done using the `{}` grouping instead, however, as its a bit more | |
| // readable than escape backslashes. | |
| RunPatternStringTest("*\\/*", "*/{*}"); | |
| } | |
| TEST(PatternStringTest, WildcardSlashAndWildcardInGrouping) { | |
| RunPatternStringTest("*/{*}", "*/{*}"); | |
| } | |
| TEST(PatternStringTest, WildcardSlashSlashAndWildcard) { | |
| RunPatternStringTest("*//*", "*//*"); | |
| } | |
| TEST( | |
| PatternStringTest, | |
| WildcardFollowedByEmptyGroupWithZeroOrMoreModifierAndWildcardWithOptionalModifier) { | |
| RunPatternStringTest("*{}**?", "*(.*)?"); | |
| } | |
| TEST(PatternStringTest, CaseFromFuzzer) { | |
| RunPatternStringTest(".:bax\\a*{}**", "{.:bax}a*(.*)"); | |
| } | |
| struct DirectMatchCase { | |
| std::string_view input; | |
| bool expected_match = true; | |
| std::vector<std::pair<std::string_view, std::optional<std::string_view>>> | |
| expected_groups; | |
| }; | |
| void RunDirectMatchTest(std::string_view input, | |
| std::vector<DirectMatchCase> case_list) { | |
| auto result = | |
| Parse(input, PassThrough, | |
| {.sensitive = true, .strict = true, .end = true, .start = true}); | |
| ASSERT_TRUE(result.has_value()); | |
| auto& pattern = result.value(); | |
| EXPECT_TRUE(pattern.CanDirectMatch()); | |
| for (const auto& c : case_list) { | |
| std::vector<std::pair<std::string_view, std::optional<std::string_view>>> | |
| matched_groups; | |
| EXPECT_EQ(c.expected_match, pattern.DirectMatch(c.input, &matched_groups)); | |
| ASSERT_EQ(c.expected_groups.size(), matched_groups.size()); | |
| for (size_t i = 0; i < matched_groups.size(); ++i) { | |
| EXPECT_EQ(c.expected_groups[i], matched_groups[i]); | |
| } | |
| } | |
| } | |
| void RunDirectMatchUnsupportedTest(std::string_view input, | |
| Options options = {.sensitive = true, | |
| .strict = true, | |
| .end = true, | |
| .start = true}) { | |
| auto result = Parse(input, PassThrough, options); | |
| ASSERT_TRUE(result.has_value()); | |
| auto& pattern = result.value(); | |
| EXPECT_FALSE(pattern.CanDirectMatch()); | |
| } | |
| TEST(PatternDirectMatch, FullWildcardSupported) { | |
| RunDirectMatchTest("*", | |
| { | |
| {.input = "/foo", .expected_groups = {{"0", "/foo"}}}, | |
| {.input = "", .expected_groups = {{"0", ""}}}, | |
| }); | |
| } | |
| TEST(PatternDirectMatch, FullWildcardInGroupSupported) { | |
| RunDirectMatchTest("{*}", | |
| { | |
| {.input = "/foo", .expected_groups = {{"0", "/foo"}}}, | |
| {.input = "", .expected_groups = {{"0", ""}}}, | |
| }); | |
| } | |
| TEST(PatternDirectMatch, FullWildcardUsingRegexSupported) { | |
| RunDirectMatchTest("(.*)", | |
| { | |
| {.input = "/foo", .expected_groups = {{"0", "/foo"}}}, | |
| {.input = "", .expected_groups = {{"0", ""}}}, | |
| }); | |
| } | |
| TEST(PatternDirectMatch, FullWildcardAndOptionalModifierSupported) { | |
| RunDirectMatchTest("*?", | |
| { | |
| {.input = "/foo", .expected_groups = {{"0", "/foo"}}}, | |
| {.input = "", .expected_groups = {{"0", ""}}}, | |
| }); | |
| } | |
| TEST(PatternDirectMatch, FullWildcardAndOneOrMoreModifierSupported) { | |
| RunDirectMatchTest("*+", | |
| { | |
| {.input = "/foo", .expected_groups = {{"0", "/foo"}}}, | |
| {.input = "", .expected_groups = {{"0", ""}}}, | |
| }); | |
| } | |
| TEST(PatternDirectMatch, FullWildcardAndZeroOrMoreModifierSupported) { | |
| RunDirectMatchTest("**", | |
| { | |
| {.input = "/foo", .expected_groups = {{"0", "/foo"}}}, | |
| {.input = "", .expected_groups = {{"0", ""}}}, | |
| }); | |
| } | |
| TEST(PatternDirectMatch, FullWildcardWithNameUsingRegexSupported) { | |
| RunDirectMatchTest( | |
| ":name(.*)", { | |
| {.input = "/foo", .expected_groups = {{"name", "/foo"}}}, | |
| {.input = "", .expected_groups = {{"name", ""}}}, | |
| }); | |
| } | |
| TEST(PatternDirectMatch, OptionsFalseStartUnsupported) { | |
| RunDirectMatchUnsupportedTest( | |
| "*", {.sensitive = true, .strict = true, .end = true, .start = false}); | |
| } | |
| TEST(PatternDirectMatch, OptionsFalseEndUnsupported) { | |
| RunDirectMatchUnsupportedTest( | |
| "*", {.sensitive = true, .strict = true, .end = false, .start = true}); | |
| } | |
| TEST(PatternDirectMatch, OptionsFalseStrictUnsupported) { | |
| RunDirectMatchUnsupportedTest( | |
| "*", {.sensitive = true, .strict = false, .end = true, .start = true}); | |
| } | |
| TEST(PatternDirectMatch, OptionsFalseSensitiveUnsupported) { | |
| RunDirectMatchUnsupportedTest( | |
| "*", {.sensitive = false, .strict = true, .end = true, .start = true}); | |
| } | |
| TEST(PatternDirectMatch, FullWildcardWithPrefixUnsupported) { | |
| RunDirectMatchUnsupportedTest("/*"); | |
| } | |
| TEST(PatternDirectMatch, FullWildcardInGroupWithPrefixUnsupported) { | |
| RunDirectMatchUnsupportedTest("{foo*}"); | |
| } | |
| TEST(PatternDirectMatch, FullWildcardInGroupWithSuffixUnsupported) { | |
| RunDirectMatchUnsupportedTest("{*foo}"); | |
| } | |
| TEST(PatternDirectMatch, EmptyPatternSupported) { | |
| RunDirectMatchTest("", { | |
| {.input = "", .expected_groups = {}}, | |
| {.input = "/", .expected_match = false}, | |
| }); | |
| } | |
| TEST(PatternDirectMatch, FixedTextSupported) { | |
| RunDirectMatchTest("foo", { | |
| {.input = "foo", .expected_groups = {}}, | |
| {.input = "fo", .expected_match = false}, | |
| {.input = "foobar", .expected_match = false}, | |
| }); | |
| } | |
| TEST(PatternDirectMatch, FixedTextInGroupSupported) { | |
| RunDirectMatchTest("{foo}", { | |
| {.input = "foo", .expected_groups = {}}, | |
| {.input = "fo", .expected_match = false}, | |
| {.input = "foobar", .expected_match = false}, | |
| }); | |
| } | |
| TEST(PatternDirectMatch, FixedTextInGroupWithOptionalModifierUnsupported) { | |
| RunDirectMatchUnsupportedTest("{foo}?"); | |
| } | |
| TEST(PatternDirectMatch, FixedTextInGroupWithZeroOrMoreModifierUnsupported) { | |
| RunDirectMatchUnsupportedTest("{foo}*"); | |
| } | |
| TEST(PatternDirectMatch, FixedTextInGroupWithOneOrMoreModifierUnsupported) { | |
| RunDirectMatchUnsupportedTest("{foo}+"); | |
| } | |
| TEST(PatternDirectMatch, FixedTextAndFullWildcardUnsupported) { | |
| RunDirectMatchUnsupportedTest("/foo*"); | |
| } | |
| TEST(PatternDirectMatch, NamedGroupUnsupported) { | |
| RunDirectMatchUnsupportedTest(":name"); | |
| } | |
| TEST(PatternDirectMatch, RegexUnsupported) { | |
| RunDirectMatchUnsupportedTest("(foo)"); | |
| } | |
| void ExpectGenerateSuccess( | |
| std::string_view input_pattern, | |
| const std::unordered_map<std::string, std::string>& input_groups, | |
| std::string_view expected_output, | |
| Options options = Options(), | |
| EncodeCallback callback = PassThrough) { | |
| auto parsed = Parse(input_pattern, callback, options); | |
| ASSERT_TRUE(parsed.has_value()); | |
| Pattern& pattern = parsed.value(); | |
| auto result = pattern.Generate(input_groups, callback); | |
| EXPECT_TRUE(result.has_value()); | |
| if (result.has_value()) { | |
| EXPECT_EQ(result.value(), expected_output); | |
| } | |
| } | |
| void ExpectGenerateFailure( | |
| std::string_view input_pattern, | |
| const std::unordered_map<std::string, std::string>& input_groups, | |
| absl::StatusCode expected_error_code, | |
| Options options = Options(), | |
| EncodeCallback callback = PassThrough) { | |
| auto parsed = Parse(input_pattern, callback, options); | |
| ASSERT_TRUE(parsed.has_value()); | |
| Pattern& pattern = parsed.value(); | |
| auto result = pattern.Generate(input_groups, std::move(callback)); | |
| EXPECT_FALSE(result.has_value()); | |
| if (!result.has_value()) { | |
| // Tests only error codes, not looking into error messages. | |
| EXPECT_EQ(result.error().code(), expected_error_code); | |
| } | |
| } | |
| TEST(PatternGenerateTest, EmptyPatternSupported) { | |
| ExpectGenerateSuccess("", {}, ""); | |
| } | |
| TEST(PatternGenerateTest, FixedTextSupported) { | |
| ExpectGenerateSuccess("foo", {}, "foo"); | |
| } | |
| TEST(PatternGenerateTest, FixedTextInGroupSupported) { | |
| ExpectGenerateSuccess("{foo}", {}, "foo"); | |
| } | |
| TEST(PatternGenerateTest, ModifiersUnimplemented) { | |
| ExpectGenerateFailure("{foo}?", {}, absl::StatusCode::kUnimplemented); | |
| ExpectGenerateFailure("{foo}*", {}, absl::StatusCode::kUnimplemented); | |
| ExpectGenerateFailure("{foo}+", {}, absl::StatusCode::kUnimplemented); | |
| } | |
| TEST(PatternGenerateTest, SingleNamedGroupSupported) { | |
| ExpectGenerateSuccess(":foo", {{"foo", "bar"}}, "bar"); | |
| } | |
| TEST(PatternGenerateTest, SingleNamedGroupWithPresixSupported) { | |
| ExpectGenerateSuccess("foo:bar", {{"bar", "baz"}}, "foobaz"); | |
| } | |
| TEST(PatternGenerateTest, EncodeCallbackUsed) { | |
| ExpectGenerateSuccess("foo:bar", {{"bar", "baz"}}, "FOOBAZ", Options(), | |
| ToUpper); | |
| } | |
| TEST(PatternGenerateTest, MultipleNamedGroupsSupported) { | |
| ExpectGenerateSuccess(":foo/:bar", {{"foo", "baz"}, {"bar", "qux"}}, | |
| "baz/qux"); | |
| } | |
| TEST(PatternGnerateTest, FailsIfInputNotProvided) { | |
| ExpectGenerateFailure(":foo", {}, absl::StatusCode::kInvalidArgument); | |
| } | |
| TEST(PatternGenerateTest, RegexpUnimplemented) { | |
| ExpectGenerateFailure("(foo)", {}, absl::StatusCode::kUnimplemented); | |
| } | |
| TEST(PatternGenerateTest, FullWildcardUnimplemented) { | |
| ExpectGenerateFailure("*", {}, absl::StatusCode::kUnimplemented); | |
| } | |
| TEST(PatternGenerateTest, UnnamedSegmentWildcardUnimplemented) { | |
| ExpectGenerateFailure("([^\\/]+?)", {}, absl::StatusCode::kUnimplemented, | |
| {.delimiter_list = "/"}); | |
| } | |
| TEST(PatternGenerateTest, InputWithDelimiterCharsUnimplemented) { | |
| ExpectGenerateFailure(":foo", {{"foo", "bar/baz"}}, | |
| absl::StatusCode::kUnimplemented, | |
| {.delimiter_list = "/"}); | |
| } | |
| } // namespace liburlpattern |