#include #include #include #include #include #include #include #include #include #include #include #include #if defined(WIN32) #include #ifdef GetObject #undef GetObject #endif #else #include #endif using namespace mbgl; using Message = std::pair; using Messages = std::vector; class StyleParserTest : public ::testing::TestWithParam {}; TEST_P(StyleParserTest, ParseStyle) { const std::string base = std::string("test/fixtures/style_parser/") + GetParam(); using namespace std::string_literals; SCOPED_TRACE("Loading: "s + base); FixtureLog log; rapidjson::GenericDocument, rapidjson::CrtAllocator> infoDoc; infoDoc.Parse<0>(util::read_file(base + ".info.json").c_str()); ASSERT_FALSE(infoDoc.HasParseError()); ASSERT_TRUE(infoDoc.IsObject()); style::Parser parser; if (auto error = parser.parse(util::read_file(base + ".style.json"))) { Log::Error(Event::ParseStyle, "Failed to parse style: " + util::toString(error)); } ASSERT_TRUE(infoDoc.IsObject()); for (const auto& property : infoDoc.GetObject()) { const std::string name{property.name.GetString(), property.name.GetStringLength()}; const JSValue& value = property.value; ASSERT_EQ(true, value.IsObject()); if (value.HasMember("log")) { const JSValue& js_log = value["log"]; ASSERT_EQ(true, js_log.IsArray()); for (auto& js_entry : js_log.GetArray()) { ASSERT_EQ(true, js_entry.IsArray()); ASSERT_GE(4u, js_entry.Size()); const uint32_t count = js_entry[rapidjson::SizeType(0)].GetUint(); const FixtureLogObserver::LogMessage message{ *Enum::toEnum(js_entry[rapidjson::SizeType(1)].GetString()), *Enum::toEnum(js_entry[rapidjson::SizeType(2)].GetString()), int64_t(-1), js_entry[rapidjson::SizeType(3)].GetString()}; #if defined(WIN32) Sleep(10); #endif SCOPED_TRACE("Checking: "s + message.msg); const auto observedCount = log.count(message); EXPECT_EQ(count, observedCount) << "Message: " << message << std::endl; } } const auto& unchecked = log.unchecked(); if (unchecked.size()) { std::cerr << "Unchecked Log Messages (" << base << "/" << name << "): " << std::endl << unchecked; } ASSERT_EQ(0u, unchecked.size()); } } static void populateNames(std::vector& names) { const std::string ending = ".info.json"; std::string style_directory = "test/fixtures/style_parser"; auto testName = [&](const std::string& name) { if (name.length() >= ending.length() && name.ends_with(ending)) { names.push_back(name.substr(0, name.length() - ending.length())); } }; #if defined(WIN32) style_directory += "/*"; WIN32_FIND_DATAA ffd; HANDLE hFind = FindFirstFileA(style_directory.c_str(), &ffd); if (hFind != INVALID_HANDLE_VALUE) { do { const std::string name = ffd.cFileName; testName(name); } while (FindNextFileA(hFind, &ffd) != 0); FindClose(hFind); } #else DIR* dir = opendir(style_directory.c_str()); if (dir != nullptr) { for (dirent* dp = nullptr; (dp = readdir(dir)) != nullptr;) { const std::string name = dp->d_name; testName(name); } closedir(dir); } #endif } INSTANTIATE_TEST_SUITE_P(StyleParser, StyleParserTest, ::testing::ValuesIn([] { std::vector names; populateNames(names); EXPECT_GT(names.size(), 0u); return names; }())); TEST(StyleParser, SpriteAsString) { style::Parser parser; parser.parse(R"({ "version": 8, "sprite": "https://example.com/default/markers" })"); auto result = &parser.sprites; ASSERT_EQ(1, result->size()); ASSERT_EQ("https://example.com/default/markers", result->at(0).spriteURL); ASSERT_EQ("default", result->at(0).id); } TEST(StyleParser, SpriteAsArrayEmpty) { style::Parser parser; parser.parse(R"({ "version": 8, "sprite": [] })"); auto result = &parser.sprites; ASSERT_EQ(0, result->size()); } TEST(StyleParser, SpriteAsArraySingle) { style::Parser parser; parser.parse(R"({ "version": 8, "sprite": [{ "id": "default", "url": "https://example.com/default/markers" }] })"); auto result = &parser.sprites; ASSERT_EQ(1, result->size()); ASSERT_EQ("https://example.com/default/markers", result->at(0).spriteURL); ASSERT_EQ("default", result->at(0).id); } TEST(StyleParser, SpriteAsArrayMultiple) { style::Parser parser; parser.parse(R"({ "version": 8, "sprite": [{ "id": "default", "url": "https://example.com/default/markers" },{ "id": "hiking", "url": "https://example.com/hiking/markers" }] })"); auto result = &parser.sprites; ASSERT_EQ(2, result->size()); ASSERT_EQ("https://example.com/default/markers", result->at(0).spriteURL); ASSERT_EQ("default", result->at(0).id); ASSERT_EQ("https://example.com/hiking/markers", result->at(1).spriteURL); ASSERT_EQ("hiking", result->at(1).id); } TEST(StyleParser, FontStacks) { style::Parser parser; parser.parse(util::read_file("test/fixtures/style_parser/font_stacks.json")); std::set expected = { {"a"}, {"a", "b"}, {"a", "b", "c"}, }; std::set result = parser.fontStacks(); ASSERT_EQ(expected, result); } TEST(StyleParser, FontStacksNoTextField) { style::Parser parser; parser.parse(R"({ "version": 8, "layers": [{ "id": "symbol", "type": "symbol", "source": "vector", "layout": { "text-font": ["a"] } }] })"); auto result = parser.fontStacks(); ASSERT_EQ(0u, result.size()); } TEST(StyleParser, FontStacksCaseExpression) { style::Parser parser; parser.parse(R"({ "version": 8, "layers": [{ "id": "symbol", "type": "symbol", "source": "vector", "layout": { "text-field": "a", "text-font": ["case", ["==", "a", ["string", ["get", "text-font"]]], ["literal", ["Arial"]], ["literal", ["Helvetica"]]] } }] })"); std::set expected; expected.insert(FontStack({"Arial"})); expected.insert(FontStack({"Helvetica"})); std::set result = parser.fontStacks(); ASSERT_EQ(expected, result); } TEST(StyleParser, FontStacksMatchExpression) { style::Parser parser; parser.parse(R"({ "version": 8, "layers": [{ "id": "symbol", "type": "symbol", "source": "vector", "layout": { "text-field": "a", "text-font": ["match", ["get", "text-font"], "a", ["literal", ["Arial"]], ["literal", ["Helvetica"]]] } }] })"); std::set expected; expected.insert(FontStack({"Arial"})); expected.insert(FontStack({"Helvetica"})); std::set result = parser.fontStacks(); ASSERT_EQ(expected, result); } TEST(StyleParser, FontStacksStepExpression) { style::Parser parser; parser.parse(R"({ "version": 8, "layers": [{ "id": "symbol", "type": "symbol", "source": "vector", "layout": { "text-field": "a", "text-font": ["array", "string", ["step", ["get", "text-font"], ["literal", ["Arial"]], 0, ["literal", ["Helvetica"]]]] } }] })"); std::set expected; expected.insert(FontStack({"Arial"})); expected.insert(FontStack({"Helvetica"})); std::set result = parser.fontStacks(); ASSERT_EQ(expected, result); } TEST(StyleParser, FontStacksGetExpression) { // Invalid style, but not currently validated. style::Parser parser; parser.parse(R"({ "version": 8, "layers": [{ "id": "symbol", "type": "symbol", "source": "vector", "layout": { "text-field": "a", "text-font": ["array", "string", ["get", "text-font"]] } }] })"); auto result = parser.fontStacks(); ASSERT_EQ(0u, result.size()); } TEST(StyleParser, ZoomCurve) { using namespace mbgl::style; using namespace mbgl::style::expression; using namespace mbgl::style::expression::dsl; const auto zoomInterp = []() { return interpolate(linear(), zoom(), 0.0, literal(0.0), 0.0, literal(0.0)); }; auto expr1 = interpolate(linear(), literal(0.0), 0.0, zoomInterp(), 0.0, zoomInterp()); ASSERT_TRUE(expr1); ASSERT_TRUE(findZoomCurveChecked(*expr1).is()); auto expr2 = interpolate(linear(), zoom(), 0.0, literal(0.0), 0.0, zoomInterp()); ASSERT_TRUE(expr2); ASSERT_TRUE(findZoomCurveChecked(*expr2).is()); }