#include #include #include "UaParser.h" #include "internal/AlternativeExpander.h" #include "internal/Pattern.h" #include "internal/ReplaceTemplate.h" #include "internal/SnippetIndex.h" #ifdef WITH_MT_TEST #include #endif // WITH_MT_TEST namespace { const std::string UA_CORE_DIR = "./uap-core"; const uap_cpp::UserAgentParser g_ua_parser(UA_CORE_DIR + "/regexes.yaml"); TEST(UserAgentParser, basic) { const auto uagent = g_ua_parser.parse( "Mozilla/5.0 (iPhone; CPU iPhone OS 5_1_1 like Mac OS X) " "AppleWebKit/534.46 " "(KHTML, like Gecko) Version/5.1 Mobile/9B206 Safari/7534.48.3"); ASSERT_EQ("Mobile Safari", uagent.browser.family); ASSERT_EQ("5", uagent.browser.major); ASSERT_EQ("1", uagent.browser.minor); ASSERT_EQ("", uagent.browser.patch); ASSERT_EQ("Mobile Safari 5.1.0", uagent.browser.toString()); ASSERT_EQ("5.1.0", uagent.browser.toVersionString()); ASSERT_EQ("iOS", uagent.os.family); ASSERT_EQ("5", uagent.os.major); ASSERT_EQ("1", uagent.os.minor); ASSERT_EQ("1", uagent.os.patch); ASSERT_EQ("iOS 5.1.1", uagent.os.toString()); ASSERT_EQ("5.1.1", uagent.os.toVersionString()); ASSERT_EQ("Mobile Safari 5.1.0/iOS 5.1.1", uagent.toFullString()); ASSERT_EQ("iPhone", uagent.device.family); ASSERT_FALSE(uagent.isSpider()); } TEST(UserAgentParser, DeviceTypeMobile) { EXPECT_TRUE(uap_cpp::UserAgentParser::device_type( "Mozilla/5.0 (iPhone; CPU iPhone OS 5_1_1 like Mac OS X) " "AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 " "Mobile/9B206 Safari/7534.48.3") == uap_cpp::DeviceType::kMobile); EXPECT_TRUE( uap_cpp::UserAgentParser::device_type( "Mozilla/5.0 (Linux; Android 8.0.0; SAMSUNG SM-J330FN Build/R16NW) " "AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/7.2 " "Chrome/11.1.1111.111 Mobile Safari/537.36") == uap_cpp::DeviceType::kMobile); } TEST(UserAgentParser, DeviceTypeTablet) { EXPECT_TRUE(uap_cpp::UserAgentParser::device_type( "Mozilla/5.0 (Linux; U; en-us; KFTT Build/IML74K) " "AppleWebKit/535.19 (KHTML, like Gecko) Silk/2.0 " "Safari/535.19 Silk-Accelerated=false") == uap_cpp::DeviceType::kTablet); EXPECT_TRUE( uap_cpp::UserAgentParser::device_type( "Mozilla/5.0 (Linux; Android 9; SHT-AL09 Build/HUAWEISHT-AL09; wv) " "AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 " "Chrome/79.0.3945.136 Safari/537.36") == uap_cpp::DeviceType::kTablet); } TEST(UserAgentParser, DeviceTypeDesktop) { ASSERT_TRUE( uap_cpp::UserAgentParser::device_type( "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.4 (KHTML, like " "Gecko) Chrome/98 Safari/537.4 (StatusCake)") == uap_cpp::DeviceType::kDesktop); } TEST(UserAgentParser, DeviceTypeMobileOrTablet) { const auto test_cases = YAML::LoadFile("./test_device_type_mobile.yaml"); for (const auto& test : test_cases) { const auto device_type = uap_cpp::UserAgentParser::device_type(test.as()); ASSERT_TRUE(device_type == uap_cpp::DeviceType::kMobile || device_type == uap_cpp::DeviceType::kTablet); } } bool has_field(const YAML::Node& root, const std::string& fname) { const auto& yaml_field = root[fname]; return yaml_field.IsDefined(); } std::string string_field(const YAML::Node& root, const std::string& fname) { const auto& yaml_field = root[fname]; return yaml_field.IsNull() ? "" : yaml_field.as(); } void test_browser_or_os(const std::string file_path, const bool browser) { auto root = YAML::LoadFile(file_path); const auto& test_cases = root["test_cases"]; for (const auto& test : test_cases) { const auto major = string_field(test, "major"); const auto minor = string_field(test, "minor"); const auto patch = string_field(test, "patch"); const bool has_patch_minor = has_field(test, "patch_minor"); const auto patch_minor = has_patch_minor ? string_field(test, "patch_minor") : ""; const auto family = string_field(test, "family"); const auto unparsed = string_field(test, "user_agent_string"); const auto uagent = g_ua_parser.parse(unparsed); const auto& agent = browser ? uagent.browser : uagent.os; EXPECT_EQ(major, agent.major); EXPECT_EQ(minor, agent.minor); EXPECT_EQ(patch, agent.patch); if (has_patch_minor && !browser) { EXPECT_EQ(patch_minor, agent.patch_minor); } EXPECT_EQ(family, agent.family); } } void test_device(const std::string file_path) { auto root = YAML::LoadFile(file_path); const auto& test_cases = root["test_cases"]; for (const auto& test : test_cases) { const auto unparsed = string_field(test, "user_agent_string"); const auto uagent = g_ua_parser.parse(unparsed); const auto family = string_field(test, "family"); const auto brand = string_field(test, "brand"); const auto model = string_field(test, "model"); EXPECT_EQ(family, uagent.device.family); EXPECT_EQ(brand, uagent.device.brand); EXPECT_EQ(model, uagent.device.model); } } TEST(OsVersion, test_os) { test_browser_or_os(UA_CORE_DIR + "/tests/test_os.yaml", false); } TEST(OsVersion, test_ua) { test_browser_or_os(UA_CORE_DIR + "/tests/test_ua.yaml", true); } TEST(BrowserVersion, firefox_user_agent_strings) { test_browser_or_os( UA_CORE_DIR + "/test_resources/firefox_user_agent_strings.yaml", true); } TEST(BrowserVersion, opera_mini_user_agent_strings) { test_browser_or_os( UA_CORE_DIR + "/test_resources/opera_mini_user_agent_strings.yaml", true); } TEST(BrowserVersion, pgts_browser_list) { test_browser_or_os(UA_CORE_DIR + "/test_resources/pgts_browser_list.yaml", true); } TEST(OsVersion, additional_os_tests) { test_browser_or_os(UA_CORE_DIR + "/test_resources/additional_os_tests.yaml", false); } TEST(BrowserVersion, podcasting_user_agent_strings) { test_browser_or_os( UA_CORE_DIR + "/test_resources/podcasting_user_agent_strings.yaml", true); } TEST(DeviceFamily, test_device) { test_device(UA_CORE_DIR + "/tests/test_device.yaml"); } #ifdef WITH_MT_TEST namespace { void do_multithreaded_test(const std::function& work) { static constexpr int NUM_WORKERS = 4; std::vector> workers; for (int i = 0; i < NUM_WORKERS; ++i) { workers.push_back(std::async(std::launch::async, [&work]() { work(); })); } for (auto& worker : workers) { worker.wait(); } } } // namespace TEST(OsVersion, test_os_mt) { do_multithreaded_test( [] { test_browser_or_os(UA_CORE_DIR + "/tests/test_os.yaml", false); }); } TEST(OsVersion, test_ua_mt) { do_multithreaded_test( [] { test_browser_or_os(UA_CORE_DIR + "/tests/test_ua.yaml", true); }); } TEST(BrowserVersion, firefox_user_agent_strings_mt) { do_multithreaded_test([] { test_browser_or_os( UA_CORE_DIR + "/test_resources/firefox_user_agent_strings.yaml", true); }); } TEST(BrowserVersion, opera_mini_user_agent_strings_mt) { do_multithreaded_test([] { test_browser_or_os( UA_CORE_DIR + "/test_resources/opera_mini_user_agent_strings.yaml", true); }); } TEST(BrowserVersion, pgts_browser_list_mt) { do_multithreaded_test([] { test_browser_or_os(UA_CORE_DIR + "/test_resources/pgts_browser_list.yaml", true); }); } TEST(OsVersion, additional_os_tests_mt) { do_multithreaded_test([] { test_browser_or_os(UA_CORE_DIR + "/test_resources/additional_os_tests.yaml", false); }); } TEST(BrowserVersion, podcasting_user_agent_strings_mt) { do_multithreaded_test([] { test_browser_or_os( UA_CORE_DIR + "/test_resources/podcasting_user_agent_strings.yaml", true); }); } TEST(DeviceFamily, test_device_mt) { do_multithreaded_test( [] { test_device(UA_CORE_DIR + "/tests/test_device.yaml"); }); } #endif // WITH_MT_TEST void test_snippets(const std::string& expression, std::vector should_match) { uap_cpp::SnippetIndex index; auto snippet_set = index.registerSnippets(expression); auto snippet_string_map = index.getRegisteredSnippets(); std::vector snippet_strings; for (auto snippetId : snippet_set) { auto it = snippet_string_map.find(snippetId); ASSERT_NE(it, snippet_string_map.end()); snippet_strings.emplace_back(it->second); } EXPECT_EQ(snippet_strings, should_match); } TEST(SnippetIndex, snippets) { test_snippets("foo.bar", {"foo", "bar"}); test_snippets("foodbar", {"foodbar"}); test_snippets("food?bar", {"foo", "bar"}); test_snippets("foo.?bar", {"foo", "bar"}); test_snippets("foo\\.bar", {"foo", ".bar"}); test_snippets("(foo)", {"foo"}); test_snippets("toto(foo|bar)tata", {"toto", "tata"}); test_snippets("toto(foo)tata", {"toto", "foo", "tata"}); test_snippets("toto(foo)?tata", {"toto", "tata"}); test_snippets("toto(foo)*tata", {"toto", "tata"}); test_snippets("toto(foo){0,10}tata", {"toto", "tata"}); test_snippets("toto(foo){0,}tata", {"toto", "tata"}); test_snippets("toto(foo){1,10}tata", {"toto", "foo", "tata"}); test_snippets("toto(foo){1,}tata", {"toto", "foo", "tata"}); test_snippets("foo[abc]bar", {"foo", "bar"}); test_snippets("foo[abc]+bar", {"foo", "bar"}); test_snippets("foo|bar", {}); test_snippets("foo[|]bar", {"foo", "bar"}); test_snippets("[(foo)]bar", {"bar"}); test_snippets("[]foo]bar", {"bar"}); test_snippets("(foo[abc)])bar", {"foo", "bar"}); test_snippets("(foo(abc))bar", {"foo", "abc", "bar"}); test_snippets("Foo.bAR", {"foo", "bar"}); test_snippets("/(\\d+)\\.?foo(\\d+)", {"foo"}); test_snippets("(?:foo|bar);.*(baz)/(\\d+)\\.(\\d+)", {"baz"}); } void test_expand(const std::string& expression, std::vector should_match) { EXPECT_EQ(uap_cpp::AlternativeExpander::expand(expression), should_match); } TEST(AlternativeExpander, expansions) { test_expand("a", {"a"}); test_expand("(a)", {"(a)"}); test_expand("(a|b)", {"(a)", "(b)"}); test_expand("(?:a|b)", {"(?:a)", "(?:b)"}); test_expand("(a|b)?", {"(a|b)?"}); test_expand("(a|b)+", {"(a)+", "(b)+"}); test_expand("(a|b)*", {"(a|b)*"}); test_expand("(a|b){0,2}", {"(a|b){0,2}"}); test_expand("a|b", {"a", "b"}); test_expand("x(a|b)y", {"x(a)y", "x(b)y"}); test_expand("(a|b)yz", {"(a)yz", "(b)yz"}); test_expand("xa|by", {"xa", "by"}); test_expand("(a|b)(c|d)", {"(a)(c)", "(a)(d)", "(b)(c)", "(b)(d)"}); test_expand("(a(b|c)|d)", {"(a(b))", "(a(c))", "(d)"}); test_expand("(a", {"(a"}); test_expand("(a|b", {"(a|b"}); test_expand("((a", {"((a"}); test_expand("((a|b", {"((a|b"}); test_expand("(a((a|b", {"(a((a|b"}); test_expand("[ab]", {"[ab]"}); test_expand("[(|)]", {"[(|)]"}); test_expand("[(a|a)]", {"[(a|a)]"}); test_expand("a(|)b", {"a()b", "a()b"}); test_expand("([ab]|[bc])", {"([ab])", "([bc])"}); test_expand("[[](a|b)", {"[[](a)", "[[](b)"}); test_expand("[]abc](a|b)", {"[]abc](a)", "[]abc](b)"}); } std::string match_and_expand(const std::string& expression, const std::string& input_string, const std::string& replace_template) { uap_cpp::Pattern p(expression); uap_cpp::Match m; if (p.match(input_string, m)) { return uap_cpp::ReplaceTemplate(replace_template).expand(m); } return ""; } TEST(ReplaceTemplate, expansions) { EXPECT_EQ(match_and_expand("something", "other", "foo"), ""); EXPECT_EQ(match_and_expand("something", "something", "foo"), "foo"); EXPECT_EQ(match_and_expand("some(thing)", "something", "no$1"), "nothing"); EXPECT_EQ(match_and_expand("so([Mm])eth(ing)", "that's something!", "$1$2$3"), "ming"); EXPECT_EQ(match_and_expand("([^ ]+) (.+)", "a b", "$2-$1"), "b-a"); } } // namespace int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }