#include "../fixtures/fixture_log_observer.hpp" #include "../fixtures/mock_file_source.hpp" #include "../fixtures/util.hpp" #include #include #include #include #include #include using namespace mbgl; using GlyphStoreTestCallback = std::function; struct GlyphStoreParams { const std::string url; const std::string stack; const std::set ranges; }; class GlyphStoreThread : public GlyphStore::Observer { public: GlyphStoreThread(FileSource* fileSource, GlyphStoreTestCallback callback) : callback_(std::move(callback)) { util::ThreadContext::setFileSource(fileSource); } void loadGlyphStore(const GlyphStoreParams& params) { glyphStore_.reset(new GlyphStore()); glyphStore_->setObserver(this); glyphStore_->setURL(params.url); ASSERT_FALSE(glyphStore_->hasGlyphRanges(params.stack, params.ranges)); } void unloadGlyphStore() { glyphStore_->setObserver(nullptr); glyphStore_.reset(); } void onGlyphRangeLoaded() override { callback_(glyphStore_.get(), nullptr); } void onGlyphRangeLoadingFailed(std::exception_ptr error) override { callback_(glyphStore_.get(), error); } private: std::unique_ptr glyphStore_; GlyphStoreTestCallback callback_; }; class GlyphStoreTest : public testing::Test { protected: void runTest(const GlyphStoreParams& params, FileSource* fileSource, GlyphStoreTestCallback callback) { util::RunLoop loop; async_ = std::make_unique([&]{ loop.stop(); }); async_->unref(); const util::ThreadContext context = {"Map", util::ThreadType::Map, util::ThreadPriority::Regular}; util::Thread tester(context, fileSource, callback); tester.invoke(&GlyphStoreThread::loadGlyphStore, params); loop.run(); tester.invoke(&GlyphStoreThread::unloadGlyphStore); } void stopTest() { testDone = true; async_->send(); } bool isDone() const { return testDone; } private: bool testDone = false; std::unique_ptr async_; }; TEST_F(GlyphStoreTest, LoadingSuccess) { GlyphStoreParams params = { "test/fixtures/resources/glyphs.pbf", "Test Stack", {{0, 255}, {256, 511}} }; auto callback = [this, ¶ms](GlyphStore* store, std::exception_ptr error) { ASSERT_TRUE(util::ThreadContext::currentlyOn(util::ThreadType::Map)); // We need to check if the test is over because checking // if the GlyphStore has glyphs below will cause more requests // to happen and we don't want this endless loop. if (isDone()) { return; } ASSERT_EQ(error, nullptr); if (!store->hasGlyphRanges(params.stack, params.ranges)) { return; } ASSERT_FALSE(store->hasGlyphRanges("Foobar", params.ranges)); ASSERT_FALSE(store->hasGlyphRanges("Foobar", {{512, 767}})); ASSERT_FALSE(store->hasGlyphRanges("Test Stack", {{512, 767}})); auto fontStack = store->getFontStack(params.stack); ASSERT_FALSE(fontStack->getMetrics().empty()); ASSERT_FALSE(fontStack->getSDFs().empty()); stopTest(); }; MockFileSource fileSource(MockFileSource::Success, ""); runTest(params, &fileSource, callback); } TEST_F(GlyphStoreTest, LoadingFail) { GlyphStoreParams params = { "test/fixtures/resources/glyphs.pbf", "Test Stack", {{0, 255}, {256, 511}} }; auto callback = [this, ¶ms](GlyphStore* store, std::exception_ptr error) { ASSERT_TRUE(util::ThreadContext::currentlyOn(util::ThreadType::Map)); if (isDone()) { return; } ASSERT_TRUE(error != nullptr); auto fontStack = store->getFontStack(params.stack); ASSERT_TRUE(fontStack->getMetrics().empty()); ASSERT_TRUE(fontStack->getSDFs().empty()); for (const auto& range : params.ranges) { ASSERT_FALSE(store->hasGlyphRanges(params.stack, {range})); } ASSERT_FALSE(store->hasGlyphRanges(params.stack, params.ranges)); ASSERT_FALSE(store->hasGlyphRanges("Foobar", params.ranges)); ASSERT_FALSE(store->hasGlyphRanges("Foobar", {{512, 767}})); stopTest(); }; MockFileSource fileSource(MockFileSource::RequestFail, "glyphs.pbf"); runTest(params, &fileSource, callback); } TEST_F(GlyphStoreTest, LoadingCorrupted) { GlyphStoreParams params = { "test/fixtures/resources/glyphs.pbf", "Test Stack", {{0, 255}, {256, 511}} }; auto callback = [this, ¶ms](GlyphStore* store, std::exception_ptr error) { ASSERT_TRUE(util::ThreadContext::currentlyOn(util::ThreadType::Map)); if (isDone()) { return; } ASSERT_TRUE(error != nullptr); auto fontStack = store->getFontStack(params.stack); ASSERT_TRUE(fontStack->getMetrics().empty()); ASSERT_TRUE(fontStack->getSDFs().empty()); for (const auto& range : params.ranges) { ASSERT_FALSE(store->hasGlyphRanges(params.stack, {range})); } ASSERT_FALSE(store->hasGlyphRanges(params.stack, params.ranges)); ASSERT_FALSE(store->hasGlyphRanges("Foobar", params.ranges)); ASSERT_FALSE(store->hasGlyphRanges("Foobar", {{512, 767}})); stopTest(); }; MockFileSource fileSource(MockFileSource::RequestWithCorruptedData, "glyphs.pbf"); runTest(params, &fileSource, callback); } TEST_F(GlyphStoreTest, LoadingCancel) { GlyphStoreParams params = { "test/fixtures/resources/glyphs.pbf", "Test Stack", {{0, 255}, {256, 511}} }; auto callback = [this](GlyphStore*, std::exception_ptr) { FAIL() << "Should never be called"; }; MockFileSource fileSource(MockFileSource::SuccessWithDelay, "glyphs.pbf"); fileSource.setOnRequestDelayedCallback([this]{ stopTest(); }); runTest(params, &fileSource, callback); } TEST_F(GlyphStoreTest, InvalidURL) { GlyphStoreParams params = { "foo bar", "Test Stack", {{0, 255}, {256, 511}} }; auto callback = [this, ¶ms](GlyphStore* store, std::exception_ptr error) { ASSERT_TRUE(error != nullptr); auto fontStack = store->getFontStack(params.stack); ASSERT_TRUE(fontStack->getMetrics().empty()); ASSERT_TRUE(fontStack->getSDFs().empty()); stopTest(); }; MockFileSource fileSource(MockFileSource::Success, ""); runTest(params, &fileSource, callback); }