diff options
Diffstat (limited to 'test')
111 files changed, 3904 insertions, 2805 deletions
diff --git a/test/.clang-tidy b/test/.clang-tidy new file mode 100644 index 0000000000..492d4affbd --- /dev/null +++ b/test/.clang-tidy @@ -0,0 +1,2 @@ +Checks: 'modernize-*,-modernize-use-equals-delete,-modernize-use-equals-default,misc-static-assert,llvm-namespace-comment,-clang-analyzer-security.insecureAPI.rand,-clang-analyzer-core.uninitialized.UndefReturn,-clang-analyzer-core.StackAddressEscape,-clang-analyzer-core.CallAndMessage,-clang-diagnostic-unused-command-line-argument,-clang-analyzer-core.uninitialized.*,-clang-analyzer-core.NullDereference,-clang-analyzer-core.NonNullParamChecker' +HeaderFilterRegex: '\/mbgl\/' diff --git a/test/actor/actor.test.cpp b/test/actor/actor.test.cpp index 03f41a6e64..967dc152d9 100644 --- a/test/actor/actor.test.cpp +++ b/test/actor/actor.test.cpp @@ -6,6 +6,7 @@ #include <chrono> #include <functional> #include <future> +#include <memory> using namespace mbgl; using namespace std::chrono_literals; @@ -26,7 +27,7 @@ TEST(Actor, Construction) { EXPECT_TRUE(constructed); } -TEST(Actor, DestructionClosesMailbox) { +TEST(Actor, DestructionBlocksOnReceive) { // Destruction blocks until the actor is not receiving. struct Test { @@ -67,6 +68,149 @@ TEST(Actor, DestructionClosesMailbox) { exitingPromise.set_value(); } +TEST(Actor, DestructionBlocksOnSend) { + // Destruction blocks until the actor is not being sent a message. + + struct TestScheduler : public Scheduler { + std::promise<void> promise; + std::future<void> future; + std::atomic<bool> waited; + + TestScheduler(std::promise<void> promise_, std::future<void> future_) + : promise(std::move(promise_)), + future(std::move(future_)), + waited(false) { + } + + ~TestScheduler() { + EXPECT_TRUE(waited.load()); + } + + void schedule(std::weak_ptr<Mailbox>) final { + promise.set_value(); + future.wait(); + std::this_thread::sleep_for(1ms); + waited = true; + } + }; + + struct Test { + Test(ActorRef<Test>) {} + void message() {} + }; + + std::promise<void> enteredPromise; + std::future<void> enteredFuture = enteredPromise.get_future(); + + std::promise<void> exitingPromise; + std::future<void> exitingFuture = exitingPromise.get_future(); + + auto scheduler = std::make_unique<TestScheduler>(std::move(enteredPromise), std::move(exitingFuture)); + auto actor = std::make_unique<Actor<Test>>(*scheduler); + + std::thread thread { + [] (ActorRef<Test> ref) { + ref.invoke(&Test::message); + }, + actor->self() + }; + + enteredFuture.wait(); + exitingPromise.set_value(); + + actor.reset(); + scheduler.reset(); + + thread.join(); +} + +TEST(Actor, DestructionAllowedInReceiveOnSameThread) { + // Destruction doesn't block if occurring on the same + // thread as receive(). This prevents deadlocks and + // allows for self-closing actors + + struct Test { + + Test(ActorRef<Test>){}; + + void callMeBack(std::function<void ()> callback) { + callback(); + } + }; + + ThreadPool pool { 1 }; + + std::promise<void> callbackFiredPromise; + + auto test = std::make_unique<Actor<Test>>(pool); + + // Callback (triggered while mutex is locked in Mailbox::receive()) + test->invoke(&Test::callMeBack, [&]() { + // Destroy the Actor/Mailbox in the same thread + test.reset(); + callbackFiredPromise.set_value(); + }); + + auto status = callbackFiredPromise.get_future().wait_for(std::chrono::seconds(1)); + ASSERT_EQ(std::future_status::ready, status); +} + +TEST(Actor, SelfDestructionDoesntCrashWaitingReceivingThreads) { + // Ensures destruction doesn't cause waiting threads to + // crash when a actor closes it's own mailbox from a + // callback + + struct Test { + + Test(ActorRef<Test>){}; + + void callMeBack(std::function<void ()> callback) { + callback(); + } + }; + + + ThreadPool pool { 2 }; + + std::promise<void> actorClosedPromise; + + auto closingActor = std::make_unique<Actor<Test>>(pool); + auto waitingActor = std::make_unique<Actor<Test>>(pool); + + std::atomic<bool> waitingMessageProcessed {false}; + + // Callback (triggered while mutex is locked in Mailbox::receive()) + closingActor->invoke(&Test::callMeBack, [&]() { + + // Queue up another message from another thread + std::promise<void> messageQueuedPromise; + waitingActor->invoke(&Test::callMeBack, [&]() { + // This will be waiting on the mutex in + // Mailbox::receive(), holding a lock + // on the weak_ptr so the mailbox is not + // destroyed + closingActor->invoke(&Test::callMeBack, [&]() { + waitingMessageProcessed.store(true); + }); + messageQueuedPromise.set_value(); + }); + + // Wait for the message to be queued + ASSERT_EQ( + messageQueuedPromise.get_future().wait_for(std::chrono::seconds(1)), + std::future_status::ready + ); + + // Destroy the Actor/Mailbox in the same thread + closingActor.reset(); + actorClosedPromise.set_value(); + }); + + auto status = actorClosedPromise.get_future().wait_for(std::chrono::seconds(1)); + ASSERT_EQ(std::future_status::ready, status); + ASSERT_FALSE(waitingMessageProcessed.load()); +} + TEST(Actor, OrderedMailbox) { // Messages are processed in order. @@ -137,3 +281,80 @@ TEST(Actor, NonConcurrentMailbox) { test.invoke(&Test::end); endedFuture.wait(); } + +TEST(Actor, Ask) { + // Asking for a result + + struct Test { + + Test(ActorRef<Test>) {} + + int doubleIt(int i) { + return i * 2; + } + }; + + ThreadPool pool { 2 }; + Actor<Test> test(pool); + + auto result = test.ask(&Test::doubleIt, 1); + + ASSERT_TRUE(result.valid()); + + auto status = result.wait_for(std::chrono::seconds(1)); + ASSERT_EQ(std::future_status::ready, status); + ASSERT_EQ(2, result.get()); +} + +TEST(Actor, AskVoid) { + // Ask waits for void methods + + struct Test { + bool& executed; + + Test(bool& executed_) : executed(executed_) { + } + + void doIt() { + executed = true; + } + }; + + ThreadPool pool { 1 }; + bool executed = false; + Actor<Test> actor(pool, executed); + + actor.ask(&Test::doIt).get(); + EXPECT_TRUE(executed); +} + +TEST(Actor, NoSelfActorRef) { + // Not all actors need a reference to self + + // Trivially constructable + struct Trivial {}; + + ThreadPool pool { 2 }; + Actor<Trivial> trivial(pool); + + + // With arguments + struct WithArguments { + std::promise<void> promise; + + WithArguments(std::promise<void> promise_) + : promise(std::move(promise_)) { + } + + void receive() { + promise.set_value(); + } + }; + + std::promise<void> promise; + auto future = promise.get_future(); + Actor<WithArguments> withArguments(pool, std::move(promise)); + + withArguments.invoke(&WithArguments::receive); + future.wait(); +} diff --git a/test/actor/actor_ref.test.cpp b/test/actor/actor_ref.test.cpp index 78721c965e..20aa1c35c1 100644 --- a/test/actor/actor_ref.test.cpp +++ b/test/actor/actor_ref.test.cpp @@ -3,12 +3,9 @@ #include <mbgl/test/util.hpp> -#include <chrono> -#include <functional> #include <future> using namespace mbgl; -using namespace std::chrono_literals; TEST(ActorRef, CanOutliveActor) { // An ActorRef can outlive its actor. Doing does not extend the actor's lifetime. @@ -40,3 +37,80 @@ TEST(ActorRef, CanOutliveActor) { EXPECT_TRUE(died); test.invoke(&Test::receive); } + +TEST(ActorRef, Ask) { + // Ask returns a Future eventually returning the result + + struct Test { + + Test(ActorRef<Test>) {} + + int gimme() { + return 20; + } + + int echo(int i) { + return i; + } + }; + + ThreadPool pool { 1 }; + Actor<Test> actor(pool); + ActorRef<Test> ref = actor.self(); + + EXPECT_EQ(20, ref.ask(&Test::gimme).get()); + EXPECT_EQ(30, ref.ask(&Test::echo, 30).get()); +} + +TEST(ActorRef, AskVoid) { + // Ask waits for void methods + + struct Test { + bool& executed; + + Test(bool& executed_) : executed(executed_) { + } + + void doIt() { + executed = true; + } + }; + + ThreadPool pool { 1 }; + bool executed = false; + Actor<Test> actor(pool, executed); + ActorRef<Test> ref = actor.self(); + + ref.ask(&Test::doIt).get(); + EXPECT_TRUE(executed); +} + +TEST(ActorRef, AskOnDestroyedActor) { + // Tests behavior when calling ask() after the + // Actor has gone away. Should set a exception_ptr. + + struct Test { + bool& died; + + Test(ActorRef<Test>, bool& died_) : died(died_) {} + + ~Test() { + died = true; + } + + int receive() { + return 1; + } + }; + bool died = false; + + ThreadPool pool { 1 }; + auto actor = std::make_unique<Actor<Test>>(pool, died); + ActorRef<Test> ref = actor->self(); + + actor.reset(); + EXPECT_TRUE(died); + + auto result = ref.ask(&Test::receive); + EXPECT_ANY_THROW(result.get()); +} diff --git a/test/algorithm/generate_clip_ids.test.cpp b/test/algorithm/generate_clip_ids.test.cpp index 8ca0191b3a..1ebdccb99e 100644 --- a/test/algorithm/generate_clip_ids.test.cpp +++ b/test/algorithm/generate_clip_ids.test.cpp @@ -5,173 +5,150 @@ using namespace mbgl; struct Renderable { + UnwrappedTileID id; ClipID clip; - bool used = true; + bool used; + + Renderable(UnwrappedTileID id_, + ClipID clip_, + bool used_ = true) + : id(std::move(id_)), + clip(std::move(clip_)), + used(used_) {} bool operator==(const Renderable& rhs) const { - return clip == rhs.clip; + return id == rhs.id && clip == rhs.clip; } }; ::std::ostream& operator<<(::std::ostream& os, const Renderable& rhs) { - return os << "ClipID(" << rhs.clip << ")"; + return os << "Renderable{ " << rhs.id << ", " << rhs.clip << " }"; } -namespace { - -// void print(const std::map<UnwrappedTileID, Renderable>& renderables) { -// std::cout << " EXPECT_EQ(decltype(renderables)({" << std::endl; -// for (auto& pair : renderables) { -// std::cout << " { UnwrappedTileID{ " << int(pair.first.canonical.z) << ", " -// << (int64_t(pair.first.canonical.x) + -// pair.first.wrap * (1ll << pair.first.canonical.z)) -// << ", " << pair.first.canonical.y << " }, Renderable{ ClipID{ \"" -// << pair.second.clip.mask << "\", \"" << pair.second.clip.reference << "\" } } }," -// << std::endl; -// } -// std::cout << " })," << std::endl; -// std::cout << " renderables);" << std::endl; -// } - -// void print(const std::map<UnwrappedTileID, ClipID>& stencils) { -// std::cout << " EXPECT_EQ(decltype(stencils)({" << std::endl; -// for (auto& pair : stencils) { -// std::cout << " { UnwrappedTileID{ " << int(pair.first.canonical.z) << ", " -// << (int64_t(pair.first.canonical.x) + -// pair.first.wrap * (1ll << pair.first.canonical.z)) -// << ", " << pair.first.canonical.y << " }, ClipID{ \"" << pair.second.mask -// << "\", \"" << pair.second.reference << "\" } }," << std::endl; -// } -// std::cout << " })," << std::endl; -// std::cout << " stencils);" << std::endl; -// } - -} // end namespace - TEST(GenerateClipIDs, ParentAndFourChildren) { - std::map<UnwrappedTileID, Renderable> renderables{ - { UnwrappedTileID{ 0, 0, 0 }, Renderable{ {} } }, + std::vector<Renderable> renderables{ + Renderable{ UnwrappedTileID{ 0, 0, 0 }, {} }, // All four covering children - { UnwrappedTileID{ 1, 0, 0 }, Renderable{ {} } }, - { UnwrappedTileID{ 1, 0, 1 }, Renderable{ {} } }, - { UnwrappedTileID{ 1, 1, 0 }, Renderable{ {} } }, - { UnwrappedTileID{ 1, 1, 1 }, Renderable{ {} } }, + Renderable{ UnwrappedTileID{ 1, 0, 0 }, {} }, + Renderable{ UnwrappedTileID{ 1, 0, 1 }, {} }, + Renderable{ UnwrappedTileID{ 1, 1, 0 }, {} }, + Renderable{ UnwrappedTileID{ 1, 1, 1 }, {} }, }; algorithm::ClipIDGenerator generator; - generator.update(renderables); + generator.update<Renderable>({ renderables.begin(), renderables.end() }); EXPECT_EQ(decltype(renderables)({ - { UnwrappedTileID{ 0, 0, 0 }, Renderable{ ClipID{ "00000111", "00000001" } } }, - { UnwrappedTileID{ 1, 0, 0 }, Renderable{ ClipID{ "00000111", "00000010" } } }, - { UnwrappedTileID{ 1, 0, 1 }, Renderable{ ClipID{ "00000111", "00000011" } } }, - { UnwrappedTileID{ 1, 1, 0 }, Renderable{ ClipID{ "00000111", "00000100" } } }, - { UnwrappedTileID{ 1, 1, 1 }, Renderable{ ClipID{ "00000111", "00000101" } } }, + Renderable{ UnwrappedTileID{ 0, 0, 0 }, ClipID{ "00000111", "00000001" } }, + Renderable{ UnwrappedTileID{ 1, 0, 0 }, ClipID{ "00000111", "00000010" } }, + Renderable{ UnwrappedTileID{ 1, 0, 1 }, ClipID{ "00000111", "00000011" } }, + Renderable{ UnwrappedTileID{ 1, 1, 0 }, ClipID{ "00000111", "00000100" } }, + Renderable{ UnwrappedTileID{ 1, 1, 1 }, ClipID{ "00000111", "00000101" } }, }), renderables); - const auto stencils = generator.getStencils(); - EXPECT_EQ(decltype(stencils)({ + const auto clipIDs = generator.getClipIDs(); + EXPECT_EQ(decltype(clipIDs)({ // 0/0/0 is missing because it is covered by children. { UnwrappedTileID{ 1, 0, 0 }, ClipID{ "00000111", "00000010" } }, { UnwrappedTileID{ 1, 0, 1 }, ClipID{ "00000111", "00000011" } }, { UnwrappedTileID{ 1, 1, 0 }, ClipID{ "00000111", "00000100" } }, { UnwrappedTileID{ 1, 1, 1 }, ClipID{ "00000111", "00000101" } }, }), - stencils); + clipIDs); } TEST(GenerateClipIDs, ParentAndFourChildrenNegative) { - std::map<UnwrappedTileID, Renderable> renderables{ - { UnwrappedTileID{ 1, -2, 0 }, Renderable{ {} } }, - { UnwrappedTileID{ 1, -2, 1 }, Renderable{ {} } }, - { UnwrappedTileID{ 1, -1, 0 }, Renderable{ {} } }, - { UnwrappedTileID{ 1, -1, 1 }, Renderable{ {} } }, - { UnwrappedTileID{ 0, -1, 0 }, Renderable{ {} } }, + std::vector<Renderable> renderables{ + Renderable{ UnwrappedTileID{ 1, -2, 0 }, {} }, + Renderable{ UnwrappedTileID{ 1, -2, 1 }, {} }, + Renderable{ UnwrappedTileID{ 1, -1, 0 }, {} }, + Renderable{ UnwrappedTileID{ 1, -1, 1 }, {} }, + Renderable{ UnwrappedTileID{ 0, -1, 0 }, {} }, }; algorithm::ClipIDGenerator generator; - generator.update(renderables); + generator.update<Renderable>({ renderables.begin(), renderables.end() }); EXPECT_EQ(decltype(renderables)({ - { UnwrappedTileID{ 0, -1, 0 }, Renderable{ ClipID{ "00000111", "00000001" } } }, - { UnwrappedTileID{ 1, -2, 0 }, Renderable{ ClipID{ "00000111", "00000010" } } }, - { UnwrappedTileID{ 1, -2, 1 }, Renderable{ ClipID{ "00000111", "00000011" } } }, - { UnwrappedTileID{ 1, -1, 0 }, Renderable{ ClipID{ "00000111", "00000100" } } }, - { UnwrappedTileID{ 1, -1, 1 }, Renderable{ ClipID{ "00000111", "00000101" } } }, + Renderable{ UnwrappedTileID{ 1, -2, 0 }, ClipID{ "00000111", "00000010" } }, + Renderable{ UnwrappedTileID{ 1, -2, 1 }, ClipID{ "00000111", "00000011" } }, + Renderable{ UnwrappedTileID{ 1, -1, 0 }, ClipID{ "00000111", "00000100" } }, + Renderable{ UnwrappedTileID{ 1, -1, 1 }, ClipID{ "00000111", "00000101" } }, + Renderable{ UnwrappedTileID{ 0, -1, 0 }, ClipID{ "00000111", "00000001" } }, }), renderables); - const auto stencils = generator.getStencils(); - EXPECT_EQ(decltype(stencils)({ + const auto clipIDs = generator.getClipIDs(); + EXPECT_EQ(decltype(clipIDs)({ { UnwrappedTileID{ 1, -2, 0 }, ClipID{ "00000111", "00000010" } }, { UnwrappedTileID{ 1, -2, 1 }, ClipID{ "00000111", "00000011" } }, { UnwrappedTileID{ 1, -1, 0 }, ClipID{ "00000111", "00000100" } }, { UnwrappedTileID{ 1, -1, 1 }, ClipID{ "00000111", "00000101" } }, }), - stencils); + clipIDs); } TEST(GenerateClipIDs, NegativeParentAndMissingLevel) { - std::map<UnwrappedTileID, Renderable> renderables{ - { UnwrappedTileID{ 1, -1, 0 }, Renderable{ {} } }, - { UnwrappedTileID{ 2, -1, 0 }, Renderable{ {} } }, - { UnwrappedTileID{ 2, -2, 1 }, Renderable{ {} } }, - { UnwrappedTileID{ 2, -1, 1 }, Renderable{ {} } }, - { UnwrappedTileID{ 2, -2, 0 }, Renderable{ {} } }, + std::vector<Renderable> renderables{ + Renderable{ UnwrappedTileID{ 1, -1, 0 }, {} }, + Renderable{ UnwrappedTileID{ 2, -1, 0 }, {} }, + Renderable{ UnwrappedTileID{ 2, -2, 1 }, {} }, + Renderable{ UnwrappedTileID{ 2, -1, 1 }, {} }, + Renderable{ UnwrappedTileID{ 2, -2, 0 }, {} }, }; algorithm::ClipIDGenerator generator; - generator.update(renderables); + generator.update<Renderable>({ renderables.begin(), renderables.end() }); EXPECT_EQ(decltype(renderables)({ - { UnwrappedTileID{ 1, -1, 0 }, Renderable{ ClipID{ "00000111", "00000001" } } }, - { UnwrappedTileID{ 2, -2, 0 }, Renderable{ ClipID{ "00000111", "00000010" } } }, - { UnwrappedTileID{ 2, -2, 1 }, Renderable{ ClipID{ "00000111", "00000011" } } }, - { UnwrappedTileID{ 2, -1, 0 }, Renderable{ ClipID{ "00000111", "00000100" } } }, - { UnwrappedTileID{ 2, -1, 1 }, Renderable{ ClipID{ "00000111", "00000101" } } }, + Renderable{ UnwrappedTileID{ 1, -1, 0 }, ClipID{ "00000111", "00000001" } }, + Renderable{ UnwrappedTileID{ 2, -1, 0 }, ClipID{ "00000111", "00000100" } }, + Renderable{ UnwrappedTileID{ 2, -2, 1 }, ClipID{ "00000111", "00000011" } }, + Renderable{ UnwrappedTileID{ 2, -1, 1 }, ClipID{ "00000111", "00000101" } }, + Renderable{ UnwrappedTileID{ 2, -2, 0 }, ClipID{ "00000111", "00000010" } }, }), renderables); - const auto stencils = generator.getStencils(); - EXPECT_EQ(decltype(stencils)({ + const auto clipIDs = generator.getClipIDs(); + EXPECT_EQ(decltype(clipIDs)({ { UnwrappedTileID{ 2, -2, 0 }, ClipID{ "00000111", "00000010" } }, { UnwrappedTileID{ 2, -2, 1 }, ClipID{ "00000111", "00000011" } }, { UnwrappedTileID{ 2, -1, 0 }, ClipID{ "00000111", "00000100" } }, { UnwrappedTileID{ 2, -1, 1 }, ClipID{ "00000111", "00000101" } }, }), - stencils); + clipIDs); } TEST(GenerateClipIDs, SevenOnSameLevel) { - std::map<UnwrappedTileID, Renderable> renderables{ + std::vector<Renderable> renderables{ // first column - { UnwrappedTileID{ 2, 0, 0 }, Renderable{ {} } }, - { UnwrappedTileID{ 2, 0, 1 }, Renderable{ {} } }, - { UnwrappedTileID{ 2, 0, 2 }, Renderable{ {} } }, + Renderable{ UnwrappedTileID{ 2, 0, 0 }, {} }, + Renderable{ UnwrappedTileID{ 2, 0, 1 }, {} }, + Renderable{ UnwrappedTileID{ 2, 0, 2 }, {} }, // second column - { UnwrappedTileID{ 2, 1, 0 }, Renderable{ {} } }, - { UnwrappedTileID{ 2, 1, 1 }, Renderable{ {} } }, - { UnwrappedTileID{ 2, 1, 2 }, Renderable{ {} } }, + Renderable{ UnwrappedTileID{ 2, 1, 0 }, {} }, + Renderable{ UnwrappedTileID{ 2, 1, 1 }, {} }, + Renderable{ UnwrappedTileID{ 2, 1, 2 }, {} }, // third column - { UnwrappedTileID{ 2, 2, 0 }, Renderable{ {} } }, + Renderable{ UnwrappedTileID{ 2, 2, 0 }, {} }, }; algorithm::ClipIDGenerator generator; - generator.update(renderables); + generator.update<Renderable>({ renderables.begin(), renderables.end() }); EXPECT_EQ(decltype(renderables)({ - { UnwrappedTileID{ 2, 0, 0 }, Renderable{ ClipID{ "00000111", "00000001" } } }, - { UnwrappedTileID{ 2, 0, 1 }, Renderable{ ClipID{ "00000111", "00000010" } } }, - { UnwrappedTileID{ 2, 0, 2 }, Renderable{ ClipID{ "00000111", "00000011" } } }, - { UnwrappedTileID{ 2, 1, 0 }, Renderable{ ClipID{ "00000111", "00000100" } } }, - { UnwrappedTileID{ 2, 1, 1 }, Renderable{ ClipID{ "00000111", "00000101" } } }, - { UnwrappedTileID{ 2, 1, 2 }, Renderable{ ClipID{ "00000111", "00000110" } } }, - { UnwrappedTileID{ 2, 2, 0 }, Renderable{ ClipID{ "00000111", "00000111" } } }, + Renderable{ UnwrappedTileID{ 2, 0, 0 }, ClipID{ "00000111", "00000001" } }, + Renderable{ UnwrappedTileID{ 2, 0, 1 }, ClipID{ "00000111", "00000010" } }, + Renderable{ UnwrappedTileID{ 2, 0, 2 }, ClipID{ "00000111", "00000011" } }, + Renderable{ UnwrappedTileID{ 2, 1, 0 }, ClipID{ "00000111", "00000100" } }, + Renderable{ UnwrappedTileID{ 2, 1, 1 }, ClipID{ "00000111", "00000101" } }, + Renderable{ UnwrappedTileID{ 2, 1, 2 }, ClipID{ "00000111", "00000110" } }, + Renderable{ UnwrappedTileID{ 2, 2, 0 }, ClipID{ "00000111", "00000111" } }, }), renderables); - const auto stencils = generator.getStencils(); - EXPECT_EQ(decltype(stencils)({ + const auto clipIDs = generator.getClipIDs(); + EXPECT_EQ(decltype(clipIDs)({ { UnwrappedTileID{ 2, 0, 0 }, ClipID{ "00000111", "00000001" } }, { UnwrappedTileID{ 2, 0, 1 }, ClipID{ "00000111", "00000010" } }, { UnwrappedTileID{ 2, 0, 2 }, ClipID{ "00000111", "00000011" } }, @@ -180,51 +157,51 @@ TEST(GenerateClipIDs, SevenOnSameLevel) { { UnwrappedTileID{ 2, 1, 2 }, ClipID{ "00000111", "00000110" } }, { UnwrappedTileID{ 2, 2, 0 }, ClipID{ "00000111", "00000111" } }, }), - stencils); + clipIDs); } TEST(GenerateClipIDs, MultipleLevels) { - std::map<UnwrappedTileID, Renderable> renderables{ - { UnwrappedTileID{ 2, 0, 0 }, Renderable{ {} } }, + std::vector<Renderable> renderables{ + Renderable{ UnwrappedTileID{ 2, 0, 0 }, {} }, // begin subtiles of (2/0/0) - { UnwrappedTileID{ 3, 0, 0 }, Renderable{ {} } }, - { UnwrappedTileID{ 3, 0, 1 }, Renderable{ {} } }, + Renderable{ UnwrappedTileID{ 3, 0, 0 }, {} }, + Renderable{ UnwrappedTileID{ 3, 0, 1 }, {} }, // begin subtiles of (3/0/1) - { UnwrappedTileID{ 4, 0, 2 }, Renderable{ {} } }, - { UnwrappedTileID{ 4, 1, 2 }, Renderable{ {} } }, - { UnwrappedTileID{ 4, 0, 3 }, Renderable{ {} } }, - { UnwrappedTileID{ 4, 1, 3 }, Renderable{ {} } }, + Renderable{ UnwrappedTileID{ 4, 0, 2 }, {} }, + Renderable{ UnwrappedTileID{ 4, 1, 2 }, {} }, + Renderable{ UnwrappedTileID{ 4, 0, 3 }, {} }, + Renderable{ UnwrappedTileID{ 4, 1, 3 }, {} }, // end subtiles of (3/0/1) - { UnwrappedTileID{ 3, 1, 0 }, Renderable{ {} } }, - { UnwrappedTileID{ 3, 1, 1 }, Renderable{ {} } }, + Renderable{ UnwrappedTileID{ 3, 1, 0 }, {} }, + Renderable{ UnwrappedTileID{ 3, 1, 1 }, {} }, // end subtiles of (2/0/0) - { UnwrappedTileID{ 2, 1, 0 }, Renderable{ {} } }, + Renderable{ UnwrappedTileID{ 2, 1, 0 }, {} }, // begin subtiles of (2/1/0) - { UnwrappedTileID{ 3, 2, 0 }, Renderable{ {} } }, - { UnwrappedTileID{ 3, 2, 1 }, Renderable{ {} } }, + Renderable{ UnwrappedTileID{ 3, 2, 0 }, {} }, + Renderable{ UnwrappedTileID{ 3, 2, 1 }, {} }, // end subtiles of (2/1/0) }; algorithm::ClipIDGenerator generator; - generator.update(renderables); + generator.update<Renderable>({ renderables.begin(), renderables.end() }); ASSERT_EQ(decltype(renderables)({ - { UnwrappedTileID{ 2, 0, 0 }, Renderable{ ClipID{ "00001111", "00000001" } } }, - { UnwrappedTileID{ 2, 1, 0 }, Renderable{ ClipID{ "00001111", "00000010" } } }, - { UnwrappedTileID{ 3, 0, 0 }, Renderable{ ClipID{ "00001111", "00000011" } } }, - { UnwrappedTileID{ 3, 0, 1 }, Renderable{ ClipID{ "00001111", "00000100" } } }, - { UnwrappedTileID{ 3, 1, 0 }, Renderable{ ClipID{ "00001111", "00000101" } } }, - { UnwrappedTileID{ 3, 1, 1 }, Renderable{ ClipID{ "00001111", "00000110" } } }, - { UnwrappedTileID{ 3, 2, 0 }, Renderable{ ClipID{ "00001111", "00000111" } } }, - { UnwrappedTileID{ 3, 2, 1 }, Renderable{ ClipID{ "00001111", "00001000" } } }, - { UnwrappedTileID{ 4, 0, 2 }, Renderable{ ClipID{ "00001111", "00001001" } } }, - { UnwrappedTileID{ 4, 0, 3 }, Renderable{ ClipID{ "00001111", "00001010" } } }, - { UnwrappedTileID{ 4, 1, 2 }, Renderable{ ClipID{ "00001111", "00001011" } } }, - { UnwrappedTileID{ 4, 1, 3 }, Renderable{ ClipID{ "00001111", "00001100" } } }, + Renderable{ UnwrappedTileID{ 2, 0, 0 }, ClipID{ "00001111", "00000001" } }, + Renderable{ UnwrappedTileID{ 3, 0, 0 }, ClipID{ "00001111", "00000011" } }, + Renderable{ UnwrappedTileID{ 3, 0, 1 }, ClipID{ "00001111", "00000100" } }, + Renderable{ UnwrappedTileID{ 4, 0, 2 }, ClipID{ "00001111", "00001001" } }, + Renderable{ UnwrappedTileID{ 4, 1, 2 }, ClipID{ "00001111", "00001011" } }, + Renderable{ UnwrappedTileID{ 4, 0, 3 }, ClipID{ "00001111", "00001010" } }, + Renderable{ UnwrappedTileID{ 4, 1, 3 }, ClipID{ "00001111", "00001100" } }, + Renderable{ UnwrappedTileID{ 3, 1, 0 }, ClipID{ "00001111", "00000101" } }, + Renderable{ UnwrappedTileID{ 3, 1, 1 }, ClipID{ "00001111", "00000110" } }, + Renderable{ UnwrappedTileID{ 2, 1, 0 }, ClipID{ "00001111", "00000010" } }, + Renderable{ UnwrappedTileID{ 3, 2, 0 }, ClipID{ "00001111", "00000111" } }, + Renderable{ UnwrappedTileID{ 3, 2, 1 }, ClipID{ "00001111", "00001000" } }, }), renderables); - const auto stencils = generator.getStencils(); - EXPECT_EQ(decltype(stencils)({ + const auto clipIDs = generator.getClipIDs(); + EXPECT_EQ(decltype(clipIDs)({ { UnwrappedTileID{ 2, 1, 0 }, ClipID{ "00001111", "00000010" } }, { UnwrappedTileID{ 3, 0, 0 }, ClipID{ "00001111", "00000011" } }, { UnwrappedTileID{ 3, 1, 0 }, ClipID{ "00001111", "00000101" } }, @@ -236,46 +213,46 @@ TEST(GenerateClipIDs, MultipleLevels) { { UnwrappedTileID{ 4, 1, 2 }, ClipID{ "00001111", "00001011" } }, { UnwrappedTileID{ 4, 1, 3 }, ClipID{ "00001111", "00001100" } }, }), - stencils); + clipIDs); } TEST(GenerateClipIDs, Bug206) { - std::map<UnwrappedTileID, Renderable> renderables{ - { UnwrappedTileID{ 10, 162, 395 }, Renderable{ {} } }, - { UnwrappedTileID{ 10, 162, 396 }, Renderable{ {} } }, - { UnwrappedTileID{ 10, 163, 395 }, Renderable{ {} } }, + std::vector<Renderable> renderables{ + Renderable{ UnwrappedTileID{ 10, 162, 395 }, {} }, + Renderable{ UnwrappedTileID{ 10, 162, 396 }, {} }, + Renderable{ UnwrappedTileID{ 10, 163, 395 }, {} }, // begin subtiles of (10/163/395) - { UnwrappedTileID{ 11, 326, 791 }, Renderable{ {} } }, - { UnwrappedTileID{ 12, 654, 1582 }, Renderable{ {} } }, - { UnwrappedTileID{ 12, 654, 1583 }, Renderable{ {} } }, - { UnwrappedTileID{ 12, 655, 1582 }, Renderable{ {} } }, - { UnwrappedTileID{ 12, 655, 1583 }, Renderable{ {} } }, + Renderable{ UnwrappedTileID{ 11, 326, 791 }, {} }, + Renderable{ UnwrappedTileID{ 12, 654, 1582 }, {} }, + Renderable{ UnwrappedTileID{ 12, 654, 1583 }, {} }, + Renderable{ UnwrappedTileID{ 12, 655, 1582 }, {} }, + Renderable{ UnwrappedTileID{ 12, 655, 1583 }, {} }, // end subtiles of (10/163/395) - { UnwrappedTileID{ 10, 163, 396 }, Renderable{ {} } }, - { UnwrappedTileID{ 10, 164, 395 }, Renderable{ {} } }, - { UnwrappedTileID{ 10, 164, 396 }, Renderable{ {} } }, + Renderable{ UnwrappedTileID{ 10, 163, 396 }, {} }, + Renderable{ UnwrappedTileID{ 10, 164, 395 }, {} }, + Renderable{ UnwrappedTileID{ 10, 164, 396 }, {} }, }; algorithm::ClipIDGenerator generator; - generator.update(renderables); + generator.update<Renderable>({ renderables.begin(), renderables.end() }); EXPECT_EQ( decltype(renderables)({ - { UnwrappedTileID{ 10, 162, 395 }, Renderable{ ClipID{ "00001111", "00000001" } } }, - { UnwrappedTileID{ 10, 162, 396 }, Renderable{ ClipID{ "00001111", "00000010" } } }, - { UnwrappedTileID{ 10, 163, 395 }, Renderable{ ClipID{ "00001111", "00000011" } } }, - { UnwrappedTileID{ 10, 163, 396 }, Renderable{ ClipID{ "00001111", "00000100" } } }, - { UnwrappedTileID{ 10, 164, 395 }, Renderable{ ClipID{ "00001111", "00000101" } } }, - { UnwrappedTileID{ 10, 164, 396 }, Renderable{ ClipID{ "00001111", "00000110" } } }, - { UnwrappedTileID{ 11, 326, 791 }, Renderable{ ClipID{ "00001111", "00000111" } } }, - { UnwrappedTileID{ 12, 654, 1582 }, Renderable{ ClipID{ "00001111", "00001000" } } }, - { UnwrappedTileID{ 12, 654, 1583 }, Renderable{ ClipID{ "00001111", "00001001" } } }, - { UnwrappedTileID{ 12, 655, 1582 }, Renderable{ ClipID{ "00001111", "00001010" } } }, - { UnwrappedTileID{ 12, 655, 1583 }, Renderable{ ClipID{ "00001111", "00001011" } } }, + Renderable{ UnwrappedTileID{ 10, 162, 395 }, ClipID{ "00001111", "00000001" } }, + Renderable{ UnwrappedTileID{ 10, 162, 396 }, ClipID{ "00001111", "00000010" } }, + Renderable{ UnwrappedTileID{ 10, 163, 395 }, ClipID{ "00001111", "00000011" } }, + Renderable{ UnwrappedTileID{ 11, 326, 791 }, ClipID{ "00001111", "00000111" } }, + Renderable{ UnwrappedTileID{ 12, 654, 1582 }, ClipID{ "00001111", "00001000" } }, + Renderable{ UnwrappedTileID{ 12, 654, 1583 }, ClipID{ "00001111", "00001001" } }, + Renderable{ UnwrappedTileID{ 12, 655, 1582 }, ClipID{ "00001111", "00001010" } }, + Renderable{ UnwrappedTileID{ 12, 655, 1583 }, ClipID{ "00001111", "00001011" } }, + Renderable{ UnwrappedTileID{ 10, 163, 396 }, ClipID{ "00001111", "00000100" } }, + Renderable{ UnwrappedTileID{ 10, 164, 395 }, ClipID{ "00001111", "00000101" } }, + Renderable{ UnwrappedTileID{ 10, 164, 396 }, ClipID{ "00001111", "00000110" } }, }), renderables); - const auto stencils = generator.getStencils(); - EXPECT_EQ(decltype(stencils)({ + const auto clipIDs = generator.getClipIDs(); + EXPECT_EQ(decltype(clipIDs)({ { UnwrappedTileID{ 10, 162, 395 }, ClipID{ "00001111", "00000001" } }, { UnwrappedTileID{ 10, 162, 396 }, ClipID{ "00001111", "00000010" } }, { UnwrappedTileID{ 10, 163, 395 }, ClipID{ "00001111", "00000011" } }, @@ -288,62 +265,62 @@ TEST(GenerateClipIDs, Bug206) { { UnwrappedTileID{ 12, 655, 1582 }, ClipID{ "00001111", "00001010" } }, { UnwrappedTileID{ 12, 655, 1583 }, ClipID{ "00001111", "00001011" } }, }), - stencils); + clipIDs); } TEST(GenerateClipIDs, MultipleSources) { - std::map<UnwrappedTileID, Renderable> renderables1{ - { UnwrappedTileID{ 0, 0, 0 }, Renderable{ {} } }, - { UnwrappedTileID{ 1, 1, 1 }, Renderable{ {} } }, + std::vector<Renderable> renderables1{ + Renderable{ UnwrappedTileID{ 0, 0, 0 }, {} }, + Renderable{ UnwrappedTileID{ 1, 1, 1 }, {} }, // Differing children - { UnwrappedTileID{ 2, 2, 1 }, Renderable{ {} } }, - { UnwrappedTileID{ 2, 2, 2 }, Renderable{ {} } }, + Renderable{ UnwrappedTileID{ 2, 2, 1 }, {} }, + Renderable{ UnwrappedTileID{ 2, 2, 2 }, {} }, }; - std::map<UnwrappedTileID, Renderable> renderables2{ - { UnwrappedTileID{ 0, 0, 0 }, Renderable{ {} } }, - { UnwrappedTileID{ 1, 1, 1 }, Renderable{ {} } }, + std::vector<Renderable> renderables2{ + Renderable{ UnwrappedTileID{ 0, 0, 0 }, {} }, + Renderable{ UnwrappedTileID{ 1, 1, 1 }, {} }, // Differing children - { UnwrappedTileID{ 2, 1, 1 }, Renderable{ {} } }, - { UnwrappedTileID{ 2, 2, 2 }, Renderable{ {} } }, + Renderable{ UnwrappedTileID{ 2, 1, 1 }, {} }, + Renderable{ UnwrappedTileID{ 2, 2, 2 }, {} }, }; - std::map<UnwrappedTileID, Renderable> renderables3{ - { UnwrappedTileID{ 1, 0, 0 }, Renderable{ {} } }, - { UnwrappedTileID{ 1, 0, 1 }, Renderable{ {} } }, - { UnwrappedTileID{ 1, 1, 0 }, Renderable{ {} } }, - { UnwrappedTileID{ 1, 1, 1 }, Renderable{ {} } }, + std::vector<Renderable> renderables3{ + Renderable{ UnwrappedTileID{ 1, 0, 0 }, {} }, + Renderable{ UnwrappedTileID{ 1, 0, 1 }, {} }, + Renderable{ UnwrappedTileID{ 1, 1, 0 }, {} }, + Renderable{ UnwrappedTileID{ 1, 1, 1 }, {} }, // Differing children - { UnwrappedTileID{ 2, 1, 1 }, Renderable{ {} } }, + Renderable{ UnwrappedTileID{ 2, 1, 1 }, {} }, }; algorithm::ClipIDGenerator generator; - generator.update(renderables1); - generator.update(renderables2); - generator.update(renderables3); + generator.update<Renderable>({ renderables1.begin(), renderables1.end() }); + generator.update<Renderable>({ renderables2.begin(), renderables2.end() }); + generator.update<Renderable>({ renderables3.begin(), renderables3.end() }); EXPECT_EQ(decltype(renderables1)({ - { UnwrappedTileID{ 0, 0, 0 }, Renderable{ ClipID{ "00000111", "00000001" } } }, - { UnwrappedTileID{ 1, 1, 1 }, Renderable{ ClipID{ "00000111", "00000010" } } }, - { UnwrappedTileID{ 2, 2, 1 }, Renderable{ ClipID{ "00000111", "00000011" } } }, - { UnwrappedTileID{ 2, 2, 2 }, Renderable{ ClipID{ "00000111", "00000100" } } }, + Renderable{ UnwrappedTileID{ 0, 0, 0 }, ClipID{ "00000111", "00000001" } }, + Renderable{ UnwrappedTileID{ 1, 1, 1 }, ClipID{ "00000111", "00000010" } }, + Renderable{ UnwrappedTileID{ 2, 2, 1 }, ClipID{ "00000111", "00000011" } }, + Renderable{ UnwrappedTileID{ 2, 2, 2 }, ClipID{ "00000111", "00000100" } }, }), renderables1); EXPECT_EQ(decltype(renderables2)({ - { UnwrappedTileID{ 0, 0, 0 }, Renderable{ ClipID{ "00011000", "00001000" } } }, - { UnwrappedTileID{ 1, 1, 1 }, Renderable{ ClipID{ "00011111", "00000010" } } }, - { UnwrappedTileID{ 2, 1, 1 }, Renderable{ ClipID{ "00011000", "00010000" } } }, - { UnwrappedTileID{ 2, 2, 2 }, Renderable{ ClipID{ "00011111", "00000100" } } }, + Renderable{ UnwrappedTileID{ 0, 0, 0 }, ClipID{ "00011000", "00001000" } }, + Renderable{ UnwrappedTileID{ 1, 1, 1 }, ClipID{ "00011111", "00000010" } }, + Renderable{ UnwrappedTileID{ 2, 1, 1 }, ClipID{ "00011000", "00010000" } }, + Renderable{ UnwrappedTileID{ 2, 2, 2 }, ClipID{ "00011111", "00000100" } }, }), renderables2); EXPECT_EQ(decltype(renderables3)({ - { UnwrappedTileID{ 1, 0, 0 }, Renderable{ ClipID{ "11100000", "00100000" } } }, - { UnwrappedTileID{ 1, 0, 1 }, Renderable{ ClipID{ "11100000", "01000000" } } }, - { UnwrappedTileID{ 1, 1, 0 }, Renderable{ ClipID{ "11100000", "01100000" } } }, - { UnwrappedTileID{ 1, 1, 1 }, Renderable{ ClipID{ "11100000", "10000000" } } }, - { UnwrappedTileID{ 2, 1, 1 }, Renderable{ ClipID{ "11111000", "00010000" } } }, + Renderable{ UnwrappedTileID{ 1, 0, 0 }, ClipID{ "11100000", "00100000" } }, + Renderable{ UnwrappedTileID{ 1, 0, 1 }, ClipID{ "11100000", "01000000" } }, + Renderable{ UnwrappedTileID{ 1, 1, 0 }, ClipID{ "11100000", "01100000" } }, + Renderable{ UnwrappedTileID{ 1, 1, 1 }, ClipID{ "11100000", "10000000" } }, + Renderable{ UnwrappedTileID{ 2, 1, 1 }, ClipID{ "11111000", "00010000" } }, }), renderables3); - const auto stencils = generator.getStencils(); - EXPECT_EQ(decltype(stencils)({ + const auto clipIDs = generator.getClipIDs(); + EXPECT_EQ(decltype(clipIDs)({ { UnwrappedTileID{ 1, 0, 0 }, ClipID{ "11111111", "00101001" } }, { UnwrappedTileID{ 1, 0, 1 }, ClipID{ "11111111", "01001001" } }, { UnwrappedTileID{ 1, 1, 0 }, ClipID{ "11111111", "01101001" } }, @@ -352,77 +329,78 @@ TEST(GenerateClipIDs, MultipleSources) { { UnwrappedTileID{ 2, 2, 1 }, ClipID{ "11111111", "01101011" } }, { UnwrappedTileID{ 2, 2, 2 }, ClipID{ "11111111", "10000100" } }, }), - stencils); + clipIDs); } TEST(GenerateClipIDs, DuplicateIDs) { - std::map<UnwrappedTileID, Renderable> renderables1{ - { UnwrappedTileID{ 2, 0, 0 }, Renderable{ {} } }, - { UnwrappedTileID{ 2, 0, 1 }, Renderable{ {} } }, + std::vector<Renderable> renderables1{ + Renderable{ UnwrappedTileID{ 2, 0, 0 }, {} }, + Renderable{ UnwrappedTileID{ 2, 0, 1 }, {} }, }; - std::map<UnwrappedTileID, Renderable> renderables2{ - { UnwrappedTileID{ 2, 0, 0 }, Renderable{ {} } }, - { UnwrappedTileID{ 2, 0, 1 }, Renderable{ {} } }, - { UnwrappedTileID{ 2, 0, 1 }, Renderable{ {} } }, + std::vector<Renderable> renderables2{ + Renderable{ UnwrappedTileID{ 2, 0, 0 }, {} }, + Renderable{ UnwrappedTileID{ 2, 0, 1 }, {} }, + Renderable{ UnwrappedTileID{ 2, 0, 1 }, {} }, }; algorithm::ClipIDGenerator generator; - generator.update(renderables1); - generator.update(renderables2); + generator.update<Renderable>({ renderables1.begin(), renderables1.end() }); + generator.update<Renderable>({ renderables2.begin(), renderables2.end() }); EXPECT_EQ(decltype(renderables1)({ - { UnwrappedTileID{ 2, 0, 0 }, Renderable{ ClipID{ "00000011", "00000001" } } }, - { UnwrappedTileID{ 2, 0, 1 }, Renderable{ ClipID{ "00000011", "00000010" } } }, + Renderable{ UnwrappedTileID{ 2, 0, 0 }, ClipID{ "00000011", "00000001" } }, + Renderable{ UnwrappedTileID{ 2, 0, 1 }, ClipID{ "00000011", "00000010" } }, }), renderables1); EXPECT_EQ(decltype(renderables2)({ - { UnwrappedTileID{ 2, 0, 0 }, Renderable{ ClipID{ "00000011", "00000001" } } }, - { UnwrappedTileID{ 2, 0, 1 }, Renderable{ ClipID{ "00000011", "00000010" } } }, + Renderable{ UnwrappedTileID{ 2, 0, 0 }, ClipID{ "00000011", "00000001" } }, + Renderable{ UnwrappedTileID{ 2, 0, 1 }, ClipID{ "00000011", "00000010" } }, + Renderable{ UnwrappedTileID{ 2, 0, 1 }, ClipID{ "00000011", "00000010" } }, }), renderables2); - const auto stencils = generator.getStencils(); - EXPECT_EQ(decltype(stencils)({ + const auto clipIDs = generator.getClipIDs(); + EXPECT_EQ(decltype(clipIDs)({ { UnwrappedTileID{ 2, 0, 0 }, ClipID{ "00000011", "00000001" } }, { UnwrappedTileID{ 2, 0, 1 }, ClipID{ "00000011", "00000010" } }, }), - stencils); + clipIDs); } TEST(GenerateClipIDs, SecondSourceHasParentOfFirstSource) { - std::map<UnwrappedTileID, Renderable> renderables1{ - { UnwrappedTileID{ 1, 0, 0 }, Renderable{ {} } }, + std::vector<Renderable> renderables1{ + Renderable{ UnwrappedTileID{ 1, 0, 0 }, {} }, }; - std::map<UnwrappedTileID, Renderable> renderables2{ - { UnwrappedTileID{ 0, 0, 0 }, Renderable{ {} } }, + std::vector<Renderable> renderables2{ + Renderable{ UnwrappedTileID{ 0, 0, 0 }, {} }, // Same as in renderables1, but has a parent that it knocks out. - { UnwrappedTileID{ 1, 0, 0 }, Renderable{ {} } }, + Renderable{ UnwrappedTileID{ 1, 0, 0 }, {} }, }; - std::map<UnwrappedTileID, Renderable> renderables3{ - { UnwrappedTileID{ 0, 0, 0 }, Renderable{ {} } }, + std::vector<Renderable> renderables3{ + Renderable{ UnwrappedTileID{ 0, 0, 0 }, {} }, }; algorithm::ClipIDGenerator generator; - generator.update(renderables1); - generator.update(renderables2); - generator.update(renderables3); + generator.update<Renderable>({ renderables1.begin(), renderables1.end() }); + generator.update<Renderable>({ renderables2.begin(), renderables2.end() }); + generator.update<Renderable>({ renderables3.begin(), renderables3.end() }); EXPECT_EQ(decltype(renderables1)({ - { UnwrappedTileID{ 1, 0, 0 }, Renderable{ ClipID{ "00000001", "00000001" } } }, + Renderable{ UnwrappedTileID{ 1, 0, 0 }, ClipID{ "00000001", "00000001" } }, }), renderables1); EXPECT_EQ(decltype(renderables2)({ - { UnwrappedTileID{ 0, 0, 0 }, Renderable{ ClipID{ "00000010", "00000010" } } }, - { UnwrappedTileID{ 1, 0, 0 }, Renderable{ ClipID{ "00000011", "00000001" } } }, + Renderable{ UnwrappedTileID{ 0, 0, 0 }, ClipID{ "00000010", "00000010" } }, + Renderable{ UnwrappedTileID{ 1, 0, 0 }, ClipID{ "00000011", "00000001" } }, }), renderables2); EXPECT_EQ(decltype(renderables3)({ - { UnwrappedTileID{ 0, 0, 0 }, Renderable{ ClipID{ "00000100", "00000100" } } }, + Renderable{ UnwrappedTileID{ 0, 0, 0 }, ClipID{ "00000100", "00000100" } }, }), renderables3); - const auto stencils = generator.getStencils(); - EXPECT_EQ(decltype(stencils)({ + const auto clipIDs = generator.getClipIDs(); + EXPECT_EQ(decltype(clipIDs)({ { UnwrappedTileID{ 0, 0, 0 }, ClipID{ "00000110", "00000110" } }, { UnwrappedTileID{ 1, 0, 0 }, ClipID{ "00000111", "00000101" } }, }), - stencils); + clipIDs); } diff --git a/test/algorithm/update_renderables.test.cpp b/test/algorithm/update_renderables.test.cpp index af90d262de..2d37992579 100644 --- a/test/algorithm/update_renderables.test.cpp +++ b/test/algorithm/update_renderables.test.cpp @@ -23,10 +23,10 @@ struct GetTileDataAction { }; std::ostream& operator<<(std::ostream& os, const GetTileDataAction& action) { - return os << "GetTileDataAction{ { " << int(action.tileID.overscaledZ) << ", { " + return os << "GetTileDataAction{ { " << int(action.tileID.overscaledZ) << ", " << int(action.tileID.wrap) << ", { " << int(action.tileID.canonical.z) << ", " << action.tileID.canonical.x << ", " << action.tileID.canonical.y << " } }, " - << (action.found == Found ? "Found" : "NotFound") << " }"; + << (action.found == Found ? "Found" : "NotFound") << " }\n"; } struct CreateTileDataAction { @@ -38,9 +38,9 @@ struct CreateTileDataAction { }; std::ostream& operator<<(std::ostream& os, const CreateTileDataAction& action) { - return os << "CreateTileDataAction{ { " << int(action.tileID.overscaledZ) << ", { " + return os << "CreateTileDataAction{ { " << int(action.tileID.overscaledZ) << ", " << int(action.tileID.wrap) << ", { " << int(action.tileID.canonical.z) << ", " << action.tileID.canonical.x << ", " - << action.tileID.canonical.y << " } } }"; + << action.tileID.canonical.y << " } } }\n"; } struct RetainTileDataAction { @@ -53,10 +53,10 @@ struct RetainTileDataAction { }; std::ostream& operator<<(std::ostream& os, const RetainTileDataAction& action) { - return os << "RetainTileDataAction{ { " << int(action.tileID.overscaledZ) << ", { " + return os << "RetainTileDataAction{ { " << int(action.tileID.overscaledZ) << ", " << int(action.tileID.wrap) << ", { " << int(action.tileID.canonical.z) << ", " << action.tileID.canonical.x << ", " << action.tileID.canonical.y << " } }, " - << (action.necessity == Resource::Necessity::Required ? "Required" : "Optional") << " }"; + << (action.necessity == Resource::Necessity::Required ? "Required" : "Optional") << " }\n"; } struct RenderTileAction { @@ -76,7 +76,7 @@ std::ostream& operator<<(std::ostream& os, const RenderTileAction& action) { << int(action.tileData.tileID.overscaledZ) << "_" << int(action.tileData.tileID.canonical.z) << "_" << action.tileData.tileID.canonical.x << "_" << action.tileData.tileID.canonical.y - << " }"; + << " }\n"; } using ActionLogEntry = @@ -100,12 +100,14 @@ auto createTileDataFn(ActionLog& log, T& dataTiles) { }; } +template <typename = int> auto retainTileDataFn(ActionLog& log) { return [&](auto& tileData, Resource::Necessity necessity) { log.emplace_back(RetainTileDataAction{ tileData.tileID, necessity }); }; } +template <typename = int> auto renderTileFn(ActionLog& log) { return [&](const auto& id, auto& tileData) { log.emplace_back(RenderTileAction{ id, tileData }); @@ -129,8 +131,8 @@ TEST(UpdateRenderables, SingleTile) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 1, { 1, 1, 1 } }, Found }, // found ideal tile - RetainTileDataAction{ { 1, { 1, 1, 1 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 1, 0, { 1, 1, 1 } }, Found }, // found ideal tile + RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, Resource::Necessity::Required }, // RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render ideal tile }), log); @@ -140,8 +142,8 @@ TEST(UpdateRenderables, SingleTile) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 1, { 1, 1, 1 } }, Found }, // found ideal tile - RetainTileDataAction{ { 1, { 1, 1, 1 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 1, 0, { 1, 1, 1 } }, Found }, // found ideal tile + RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, Resource::Necessity::Required }, // RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render ideal tile }), log); @@ -152,39 +154,39 @@ TEST(UpdateRenderables, SingleTile) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 1, { 1, 0, 1 } }, NotFound }, // missing ideal tile - CreateTileDataAction{ { 1, { 1, 0, 1 } } }, // create ideal tile - RetainTileDataAction{ { 1, { 1, 0, 1 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 2, { 2, 0, 2 } }, NotFound }, // four child tiles - GetTileDataAction{ { 2, { 2, 0, 3 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 1, 2 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 1, 3 } }, NotFound }, // ... - GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // parent tile - - GetTileDataAction{ { 1, { 1, 1, 1 } }, Found }, // found ideal tile - RetainTileDataAction{ { 1, { 1, 1, 1 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 1, 0, { 1, 0, 1 } }, NotFound }, // missing ideal tile + CreateTileDataAction{ { 1, 0, { 1, 0, 1 } } }, // create ideal tile + RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 2, 0, { 2, 0, 2 } }, NotFound }, // four child tiles + GetTileDataAction{ { 2, 0, { 2, 0, 3 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 1, 2 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 1, 3 } }, NotFound }, // ... + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // parent tile + + GetTileDataAction{ { 1, 0, { 1, 1, 1 } }, Found }, // found ideal tile + RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, Resource::Necessity::Required }, // RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render found tile }), log); // Mark the created tile as having the optional request tried. log.clear(); - source.dataTiles[{ 1, { 1, 0, 1 } }]->triedOptional = true; + source.dataTiles[{ 1, 0, { 1, 0, 1 } }]->triedOptional = true; algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 1, { 1, 0, 1 } }, Found }, // missing ideal tile - RetainTileDataAction{ { 1, { 1, 0, 1 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 2, { 2, 0, 2 } }, NotFound }, // four child tiles - GetTileDataAction{ { 2, { 2, 0, 3 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 1, 2 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 1, 3 } }, NotFound }, // ... - GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // parent tile - CreateTileDataAction{ { 0, { 0, 0, 0 } } }, // load parent tile - RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // - - GetTileDataAction{ { 1, { 1, 1, 1 } }, Found }, // found ideal tile - RetainTileDataAction{ { 1, { 1, 1, 1 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 1, 0, { 1, 0, 1 } }, Found }, // missing ideal tile + RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 2, 0, { 2, 0, 2 } }, NotFound }, // four child tiles + GetTileDataAction{ { 2, 0, { 2, 0, 3 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 1, 2 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 1, 3 } }, NotFound }, // ... + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // parent tile + CreateTileDataAction{ { 0, 0, { 0, 0, 0 } } }, // load parent tile + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + + GetTileDataAction{ { 1, 0, { 1, 1, 1 } }, Found }, // found ideal tile + RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, Resource::Necessity::Required }, // RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render found tile }), log); @@ -196,12 +198,12 @@ TEST(UpdateRenderables, SingleTile) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 1, { 1, 0, 1 } }, Found }, // newly added tile - RetainTileDataAction{ { 1, { 1, 0, 1 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 1, 0, { 1, 0, 1 } }, Found }, // newly added tile + RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, Resource::Necessity::Required }, // RenderTileAction{ { 1, 0, 1 }, *tile_1_1_0_1 }, // render ideal tile - GetTileDataAction{ { 1, { 1, 1, 1 } }, Found }, // ideal tile - RetainTileDataAction{ { 1, { 1, 1, 1 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 1, 0, { 1, 1, 1 } }, Found }, // ideal tile + RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, Resource::Necessity::Required }, // RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render found tile }), log); @@ -215,22 +217,22 @@ TEST(UpdateRenderables, SingleTile) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // found tile, not ready - RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // four child tiles - GetTileDataAction{ { 2, { 2, 0, 1 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 1, 0 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 1, 1 } }, NotFound }, // ... - GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, // parent tile - RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // found tile, not ready + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // four child tiles + GetTileDataAction{ { 2, 0, { 2, 0, 1 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 1, 0 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 1, 1 } }, NotFound }, // ... + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, Found }, // parent tile + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // // optional parent tile was already created before, but is not renderable - GetTileDataAction{ { 1, { 1, 0, 1 } }, Found }, // ideal tile - RetainTileDataAction{ { 1, { 1, 0, 1 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 1, 0, { 1, 0, 1 } }, Found }, // ideal tile + RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, Resource::Necessity::Required }, // RenderTileAction{ { 1, 0, 1 }, *tile_1_1_0_1 }, // render ideal tile - GetTileDataAction{ { 1, { 1, 1, 1 } }, Found }, // ideal tile - RetainTileDataAction{ { 1, { 1, 1, 1 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 1, 0, { 1, 1, 1 } }, Found }, // ideal tile + RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, Resource::Necessity::Required }, // RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render ideal tile }), log); @@ -241,16 +243,16 @@ TEST(UpdateRenderables, SingleTile) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // found tile, now ready - RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // found tile, now ready + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Required }, // RenderTileAction{ { 1, 0, 0 }, *tile_1_1_0_0 }, // - GetTileDataAction{ { 1, { 1, 0, 1 } }, Found }, // ideal tile - RetainTileDataAction{ { 1, { 1, 0, 1 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 1, 0, { 1, 0, 1 } }, Found }, // ideal tile + RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, Resource::Necessity::Required }, // RenderTileAction{ { 1, 0, 1 }, *tile_1_1_0_1 }, // - GetTileDataAction{ { 1, { 1, 1, 1 } }, Found }, // ideal tile - RetainTileDataAction{ { 1, { 1, 1, 1 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 1, 0, { 1, 1, 1 } }, Found }, // ideal tile + RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, Resource::Necessity::Required }, // RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // }), log); @@ -274,30 +276,30 @@ TEST(UpdateRenderables, UseParentTile) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 1, { 1, 0, 1 } }, NotFound }, // missing ideal tile - CreateTileDataAction{ { 1, { 1, 0, 1 } } }, // - RetainTileDataAction{ { 1, { 1, 0, 1 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 2, { 2, 0, 2 } }, NotFound }, // child tile - GetTileDataAction{ { 2, { 2, 0, 3 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 1, 2 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 1, 3 } }, NotFound }, // ... - GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, // parent found! - RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 1, 0, { 1, 0, 1 } }, NotFound }, // missing ideal tile + CreateTileDataAction{ { 1, 0, { 1, 0, 1 } } }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 2, 0, { 2, 0, 2 } }, NotFound }, // child tile + GetTileDataAction{ { 2, 0, { 2, 0, 3 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 1, 2 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 1, 3 } }, NotFound }, // ... + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, Found }, // parent found! + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // render parent - GetTileDataAction{ { 1, { 1, 1, 0 } }, NotFound }, // missing ideal tile - CreateTileDataAction{ { 1, { 1, 1, 0 } } }, // - RetainTileDataAction{ { 1, { 1, 1, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 2, { 2, 2, 0 } }, NotFound }, // child tile - GetTileDataAction{ { 2, { 2, 2, 1 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 3, 0 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 3, 1 } }, NotFound }, // ... - GetTileDataAction{ { 1, { 1, 1, 1 } }, NotFound }, // missing tile - CreateTileDataAction{ { 1, { 1, 1, 1 } } }, // - RetainTileDataAction{ { 1, { 1, 1, 1 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 2, { 2, 2, 2 } }, NotFound }, // child tile - GetTileDataAction{ { 2, { 2, 2, 3 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 3, 2 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 3, 3 } }, NotFound }, // ... + GetTileDataAction{ { 1, 0, { 1, 1, 0 } }, NotFound }, // missing ideal tile + CreateTileDataAction{ { 1, 0, { 1, 1, 0 } } }, // + RetainTileDataAction{ { 1, 0, { 1, 1, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 2, 0, { 2, 2, 0 } }, NotFound }, // child tile + GetTileDataAction{ { 2, 0, { 2, 2, 1 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 3, 0 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 3, 1 } }, NotFound }, // ... + GetTileDataAction{ { 1, 0, { 1, 1, 1 } }, NotFound }, // missing tile + CreateTileDataAction{ { 1, 0, { 1, 1, 1 } } }, // + RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 2, 0, { 2, 2, 2 } }, NotFound }, // child tile + GetTileDataAction{ { 2, 0, { 2, 2, 3 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 3, 2 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 3, 3 } }, NotFound }, // ... }), log); } @@ -317,34 +319,34 @@ TEST(UpdateRenderables, DontUseWrongParentTile) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 2); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // missing ideal tile - CreateTileDataAction{ { 2, { 2, 0, 0 } } }, // - RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // child tile - GetTileDataAction{ { 3, { 3, 0, 1 } }, NotFound }, // ... - GetTileDataAction{ { 3, { 3, 1, 0 } }, NotFound }, // ... - GetTileDataAction{ { 3, { 3, 1, 1 } }, NotFound }, // ... - GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // parent tile, missing - GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // parent tile, missing + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // missing ideal tile + CreateTileDataAction{ { 2, 0, { 2, 0, 0 } } }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // child tile + GetTileDataAction{ { 3, 0, { 3, 0, 1 } }, NotFound }, // ... + GetTileDataAction{ { 3, 0, { 3, 1, 0 } }, NotFound }, // ... + GetTileDataAction{ { 3, 0, { 3, 1, 1 } }, NotFound }, // ... + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // parent tile, missing + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // parent tile, missing }), log); // Now mark the created tile as having the optional request tried. log.clear(); - source.dataTiles[{ 2, { 2, 0, 0 } }]->triedOptional = true; + source.dataTiles[{ 2, 0, { 2, 0, 0 } }]->triedOptional = true; algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 2); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, // non-ready ideal tile - RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // child tile - GetTileDataAction{ { 3, { 3, 0, 1 } }, NotFound }, // ... - GetTileDataAction{ { 3, { 3, 1, 0 } }, NotFound }, // ... - GetTileDataAction{ { 3, { 3, 1, 1 } }, NotFound }, // ... - GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // parent tile, missing - CreateTileDataAction{ { 1, { 1, 0, 0 } } }, // find optional parent - RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Optional }, // - GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // parent tile, missing + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // non-ready ideal tile + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // child tile + GetTileDataAction{ { 3, 0, { 3, 0, 1 } }, NotFound }, // ... + GetTileDataAction{ { 3, 0, { 3, 1, 0 } }, NotFound }, // ... + GetTileDataAction{ { 3, 0, { 3, 1, 1 } }, NotFound }, // ... + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // parent tile, missing + CreateTileDataAction{ { 1, 0, { 1, 0, 0 } } }, // find optional parent + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // parent tile, missing }), log); @@ -354,26 +356,26 @@ TEST(UpdateRenderables, DontUseWrongParentTile) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 2); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, // non-ready ideal tile - RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // non-ready ideal tile + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // // this tile was added by the previous invocation of updateRenderables - GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // child tile - GetTileDataAction{ { 3, { 3, 0, 1 } }, NotFound }, // ... - GetTileDataAction{ { 3, { 3, 1, 0 } }, NotFound }, // ... - GetTileDataAction{ { 3, { 3, 1, 1 } }, NotFound }, // ... - GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // parent tile not ready - RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Optional }, // - GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // missing parent tile - - GetTileDataAction{ { 2, { 2, 2, 0 } }, NotFound }, // missing ideal tile - CreateTileDataAction{ { 2, { 2, 2, 0 } } }, // - RetainTileDataAction{ { 2, { 2, 2, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 3, { 3, 4, 0 } }, NotFound }, // child tile - GetTileDataAction{ { 3, { 3, 4, 1 } }, NotFound }, // ... - GetTileDataAction{ { 3, { 3, 5, 0 } }, NotFound }, // ... - GetTileDataAction{ { 3, { 3, 5, 1 } }, NotFound }, // ... - GetTileDataAction{ { 1, { 1, 1, 0 } }, Found }, // found parent tile - RetainTileDataAction{ { 1, { 1, 1, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // child tile + GetTileDataAction{ { 3, 0, { 3, 0, 1 } }, NotFound }, // ... + GetTileDataAction{ { 3, 0, { 3, 1, 0 } }, NotFound }, // ... + GetTileDataAction{ { 3, 0, { 3, 1, 1 } }, NotFound }, // ... + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // parent tile not ready + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // missing parent tile + + GetTileDataAction{ { 2, 0, { 2, 2, 0 } }, NotFound }, // missing ideal tile + CreateTileDataAction{ { 2, 0, { 2, 2, 0 } } }, // + RetainTileDataAction{ { 2, 0, { 2, 2, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 3, 0, { 3, 4, 0 } }, NotFound }, // child tile + GetTileDataAction{ { 3, 0, { 3, 4, 1 } }, NotFound }, // ... + GetTileDataAction{ { 3, 0, { 3, 5, 0 } }, NotFound }, // ... + GetTileDataAction{ { 3, 0, { 3, 5, 1 } }, NotFound }, // ... + GetTileDataAction{ { 1, 0, { 1, 1, 0 } }, Found }, // found parent tile + RetainTileDataAction{ { 1, 0, { 1, 1, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 1, 1, 0 }, *tile_1_1_1_0 }, // render parent tile }), log); @@ -399,14 +401,14 @@ TEST(UpdateRenderables, UseParentTileWhenChildNotReady) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 1, { 1, 0, 1 } }, Found }, // found, but not ready - RetainTileDataAction{ { 1, { 1, 0, 1 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 2, { 2, 0, 2 } }, NotFound }, // child tile - GetTileDataAction{ { 2, { 2, 0, 3 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 1, 2 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 1, 3 } }, NotFound }, // ... - GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, // parent tile, ready - RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 1, 0, { 1, 0, 1 } }, Found }, // found, but not ready + RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 2, 0, { 2, 0, 2 } }, NotFound }, // child tile + GetTileDataAction{ { 2, 0, { 2, 0, 3 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 1, 2 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 1, 3 } }, NotFound }, // ... + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, Found }, // parent tile, ready + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // render parent tile }), log); @@ -417,8 +419,8 @@ TEST(UpdateRenderables, UseParentTileWhenChildNotReady) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 1, { 1, 0, 1 } }, Found }, // found and ready - RetainTileDataAction{ { 1, { 1, 0, 1 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 1, 0, { 1, 0, 1 } }, Found }, // found and ready + RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, Resource::Necessity::Required }, // RenderTileAction{ { 1, 0, 1 }, *tile_1_1_0_1 }, // render ideal tile }), log); @@ -444,19 +446,19 @@ TEST(UpdateRenderables, UseOverlappingParentTile) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ideal tile not found - CreateTileDataAction{ { 1, { 1, 0, 0 } } }, // - RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // child tile - GetTileDataAction{ { 2, { 2, 0, 1 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 1, 0 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 1, 1 } }, NotFound }, // ... - GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, // parent tile found - RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // ideal tile not found + CreateTileDataAction{ { 1, 0, { 1, 0, 0 } } }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // child tile + GetTileDataAction{ { 2, 0, { 2, 0, 1 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 1, 0 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 1, 1 } }, NotFound }, // ... + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, Found }, // parent tile found + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // - GetTileDataAction{ { 1, { 1, 0, 1 } }, Found }, // ideal tile found - RetainTileDataAction{ { 1, { 1, 0, 1 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 1, 0, { 1, 0, 1 } }, Found }, // ideal tile found + RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, Resource::Necessity::Required }, // RenderTileAction{ { 1, 0, 1 }, *tile_1_1_0_1 }, // }), log); @@ -480,17 +482,17 @@ TEST(UpdateRenderables, UseChildTiles) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 0); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // ideal tile, missing - CreateTileDataAction{ { 0, { 0, 0, 0 } } }, // - RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // child tile found - RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // ideal tile, missing + CreateTileDataAction{ { 0, 0, { 0, 0, 0 } } }, // + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // child tile found + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 1, 0, 0 }, *tile_1_1_0_0 }, // render child tile - GetTileDataAction{ { 1, { 1, 0, 1 } }, NotFound }, // child tile not found - GetTileDataAction{ { 1, { 1, 1, 0 } }, Found }, // child tile found - RetainTileDataAction{ { 1, { 1, 1, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 1, 0, { 1, 0, 1 } }, NotFound }, // child tile not found + GetTileDataAction{ { 1, 0, { 1, 1, 0 } }, Found }, // child tile found + RetainTileDataAction{ { 1, 0, { 1, 1, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 1, 1, 0 }, *tile_1_1_1_0 }, // render child tile - GetTileDataAction{ { 1, { 1, 1, 1 } }, NotFound }, // child tile not found + GetTileDataAction{ { 1, 0, { 1, 1, 1 } }, NotFound }, // child tile not found // no parent tile of 0 to consider }), log); @@ -514,17 +516,17 @@ TEST(UpdateRenderables, PreferChildTiles) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ideal tile, not found - CreateTileDataAction{ { 1, { 1, 0, 0 } } }, // - RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, // child tile, found - RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // ideal tile, not found + CreateTileDataAction{ { 1, 0, { 1, 0, 0 } } }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // child tile, found + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // - GetTileDataAction{ { 2, { 2, 0, 1 } }, NotFound }, // child tile, not found - GetTileDataAction{ { 2, { 2, 1, 0 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 1, 1 } }, NotFound }, // ... - GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, // parent tile, found - RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 2, 0, { 2, 0, 1 } }, NotFound }, // child tile, not found + GetTileDataAction{ { 2, 0, { 2, 1, 0 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 1, 1 } }, NotFound }, // ... + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, Found }, // parent tile, found + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // }), log); @@ -537,19 +539,19 @@ TEST(UpdateRenderables, PreferChildTiles) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // ideal tile, not ready + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // ideal tile, not ready // ideal tile was added in previous invocation, but is not yet ready - RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, // child tile, found - RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // child tile, found + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // - GetTileDataAction{ { 2, { 2, 0, 1 } }, Found }, // ... - RetainTileDataAction{ { 2, { 2, 0, 1 } }, Resource::Necessity::Optional }, // ... + GetTileDataAction{ { 2, 0, { 2, 0, 1 } }, Found }, // ... + RetainTileDataAction{ { 2, 0, { 2, 0, 1 } }, Resource::Necessity::Optional }, // ... RenderTileAction{ { 2, 0, 1 }, *tile_2_2_0_1 }, // - GetTileDataAction{ { 2, { 2, 1, 0 } }, NotFound }, // child tile, not found - GetTileDataAction{ { 2, { 2, 1, 1 } }, NotFound }, // ... - GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, // parent tile, found - RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 2, 0, { 2, 1, 0 } }, NotFound }, // child tile, not found + GetTileDataAction{ { 2, 0, { 2, 1, 1 } }, NotFound }, // ... + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, Found }, // parent tile, found + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // }), log); @@ -560,21 +562,21 @@ TEST(UpdateRenderables, PreferChildTiles) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // ideal tile, not ready + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // ideal tile, not ready // ideal tile was added in first invocation, but is not yet ready - RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, // child tile, found - RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // child tile, found + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // - GetTileDataAction{ { 2, { 2, 0, 1 } }, Found }, // ... - RetainTileDataAction{ { 2, { 2, 0, 1 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 2, 0, { 2, 0, 1 } }, Found }, // ... + RetainTileDataAction{ { 2, 0, { 2, 0, 1 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 2, 0, 1 }, *tile_2_2_0_1 }, // - GetTileDataAction{ { 2, { 2, 1, 0 } }, Found }, // ... - RetainTileDataAction{ { 2, { 2, 1, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 2, 0, { 2, 1, 0 } }, Found }, // ... + RetainTileDataAction{ { 2, 0, { 2, 1, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 2, 1, 0 }, *tile_2_2_1_0 }, // - GetTileDataAction{ { 2, { 2, 1, 1 } }, NotFound }, // child tile, not found - GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, // parent tile, found - RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 2, 0, { 2, 1, 1 } }, NotFound }, // child tile, not found + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, Found }, // parent tile, found + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // }), log); @@ -586,20 +588,20 @@ TEST(UpdateRenderables, PreferChildTiles) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // ideal tile, not ready + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // ideal tile, not ready // ideal tile was added in first invocation, but is not yet ready - RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, // child tile, found - RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // child tile, found + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // - GetTileDataAction{ { 2, { 2, 0, 1 } }, Found }, // ... - RetainTileDataAction{ { 2, { 2, 0, 1 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 2, 0, { 2, 0, 1 } }, Found }, // ... + RetainTileDataAction{ { 2, 0, { 2, 0, 1 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 2, 0, 1 }, *tile_2_2_0_1 }, // - GetTileDataAction{ { 2, { 2, 1, 0 } }, Found }, // ... - RetainTileDataAction{ { 2, { 2, 1, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 2, 0, { 2, 1, 0 } }, Found }, // ... + RetainTileDataAction{ { 2, 0, { 2, 1, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 2, 1, 0 }, *tile_2_2_1_0 }, // - GetTileDataAction{ { 2, { 2, 1, 1 } }, Found }, // ... - RetainTileDataAction{ { 2, { 2, 1, 1 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 2, 0, { 2, 1, 1 } }, Found }, // ... + RetainTileDataAction{ { 2, 0, { 2, 1, 1 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 2, 1, 1 }, *tile_2_2_1_1 }, // }), log); @@ -624,17 +626,17 @@ TEST(UpdateRenderables, UseParentAndChildTiles) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ideal tile, missing - CreateTileDataAction{ { 1, { 1, 0, 0 } } }, // - RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, // child tile - RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // ideal tile, missing + CreateTileDataAction{ { 1, 0, { 1, 0, 0 } } }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // child tile + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // - GetTileDataAction{ { 2, { 2, 0, 1 } }, NotFound }, // - GetTileDataAction{ { 2, { 2, 1, 0 } }, NotFound }, // - GetTileDataAction{ { 2, { 2, 1, 1 } }, NotFound }, // - GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, // parent tile - RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 2, 0, { 2, 0, 1 } }, NotFound }, // + GetTileDataAction{ { 2, 0, { 2, 1, 0 } }, NotFound }, // + GetTileDataAction{ { 2, 0, { 2, 1, 1 } }, NotFound }, // + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, Found }, // parent tile + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // }), log); @@ -645,14 +647,14 @@ TEST(UpdateRenderables, UseParentAndChildTiles) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // - GetTileDataAction{ { 2, { 2, 0, 1 } }, NotFound }, // - GetTileDataAction{ { 2, { 2, 1, 0 } }, NotFound }, // - GetTileDataAction{ { 2, { 2, 1, 1 } }, NotFound }, // - GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, // parent tile - RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // ideal tile, not ready + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // + GetTileDataAction{ { 2, 0, { 2, 0, 1 } }, NotFound }, // + GetTileDataAction{ { 2, 0, { 2, 1, 0 } }, NotFound }, // + GetTileDataAction{ { 2, 0, { 2, 1, 1 } }, NotFound }, // + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, Found }, // parent tile + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // }), log); @@ -675,13 +677,13 @@ TEST(UpdateRenderables, DontUseTilesLowerThanMinzoom) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 2); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // ideal tile, missing - CreateTileDataAction{ { 2, { 2, 0, 0 } } }, // - RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // - GetTileDataAction{ { 3, { 3, 0, 1 } }, NotFound }, // - GetTileDataAction{ { 3, { 3, 1, 0 } }, NotFound }, // - GetTileDataAction{ { 3, { 3, 1, 1 } }, NotFound }, // + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // ideal tile, missing + CreateTileDataAction{ { 2, 0, { 2, 0, 0 } } }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // + GetTileDataAction{ { 3, 0, { 3, 0, 1 } }, NotFound }, // + GetTileDataAction{ { 3, 0, { 3, 1, 0 } }, NotFound }, // + GetTileDataAction{ { 3, 0, { 3, 1, 1 } }, NotFound }, // // no requests for zoom 1 tiles }), log); @@ -705,58 +707,58 @@ TEST(UpdateRenderables, UseOverzoomedTileAfterMaxzoom) { source.idealTiles, source.zoomRange, 2); EXPECT_EQ( ActionLog({ - GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // ideal tile, missing - CreateTileDataAction{ { 2, { 2, 0, 0 } } }, // - RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 3, { 2, 0, 0 } }, NotFound }, // overzoomed tile, not children! - GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // - GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // ideal tile, missing + CreateTileDataAction{ { 2, 0, { 2, 0, 0 } } }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 3, 0, { 2, 0, 0 } }, NotFound }, // overzoomed tile, not children! + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // }), log); // Mark the created tile as having tried the optional request. log.clear(); - source.dataTiles[{ 2, { 2, 0, 0 } }]->triedOptional = true; + source.dataTiles[{ 2, 0, { 2, 0, 0 } }]->triedOptional = true; algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 2); EXPECT_EQ( ActionLog({ - GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, // ideal tile, missing - RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 3, { 2, 0, 0 } }, NotFound }, // overzoomed tile, not children! - GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // - CreateTileDataAction{ { 1, { 1, 0, 0 } } }, // - RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Optional }, // - GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // ideal tile, missing + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 3, 0, { 2, 0, 0 } }, NotFound }, // overzoomed tile, not children! + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // + CreateTileDataAction{ { 1, 0, { 1, 0, 0 } } }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // }), log); // Only add a non-overzoomed ("parent") tile at first. log.clear(); - auto tile_2_2_0_0 = source.createTileData(OverscaledTileID{ 2, { 2, 0, 0 } }); + auto tile_2_2_0_0 = source.createTileData(OverscaledTileID{ 2, 0, { 2, 0, 0 } }); tile_2_2_0_0->renderable = true; algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 3); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 3, { 2, 0, 0 } }, NotFound }, // ideal tile, missing - CreateTileDataAction{ { 3, { 2, 0, 0 } } }, // - RetainTileDataAction{ { 3, { 2, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 4, { 2, 0, 0 } }, NotFound }, // - GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, // - RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 3, 0, { 2, 0, 0 } }, NotFound }, // ideal tile, missing + CreateTileDataAction{ { 3, 0, { 2, 0, 0 } } }, // + RetainTileDataAction{ { 3, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 4, 0, { 2, 0, 0 } }, NotFound }, // + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // }), log); // Then add the overzoomed tile matching the zoom level we're rendering. log.clear(); - auto tile_3_2_0_0 = source.createTileData(OverscaledTileID{ 3, { 2, 0, 0 } }); + auto tile_3_2_0_0 = source.createTileData(OverscaledTileID{ 3, 0, { 2, 0, 0 } }); tile_3_2_0_0->renderable = true; algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 3); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 3, { 2, 0, 0 } }, Found }, // - RetainTileDataAction{ { 3, { 2, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 3, 0, { 2, 0, 0 } }, Found }, // + RetainTileDataAction{ { 3, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // RenderTileAction{ { 2, 0, 0 }, *tile_3_2_0_0 }, // }), log); @@ -766,26 +768,26 @@ TEST(UpdateRenderables, UseOverzoomedTileAfterMaxzoom) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 2); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, // - RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // }), log); // Now remove the best match. log.clear(); - source.dataTiles.erase(OverscaledTileID{ 2, { 2, 0, 0 } }); + source.dataTiles.erase(OverscaledTileID{ 2, 0, { 2, 0, 0 } }); tile_2_2_0_0 = nullptr; // Use the overzoomed tile even though it doesn't match the zoom level. algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 2); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // - CreateTileDataAction{ { 2, { 2, 0, 0 } } }, // - RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 3, { 2, 0, 0 } }, Found }, // use overzoomed tile! - RetainTileDataAction{ { 3, { 2, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // + CreateTileDataAction{ { 2, 0, { 2, 0, 0 } } }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 3, 0, { 2, 0, 0 } }, Found }, // use overzoomed tile! + RetainTileDataAction{ { 3, 0, { 2, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 2, 0, 0 }, *tile_3_2_0_0 }, // }), log); @@ -803,69 +805,69 @@ TEST(UpdateRenderables, AscendToNonOverzoomedTiles) { source.idealTiles.emplace(UnwrappedTileID{ 2, 0, 0 }); // Add a matching overzoomed tile and make sure it gets selected. - auto tile_3_2_0_0 = source.createTileData(OverscaledTileID{ 3, { 2, 0, 0 } }); + auto tile_3_2_0_0 = source.createTileData(OverscaledTileID{ 3, 0, { 2, 0, 0 } }); tile_3_2_0_0->renderable = true; algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 3); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 3, { 2, 0, 0 } }, Found }, // - RetainTileDataAction{ { 3, { 2, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 3, 0, { 2, 0, 0 } }, Found }, // + RetainTileDataAction{ { 3, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // RenderTileAction{ { 2, 0, 0 }, *tile_3_2_0_0 }, // }), log); // Then, swap it with a non-overzoomed tile. log.clear(); - source.dataTiles.erase(OverscaledTileID{ 3, { 2, 0, 0 } }); + source.dataTiles.erase(OverscaledTileID{ 3, 0, { 2, 0, 0 } }); tile_3_2_0_0 = nullptr; - auto tile_2_2_0_0 = source.createTileData(OverscaledTileID{ 2, { 2, 0, 0 } }); + auto tile_2_2_0_0 = source.createTileData(OverscaledTileID{ 2, 0, { 2, 0, 0 } }); tile_2_2_0_0->renderable = true; algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 3); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 3, { 2, 0, 0 } }, NotFound }, // - CreateTileDataAction{ { 3, { 2, 0, 0 } } }, // - RetainTileDataAction{ { 3, { 2, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 4, { 2, 0, 0 } }, NotFound }, // prefer using a child first - GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, // - RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 3, 0, { 2, 0, 0 } }, NotFound }, // + CreateTileDataAction{ { 3, 0, { 2, 0, 0 } } }, // + RetainTileDataAction{ { 3, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 4, 0, { 2, 0, 0 } }, NotFound }, // prefer using a child first + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // }), log); // Then, swap it with a parent tile. log.clear(); - source.dataTiles.erase(OverscaledTileID{ 2, { 2, 0, 0 } }); + source.dataTiles.erase(OverscaledTileID{ 2, 0, { 2, 0, 0 } }); tile_2_2_0_0 = nullptr; - auto tile_1_1_0_0 = source.createTileData(OverscaledTileID{ 1, { 1, 0, 0 } }); + auto tile_1_1_0_0 = source.createTileData(OverscaledTileID{ 1, 0, { 1, 0, 0 } }); tile_1_1_0_0->renderable = true; algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 3); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 3, { 2, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 3, { 2, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 4, { 2, 0, 0 } }, NotFound }, // - GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // - GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // - RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 3, 0, { 2, 0, 0 } }, Found }, // ideal tile, not ready + RetainTileDataAction{ { 3, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 4, 0, { 2, 0, 0 } }, NotFound }, // + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 1, 0, 0 }, *tile_1_1_0_0 }, // }), log); // Now, mark the created tile as found. log.clear(); - source.dataTiles[{ 3, { 2, 0, 0 } }]->triedOptional = true; + source.dataTiles[{ 3, 0, { 2, 0, 0 } }]->triedOptional = true; algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 3); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 3, { 2, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 3, { 2, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 4, { 2, 0, 0 } }, NotFound }, // - GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // - CreateTileDataAction{ { 2, { 2, 0, 0 } } }, // - RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Optional }, // - GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // - RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 3, 0, { 2, 0, 0 } }, Found }, // ideal tile, not ready + RetainTileDataAction{ { 3, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 4, 0, { 2, 0, 0 } }, NotFound }, // + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // + CreateTileDataAction{ { 2, 0, { 2, 0, 0 } } }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 1, 0, 0 }, *tile_1_1_0_0 }, // }), log); @@ -885,60 +887,60 @@ TEST(UpdateRenderables, DoNotAscendMultipleTimesIfNotFound) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 8); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 8, { 8, 0, 0 } }, NotFound }, // ideal tile - CreateTileDataAction{ { 8, { 8, 0, 0 } } }, // - RetainTileDataAction{ { 8, { 8, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 9, { 9, 0, 0 } }, NotFound }, // child tile - GetTileDataAction{ { 9, { 9, 0, 1 } }, NotFound }, // ... - GetTileDataAction{ { 9, { 9, 1, 0 } }, NotFound }, // ... - GetTileDataAction{ { 9, { 9, 1, 1 } }, NotFound }, // ... - GetTileDataAction{ { 7, { 7, 0, 0 } }, NotFound }, // ascent - GetTileDataAction{ { 6, { 6, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 5, { 5, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 4, { 4, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // ... - - GetTileDataAction{ { 8, { 8, 1, 0 } }, NotFound }, // ideal tile - CreateTileDataAction{ { 8, { 8, 1, 0 } } }, // - RetainTileDataAction{ { 8, { 8, 1, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 9, { 9, 2, 0 } }, NotFound }, // child tile - GetTileDataAction{ { 9, { 9, 2, 1 } }, NotFound }, // ... - GetTileDataAction{ { 9, { 9, 3, 0 } }, NotFound }, // ... - GetTileDataAction{ { 9, { 9, 3, 1 } }, NotFound }, // ... + GetTileDataAction{ { 8, 0, { 8, 0, 0 } }, NotFound }, // ideal tile + CreateTileDataAction{ { 8, 0, { 8, 0, 0 } } }, // + RetainTileDataAction{ { 8, 0, { 8, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 9, 0, { 9, 0, 0 } }, NotFound }, // child tile + GetTileDataAction{ { 9, 0, { 9, 0, 1 } }, NotFound }, // ... + GetTileDataAction{ { 9, 0, { 9, 1, 0 } }, NotFound }, // ... + GetTileDataAction{ { 9, 0, { 9, 1, 1 } }, NotFound }, // ... + GetTileDataAction{ { 7, 0, { 7, 0, 0 } }, NotFound }, // ascent + GetTileDataAction{ { 6, 0, { 6, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 5, 0, { 5, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 4, 0, { 4, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // ... + + GetTileDataAction{ { 8, 0, { 8, 1, 0 } }, NotFound }, // ideal tile + CreateTileDataAction{ { 8, 0, { 8, 1, 0 } } }, // + RetainTileDataAction{ { 8, 0, { 8, 1, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 9, 0, { 9, 2, 0 } }, NotFound }, // child tile + GetTileDataAction{ { 9, 0, { 9, 2, 1 } }, NotFound }, // ... + GetTileDataAction{ { 9, 0, { 9, 3, 0 } }, NotFound }, // ... + GetTileDataAction{ { 9, 0, { 9, 3, 1 } }, NotFound }, // ... // no second ascent to 0 }), log); // Now add a mid-level tile that stops the ascent log.clear(); - auto tile_4_0_0_0 = source.createTileData(OverscaledTileID{ 4, { 4, 0, 0 } }); + auto tile_4_0_0_0 = source.createTileData(OverscaledTileID{ 4, 0, { 4, 0, 0 } }); tile_4_0_0_0->renderable = true; algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 8); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 8, { 8, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 8, { 8, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 9, { 9, 0, 0 } }, NotFound }, // child tile - GetTileDataAction{ { 9, { 9, 0, 1 } }, NotFound }, // ... - GetTileDataAction{ { 9, { 9, 1, 0 } }, NotFound }, // ... - GetTileDataAction{ { 9, { 9, 1, 1 } }, NotFound }, // ... - GetTileDataAction{ { 7, { 7, 0, 0 } }, NotFound }, // ascent - GetTileDataAction{ { 6, { 6, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 5, { 5, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 4, { 4, 0, 0 } }, Found }, // stops ascent - RetainTileDataAction{ { 4, { 4, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 8, 0, { 8, 0, 0 } }, Found }, // ideal tile, not ready + RetainTileDataAction{ { 8, 0, { 8, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 9, 0, { 9, 0, 0 } }, NotFound }, // child tile + GetTileDataAction{ { 9, 0, { 9, 0, 1 } }, NotFound }, // ... + GetTileDataAction{ { 9, 0, { 9, 1, 0 } }, NotFound }, // ... + GetTileDataAction{ { 9, 0, { 9, 1, 1 } }, NotFound }, // ... + GetTileDataAction{ { 7, 0, { 7, 0, 0 } }, NotFound }, // ascent + GetTileDataAction{ { 6, 0, { 6, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 5, 0, { 5, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 4, 0, { 4, 0, 0 } }, Found }, // stops ascent + RetainTileDataAction{ { 4, 0, { 4, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 4, 0, 0 }, *tile_4_0_0_0 }, // - GetTileDataAction{ { 8, { 8, 1, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 8, { 8, 1, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 9, { 9, 2, 0 } }, NotFound }, // child tile - GetTileDataAction{ { 9, { 9, 2, 1 } }, NotFound }, // ... - GetTileDataAction{ { 9, { 9, 3, 0 } }, NotFound }, // ... - GetTileDataAction{ { 9, { 9, 3, 1 } }, NotFound }, // ... + GetTileDataAction{ { 8, 0, { 8, 1, 0 } }, Found }, // ideal tile, not ready + RetainTileDataAction{ { 8, 0, { 8, 1, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 9, 0, { 9, 2, 0 } }, NotFound }, // child tile + GetTileDataAction{ { 9, 0, { 9, 2, 1 } }, NotFound }, // ... + GetTileDataAction{ { 9, 0, { 9, 3, 0 } }, NotFound }, // ... + GetTileDataAction{ { 9, 0, { 9, 3, 1 } }, NotFound }, // ... // no second ascent to 0 }), log); @@ -960,15 +962,15 @@ TEST(UpdateRenderables, DontRetainUnusedNonIdealTiles) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 2); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // - GetTileDataAction{ { 3, { 3, 0, 1 } }, NotFound }, // - GetTileDataAction{ { 3, { 3, 1, 0 } }, NotFound }, // - GetTileDataAction{ { 3, { 3, 1, 1 } }, NotFound }, // - GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // parent tile, not ready - RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Optional }, // - GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // ideal tile, not ready + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // + GetTileDataAction{ { 3, 0, { 3, 0, 1 } }, NotFound }, // + GetTileDataAction{ { 3, 0, { 3, 1, 0 } }, NotFound }, // + GetTileDataAction{ { 3, 0, { 3, 1, 1 } }, NotFound }, // + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // parent tile, not ready + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // }), log); } @@ -981,56 +983,54 @@ TEST(UpdateRenderables, WrappedTiles) { auto retainTileData = retainTileDataFn(log); auto renderTile = renderTileFn(log); - source.idealTiles.emplace(UnwrappedTileID{ 1, -1, 0 }); - source.idealTiles.emplace(UnwrappedTileID{ 1, 0, 0 }); - source.idealTiles.emplace(UnwrappedTileID{ 1, 1, 0 }); - source.idealTiles.emplace(UnwrappedTileID{ 1, 2, 0 }); + source.idealTiles.emplace(UnwrappedTileID{ 1, -1, 0 }); // 'wrap' -> -1 + source.idealTiles.emplace(UnwrappedTileID{ 1, 0, 0 }); // 'wrap' -> 0 + source.idealTiles.emplace(UnwrappedTileID{ 1, 1, 0 }); // 'wrap' -> 0 + source.idealTiles.emplace(UnwrappedTileID{ 1, 2, 0 }); // 'wrap' -> 1 - auto tile_0_0_0_0 = source.createTileData(OverscaledTileID{ 0, { 0, 0, 0 } }); + auto tile_0_0_0_0 = source.createTileData(OverscaledTileID{ 0, 0, { 0, 0, 0 } }); tile_0_0_0_0->renderable = true; algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 1, { 1, 1, 0 } }, NotFound }, // ideal tile 1/-1/0 - CreateTileDataAction{ { 1, { 1, 1, 0 } } }, // - RetainTileDataAction{ { 1, { 1, 1, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 2, { 2, 2, 0 } }, NotFound }, // - GetTileDataAction{ { 2, { 2, 2, 1 } }, NotFound }, // - GetTileDataAction{ { 2, { 2, 3, 0 } }, NotFound }, // - GetTileDataAction{ { 2, { 2, 3, 1 } }, NotFound }, // - GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, // - RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // - RenderTileAction{ { 0, -1, 0 }, *tile_0_0_0_0 }, // - - GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ideal tile 1/0/0 - CreateTileDataAction{ { 1, { 1, 0, 0 } } }, // - RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // - GetTileDataAction{ { 2, { 2, 0, 1 } }, NotFound }, // - GetTileDataAction{ { 2, { 2, 1, 0 } }, NotFound }, // - GetTileDataAction{ { 2, { 2, 1, 1 } }, NotFound }, // - GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, // - RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 1, -1, { 1, 1, 0 } }, NotFound }, // ideal tile 1/-1/0 (wrapped to -1) + CreateTileDataAction{ { 1, -1, { 1, 1, 0 } } }, // + RetainTileDataAction{ { 1, -1, { 1, 1, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 2, -1, { 2, 2, 0 } }, NotFound }, // + GetTileDataAction{ { 2, -1, { 2, 2, 1 } }, NotFound }, // + GetTileDataAction{ { 2, -1, { 2, 3, 0 } }, NotFound }, // + GetTileDataAction{ { 2, -1, { 2, 3, 1 } }, NotFound }, // + GetTileDataAction{ { 0, -1, { 0, 0, 0 } }, NotFound }, // { 0, 0, 0 } exists, but not the version wrapped to -1 + + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // ideal tile 1/0/0 + CreateTileDataAction{ { 1, 0, { 1, 0, 0 } } }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // + GetTileDataAction{ { 2, 0, { 2, 0, 1 } }, NotFound }, // + GetTileDataAction{ { 2, 0, { 2, 1, 0 } }, NotFound }, // + GetTileDataAction{ { 2, 0, { 2, 1, 1 } }, NotFound }, // + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, Found }, // + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // - GetTileDataAction{ { 1, { 1, 1, 0 } }, Found }, // ideal tile 1/1/0 - RetainTileDataAction{ { 1, { 1, 1, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 2, { 2, 2, 0 } }, NotFound }, // - GetTileDataAction{ { 2, { 2, 2, 1 } }, NotFound }, // - GetTileDataAction{ { 2, { 2, 3, 0 } }, NotFound }, // - GetTileDataAction{ { 2, { 2, 3, 1 } }, NotFound }, // + GetTileDataAction{ { 1, 0, { 1, 1, 0 } }, NotFound }, // ideal tile 1/1/0, doesn't match 1/-/1/0 + CreateTileDataAction{ { 1, 0, { 1, 1, 0 } } }, + RetainTileDataAction{ { 1, 0, { 1, 1, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 2, 0, { 2, 2, 0 } }, NotFound }, // + GetTileDataAction{ { 2, 0, { 2, 2, 1 } }, NotFound }, // + GetTileDataAction{ { 2, 0, { 2, 3, 0 } }, NotFound }, // + GetTileDataAction{ { 2, 0, { 2, 3, 1 } }, NotFound }, // // do not ascent; 0/0/0 has been rendered already for 1/0/0 - GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // ideal tile 1/2/0 - RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // - GetTileDataAction{ { 2, { 2, 0, 1 } }, NotFound }, // - GetTileDataAction{ { 2, { 2, 1, 0 } }, NotFound }, // - GetTileDataAction{ { 2, { 2, 1, 1 } }, NotFound }, // - GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, // - RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // - RenderTileAction{ { 0, 1, 0 }, *tile_0_0_0_0 }, // + GetTileDataAction{ { 1, 1, { 1, 0, 0 } }, NotFound }, // ideal tile 1/2/0 (wrapped to 1) + CreateTileDataAction{ { 1, 1, { 1, 0, 0 } } }, + RetainTileDataAction{ { 1, 1, { 1, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 2, 1, { 2, 0, 0 } }, NotFound }, // + GetTileDataAction{ { 2, 1, { 2, 0, 1 } }, NotFound }, // + GetTileDataAction{ { 2, 1, { 2, 1, 0 } }, NotFound }, // + GetTileDataAction{ { 2, 1, { 2, 1, 1 } }, NotFound }, // + GetTileDataAction{ { 0, 1, { 0, 0, 0 } }, NotFound }, // { 0, 0, 0 } exists, but not the version wrapped to -1 }), log); } @@ -1048,19 +1048,19 @@ TEST(UpdateRenderables, RepeatedRenderWithMissingOptionals) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 6); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 6, { 6, 0, 0 } }, NotFound }, // ideal tile, not found - CreateTileDataAction{ { 6, { 6, 0, 0 } } }, // - RetainTileDataAction{ { 6, { 6, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 7, { 7, 0, 0 } }, NotFound }, // children - GetTileDataAction{ { 7, { 7, 0, 1 } }, NotFound }, // ... - GetTileDataAction{ { 7, { 7, 1, 0 } }, NotFound }, // ... - GetTileDataAction{ { 7, { 7, 1, 1 } }, NotFound }, // ... - GetTileDataAction{ { 5, { 5, 0, 0 } }, NotFound }, // ascent - GetTileDataAction{ { 4, { 4, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 6, 0, { 6, 0, 0 } }, NotFound }, // ideal tile, not found + CreateTileDataAction{ { 6, 0, { 6, 0, 0 } } }, // + RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 7, 0, { 7, 0, 0 } }, NotFound }, // children + GetTileDataAction{ { 7, 0, { 7, 0, 1 } }, NotFound }, // ... + GetTileDataAction{ { 7, 0, { 7, 1, 0 } }, NotFound }, // ... + GetTileDataAction{ { 7, 0, { 7, 1, 1 } }, NotFound }, // ... + GetTileDataAction{ { 5, 0, { 5, 0, 0 } }, NotFound }, // ascent + GetTileDataAction{ { 4, 0, { 4, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // ... }), log); @@ -1069,41 +1069,41 @@ TEST(UpdateRenderables, RepeatedRenderWithMissingOptionals) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 6); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 6, { 6, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 6, { 6, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 7, { 7, 0, 0 } }, NotFound }, // children - GetTileDataAction{ { 7, { 7, 0, 1 } }, NotFound }, // ... - GetTileDataAction{ { 7, { 7, 1, 0 } }, NotFound }, // ... - GetTileDataAction{ { 7, { 7, 1, 1 } }, NotFound }, // ... - GetTileDataAction{ { 5, { 5, 0, 0 } }, NotFound }, // ascent - GetTileDataAction{ { 4, { 4, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 6, 0, { 6, 0, 0 } }, Found }, // ideal tile, not ready + RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 7, 0, { 7, 0, 0 } }, NotFound }, // children + GetTileDataAction{ { 7, 0, { 7, 0, 1 } }, NotFound }, // ... + GetTileDataAction{ { 7, 0, { 7, 1, 0 } }, NotFound }, // ... + GetTileDataAction{ { 7, 0, { 7, 1, 1 } }, NotFound }, // ... + GetTileDataAction{ { 5, 0, { 5, 0, 0 } }, NotFound }, // ascent + GetTileDataAction{ { 4, 0, { 4, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // ... }), log); // Mark next level has having tried optional. log.clear(); - source.dataTiles[{ 6, { 6, 0, 0 } }]->triedOptional = true; + source.dataTiles[{ 6, 0, { 6, 0, 0 } }]->triedOptional = true; algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 6); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 6, { 6, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 6, { 6, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 7, { 7, 0, 0 } }, NotFound }, // children - GetTileDataAction{ { 7, { 7, 0, 1 } }, NotFound }, // ... - GetTileDataAction{ { 7, { 7, 1, 0 } }, NotFound }, // ... - GetTileDataAction{ { 7, { 7, 1, 1 } }, NotFound }, // ... - GetTileDataAction{ { 5, { 5, 0, 0 } }, NotFound }, // ascent - CreateTileDataAction{ { 5, { 5, 0, 0 } } }, // - RetainTileDataAction{ { 5, { 5, 0, 0 } }, Resource::Necessity::Optional }, // - GetTileDataAction{ { 4, { 4, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 6, 0, { 6, 0, 0 } }, Found }, // ideal tile, not ready + RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 7, 0, { 7, 0, 0 } }, NotFound }, // children + GetTileDataAction{ { 7, 0, { 7, 0, 1 } }, NotFound }, // ... + GetTileDataAction{ { 7, 0, { 7, 1, 0 } }, NotFound }, // ... + GetTileDataAction{ { 7, 0, { 7, 1, 1 } }, NotFound }, // ... + GetTileDataAction{ { 5, 0, { 5, 0, 0 } }, NotFound }, // ascent + CreateTileDataAction{ { 5, 0, { 5, 0, 0 } } }, // + RetainTileDataAction{ { 5, 0, { 5, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 4, 0, { 4, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // ... }), log); @@ -1112,116 +1112,116 @@ TEST(UpdateRenderables, RepeatedRenderWithMissingOptionals) { algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 6); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 6, { 6, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 6, { 6, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 7, { 7, 0, 0 } }, NotFound }, // children - GetTileDataAction{ { 7, { 7, 0, 1 } }, NotFound }, // ... - GetTileDataAction{ { 7, { 7, 1, 0 } }, NotFound }, // ... - GetTileDataAction{ { 7, { 7, 1, 1 } }, NotFound }, // ... - GetTileDataAction{ { 5, { 5, 0, 0 } }, Found }, // ascent - RetainTileDataAction{ { 5, { 5, 0, 0 } }, Resource::Necessity::Optional }, // - GetTileDataAction{ { 4, { 4, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 6, 0, { 6, 0, 0 } }, Found }, // ideal tile, not ready + RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 7, 0, { 7, 0, 0 } }, NotFound }, // children + GetTileDataAction{ { 7, 0, { 7, 0, 1 } }, NotFound }, // ... + GetTileDataAction{ { 7, 0, { 7, 1, 0 } }, NotFound }, // ... + GetTileDataAction{ { 7, 0, { 7, 1, 1 } }, NotFound }, // ... + GetTileDataAction{ { 5, 0, { 5, 0, 0 } }, Found }, // ascent + RetainTileDataAction{ { 5, 0, { 5, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 4, 0, { 4, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // ... }), log); // Mark next level has having tried optional. log.clear(); - source.dataTiles[{ 5, { 5, 0, 0 } }]->triedOptional = true; + source.dataTiles[{ 5, 0, { 5, 0, 0 } }]->triedOptional = true; algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 6); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 6, { 6, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 6, { 6, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 7, { 7, 0, 0 } }, NotFound }, // children - GetTileDataAction{ { 7, { 7, 0, 1 } }, NotFound }, // ... - GetTileDataAction{ { 7, { 7, 1, 0 } }, NotFound }, // ... - GetTileDataAction{ { 7, { 7, 1, 1 } }, NotFound }, // ... - GetTileDataAction{ { 5, { 5, 0, 0 } }, Found }, // ascent - RetainTileDataAction{ { 5, { 5, 0, 0 } }, Resource::Necessity::Optional }, // - GetTileDataAction{ { 4, { 4, 0, 0 } }, NotFound }, // ... - CreateTileDataAction{ { 4, { 4, 0, 0 } } }, // - RetainTileDataAction{ { 4, { 4, 0, 0 } }, Resource::Necessity::Optional }, // - GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 6, 0, { 6, 0, 0 } }, Found }, // ideal tile, not ready + RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 7, 0, { 7, 0, 0 } }, NotFound }, // children + GetTileDataAction{ { 7, 0, { 7, 0, 1 } }, NotFound }, // ... + GetTileDataAction{ { 7, 0, { 7, 1, 0 } }, NotFound }, // ... + GetTileDataAction{ { 7, 0, { 7, 1, 1 } }, NotFound }, // ... + GetTileDataAction{ { 5, 0, { 5, 0, 0 } }, Found }, // ascent + RetainTileDataAction{ { 5, 0, { 5, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 4, 0, { 4, 0, 0 } }, NotFound }, // ... + CreateTileDataAction{ { 4, 0, { 4, 0, 0 } } }, // + RetainTileDataAction{ { 4, 0, { 4, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // ... }), log); // Mark next level has having tried optional. log.clear(); - source.dataTiles[{ 4, { 4, 0, 0 } }]->triedOptional = true; + source.dataTiles[{ 4, 0, { 4, 0, 0 } }]->triedOptional = true; algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 6); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 6, { 6, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 6, { 6, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 7, { 7, 0, 0 } }, NotFound }, // children - GetTileDataAction{ { 7, { 7, 0, 1 } }, NotFound }, // ... - GetTileDataAction{ { 7, { 7, 1, 0 } }, NotFound }, // ... - GetTileDataAction{ { 7, { 7, 1, 1 } }, NotFound }, // ... - GetTileDataAction{ { 5, { 5, 0, 0 } }, Found }, // ascent - RetainTileDataAction{ { 5, { 5, 0, 0 } }, Resource::Necessity::Optional }, // - GetTileDataAction{ { 4, { 4, 0, 0 } }, Found }, // ... - RetainTileDataAction{ { 4, { 4, 0, 0 } }, Resource::Necessity::Optional }, // - GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // ... - CreateTileDataAction{ { 3, { 3, 0, 0 } } }, // - RetainTileDataAction{ { 3, { 3, 0, 0 } }, Resource::Necessity::Optional }, // - GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 6, 0, { 6, 0, 0 } }, Found }, // ideal tile, not ready + RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 7, 0, { 7, 0, 0 } }, NotFound }, // children + GetTileDataAction{ { 7, 0, { 7, 0, 1 } }, NotFound }, // ... + GetTileDataAction{ { 7, 0, { 7, 1, 0 } }, NotFound }, // ... + GetTileDataAction{ { 7, 0, { 7, 1, 1 } }, NotFound }, // ... + GetTileDataAction{ { 5, 0, { 5, 0, 0 } }, Found }, // ascent + RetainTileDataAction{ { 5, 0, { 5, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 4, 0, { 4, 0, 0 } }, Found }, // ... + RetainTileDataAction{ { 4, 0, { 4, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // ... + CreateTileDataAction{ { 3, 0, { 3, 0, 0 } } }, // + RetainTileDataAction{ { 3, 0, { 3, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // ... }), log); // Mark next level has having tried optional. log.clear(); - source.dataTiles[{ 3, { 3, 0, 0 } }]->triedOptional = true; + source.dataTiles[{ 3, 0, { 3, 0, 0 } }]->triedOptional = true; algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 6); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 6, { 6, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 6, { 6, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 7, { 7, 0, 0 } }, NotFound }, // children - GetTileDataAction{ { 7, { 7, 0, 1 } }, NotFound }, // ... - GetTileDataAction{ { 7, { 7, 1, 0 } }, NotFound }, // ... - GetTileDataAction{ { 7, { 7, 1, 1 } }, NotFound }, // ... - GetTileDataAction{ { 5, { 5, 0, 0 } }, Found }, // ascent - RetainTileDataAction{ { 5, { 5, 0, 0 } }, Resource::Necessity::Optional }, // - GetTileDataAction{ { 4, { 4, 0, 0 } }, Found }, // ... - RetainTileDataAction{ { 4, { 4, 0, 0 } }, Resource::Necessity::Optional }, // - GetTileDataAction{ { 3, { 3, 0, 0 } }, Found }, // ... - RetainTileDataAction{ { 3, { 3, 0, 0 } }, Resource::Necessity::Optional }, // - GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // ... - CreateTileDataAction{ { 2, { 2, 0, 0 } } }, // - RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Optional }, // - GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 6, 0, { 6, 0, 0 } }, Found }, // ideal tile, not ready + RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 7, 0, { 7, 0, 0 } }, NotFound }, // children + GetTileDataAction{ { 7, 0, { 7, 0, 1 } }, NotFound }, // ... + GetTileDataAction{ { 7, 0, { 7, 1, 0 } }, NotFound }, // ... + GetTileDataAction{ { 7, 0, { 7, 1, 1 } }, NotFound }, // ... + GetTileDataAction{ { 5, 0, { 5, 0, 0 } }, Found }, // ascent + RetainTileDataAction{ { 5, 0, { 5, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 4, 0, { 4, 0, 0 } }, Found }, // ... + RetainTileDataAction{ { 4, 0, { 4, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, Found }, // ... + RetainTileDataAction{ { 3, 0, { 3, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // ... + CreateTileDataAction{ { 2, 0, { 2, 0, 0 } } }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // ... }), log); // Mark as found log.clear(); - auto tile_3_3_0_0 = source.dataTiles[{ 3, { 3, 0, 0 } }].get(); + auto tile_3_3_0_0 = source.dataTiles[{ 3, 0, { 3, 0, 0 } }].get(); tile_3_3_0_0->renderable = true; algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 6); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 6, { 6, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 6, { 6, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 7, { 7, 0, 0 } }, NotFound }, // children - GetTileDataAction{ { 7, { 7, 0, 1 } }, NotFound }, // ... - GetTileDataAction{ { 7, { 7, 1, 0 } }, NotFound }, // ... - GetTileDataAction{ { 7, { 7, 1, 1 } }, NotFound }, // ... - GetTileDataAction{ { 5, { 5, 0, 0 } }, Found }, // ascent - RetainTileDataAction{ { 5, { 5, 0, 0 } }, Resource::Necessity::Optional }, // - GetTileDataAction{ { 4, { 4, 0, 0 } }, Found }, // ... - RetainTileDataAction{ { 4, { 4, 0, 0 } }, Resource::Necessity::Optional }, // - GetTileDataAction{ { 3, { 3, 0, 0 } }, Found }, // ... - RetainTileDataAction{ { 3, { 3, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 6, 0, { 6, 0, 0 } }, Found }, // ideal tile, not ready + RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 7, 0, { 7, 0, 0 } }, NotFound }, // children + GetTileDataAction{ { 7, 0, { 7, 0, 1 } }, NotFound }, // ... + GetTileDataAction{ { 7, 0, { 7, 1, 0 } }, NotFound }, // ... + GetTileDataAction{ { 7, 0, { 7, 1, 1 } }, NotFound }, // ... + GetTileDataAction{ { 5, 0, { 5, 0, 0 } }, Found }, // ascent + RetainTileDataAction{ { 5, 0, { 5, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 4, 0, { 4, 0, 0 } }, Found }, // ... + RetainTileDataAction{ { 4, 0, { 4, 0, 0 } }, Resource::Necessity::Optional }, // + GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, Found }, // ... + RetainTileDataAction{ { 3, 0, { 3, 0, 0 } }, Resource::Necessity::Optional }, // RenderTileAction{ { 3, 0, 0 }, *tile_3_3_0_0 }, // }), log); @@ -1238,24 +1238,24 @@ TEST(UpdateRenderables, LoadRequiredIfIdealTileCantBeFound) { source.zoomRange.max = 6; source.idealTiles.emplace(UnwrappedTileID{ 6, 0, 0 }); - auto tile_6_6_0_0 = source.createTileData(OverscaledTileID{ 6, { 6, 0, 0 } }); + auto tile_6_6_0_0 = source.createTileData(OverscaledTileID{ 6, 0, { 6, 0, 0 } }); tile_6_6_0_0->triedOptional = true; tile_6_6_0_0->loaded = true; algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, source.idealTiles, source.zoomRange, 6); EXPECT_EQ(ActionLog({ - GetTileDataAction{ { 6, { 6, 0, 0 } }, Found }, // ideal tile, not found - RetainTileDataAction{ { 6, { 6, 0, 0 } }, Resource::Necessity::Required }, // - GetTileDataAction{ { 7, { 6, 0, 0 } }, NotFound }, // overzoomed child - GetTileDataAction{ { 5, { 5, 0, 0 } }, NotFound }, // ascent - CreateTileDataAction{ { 5, { 5, 0, 0 } } }, - RetainTileDataAction{ { 5, { 5, 0, 0 } }, Resource::Necessity::Required }, - GetTileDataAction{ { 4, { 4, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ... - GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 6, 0, { 6, 0, 0 } }, Found }, // ideal tile, not found + RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, Resource::Necessity::Required }, // + GetTileDataAction{ { 7, 0, { 6, 0, 0 } }, NotFound }, // overzoomed child + GetTileDataAction{ { 5, 0, { 5, 0, 0 } }, NotFound }, // ascent + CreateTileDataAction{ { 5, 0, { 5, 0, 0 } } }, + RetainTileDataAction{ { 5, 0, { 5, 0, 0 } }, Resource::Necessity::Required }, + GetTileDataAction{ { 4, 0, { 4, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // ... + GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // ... }), log); } diff --git a/test/algorithm/update_tile_masks.test.cpp b/test/algorithm/update_tile_masks.test.cpp new file mode 100644 index 0000000000..3c698eb0cd --- /dev/null +++ b/test/algorithm/update_tile_masks.test.cpp @@ -0,0 +1,132 @@ +#include <mbgl/test/util.hpp> + +#include <mbgl/algorithm/update_tile_masks.hpp> + +using namespace mbgl; + +namespace { + +class MaskedRenderable { +public: + MaskedRenderable(const UnwrappedTileID& id_, TileMask&& mask_) + : id(id_), mask(std::move(mask_)) { + } + + UnwrappedTileID id; + TileMask mask; + bool used = true; + + void setMask(TileMask&& mask_) { + mask = std::move(mask_); + } +}; + +bool operator==(const MaskedRenderable& lhs, const MaskedRenderable& rhs) { + return lhs.id == rhs.id && lhs.mask == rhs.mask; +} + +::std::ostream& operator<<(::std::ostream& os, const MaskedRenderable& rhs) { + os << "MaskedRenderable{ " << rhs.id << ", { "; + bool first = true; + for (auto& id : rhs.mask) { + if (!first) { + os << ", "; + } else { + first = false; + } + os << id; + } + return os << " } }"; +} + +} // namespace + +void validate(const std::vector<MaskedRenderable> expected) { + std::vector<MaskedRenderable> actual = expected; + std::for_each(actual.begin(), actual.end(), + [](auto& renderable) { renderable.mask.clear(); }); + algorithm::updateTileMasks<MaskedRenderable>({ actual.begin(), actual.end() }); + EXPECT_EQ(expected, actual); +} + +TEST(UpdateTileMasks, NoChildren) { + validate({ + MaskedRenderable{ UnwrappedTileID{ 0, 0, 0 }, { CanonicalTileID{ 0, 0, 0 } } }, + }); + + validate({ + MaskedRenderable{ UnwrappedTileID{ 4, 3, 8 }, { CanonicalTileID{ 0, 0, 0 } } }, + }); + + validate({ + MaskedRenderable{ UnwrappedTileID{ 1, 0, 0 }, { CanonicalTileID{ 0, 0, 0 } } }, + MaskedRenderable{ UnwrappedTileID{ 1, 1, 1 }, { CanonicalTileID{ 0, 0, 0 } } }, + }); + + validate({ + MaskedRenderable{ UnwrappedTileID{ 1, 0, 0 }, { CanonicalTileID{ 0, 0, 0 } } }, + MaskedRenderable{ UnwrappedTileID{ 2, 2, 3 }, { CanonicalTileID{ 0, 0, 0 } } }, + }); +} + +TEST(UpdateTileMasks, ParentAndFourChildren) { + validate({ + // Mask is empty (== not rendered!) because we have four covering children. + MaskedRenderable{ UnwrappedTileID{ 0, 0, 0 }, {} }, + // All four covering children + MaskedRenderable{ UnwrappedTileID{ 1, 0, 0 }, { CanonicalTileID{ 0, 0, 0 } } }, + MaskedRenderable{ UnwrappedTileID{ 1, 0, 1 }, { CanonicalTileID{ 0, 0, 0 } } }, + MaskedRenderable{ UnwrappedTileID{ 1, 1, 0 }, { CanonicalTileID{ 0, 0, 0 } } }, + MaskedRenderable{ UnwrappedTileID{ 1, 1, 1 }, { CanonicalTileID{ 0, 0, 0 } } }, + }); +} + +TEST(UpdateTileMasks, OneChild) { + validate({ + MaskedRenderable{ UnwrappedTileID{ 0, 0, 0 }, + // Only render the three children that aren't covering the other tile. + { CanonicalTileID{ 1, 0, 1 }, CanonicalTileID{ 1, 1, 0 }, + CanonicalTileID{ 1, 1, 1 } } }, + MaskedRenderable{ UnwrappedTileID{ 1, 0, 0 }, { CanonicalTileID{ 0, 0, 0 } } }, + }); +} + +TEST(UpdateTileMasks, Complex) { + validate({ + MaskedRenderable{ UnwrappedTileID{ 0, 0, 0 }, + { CanonicalTileID{ 1, 0, 1 }, CanonicalTileID{ 1, 1, 0 }, + CanonicalTileID{ 2, 2, 3 }, CanonicalTileID{ 2, 3, 2 }, + CanonicalTileID{ 3, 6, 7 }, CanonicalTileID{ 3, 7, 6 } } }, + MaskedRenderable{ UnwrappedTileID{ 0, { 1, 0, 0 } }, { CanonicalTileID{ 0, 0, 0 } } }, + MaskedRenderable{ UnwrappedTileID{ 0, { 2, 2, 2 } }, { CanonicalTileID{ 0, 0, 0 } } }, + MaskedRenderable{ UnwrappedTileID{ 0, { 3, 7, 7 } }, { CanonicalTileID{ 0, 0, 0 } } }, + MaskedRenderable{ UnwrappedTileID{ 0, { 3, 6, 6 } }, { CanonicalTileID{ 0, 0, 0 } } }, + }); + + validate({ + MaskedRenderable{ UnwrappedTileID{ 0, 0, 0 }, + { CanonicalTileID{ 1, 0, 1 }, CanonicalTileID{ 1, 1, 0 }, + CanonicalTileID{ 1, 1, 1 }, CanonicalTileID{ 2, 0, 0 }, + CanonicalTileID{ 2, 0, 1 }, CanonicalTileID{ 2, 1, 0 }, + CanonicalTileID{ 3, 2, 3 }, CanonicalTileID{ 3, 3, 2 }, + CanonicalTileID{ 3, 3, 3 }, CanonicalTileID{ 4, 4, 5 }, + CanonicalTileID{ 4, 5, 4 }, CanonicalTileID{ 4, 5, 5 } } }, + MaskedRenderable{ UnwrappedTileID{ 4, 4, 4 }, { CanonicalTileID{ 0, 0, 0 } } }, + }); + + validate({ + MaskedRenderable{ UnwrappedTileID{ 12, 1028, 1456 }, + { CanonicalTileID{ 1, 1, 1 }, CanonicalTileID{ 2, 3, 0 }, + CanonicalTileID{ 2, 3, 1 } } }, + MaskedRenderable{ UnwrappedTileID{ 13, 2056, 2912 }, + { CanonicalTileID{ 1, 0, 1 }, CanonicalTileID{ 1, 1, 0 }, + CanonicalTileID{ 1, 1, 1 } } }, + MaskedRenderable{ UnwrappedTileID{ 13, 2056, 2913 }, + { CanonicalTileID{ 1, 0, 0 }, CanonicalTileID{ 1, 1, 0 }, + CanonicalTileID{ 1, 1, 1 } } }, + MaskedRenderable{ UnwrappedTileID{ 14, 4112, 5824 }, { CanonicalTileID{ 0, 0, 0 } } }, + MaskedRenderable{ UnwrappedTileID{ 14, 4112, 5827 }, { CanonicalTileID{ 0, 0, 0 } } }, + MaskedRenderable{ UnwrappedTileID{ 14, 4114, 5824 }, { CanonicalTileID{ 0, 0, 0 } } }, + MaskedRenderable{ UnwrappedTileID{ 14, 4114, 5825 }, { CanonicalTileID{ 0, 0, 0 } } }, + }); +} diff --git a/test/api/annotations.test.cpp b/test/api/annotations.test.cpp index 97ccaae684..9e622f780a 100644 --- a/test/api/annotations.test.cpp +++ b/test/api/annotations.test.cpp @@ -3,37 +3,40 @@ #include <mbgl/util/default_thread_pool.hpp> #include <mbgl/annotation/annotation.hpp> +#include <mbgl/style/style.hpp> #include <mbgl/style/image.hpp> #include <mbgl/map/map.hpp> -#include <mbgl/map/backend_scope.hpp> -#include <mbgl/gl/headless_backend.hpp> -#include <mbgl/gl/offscreen_view.hpp> #include <mbgl/util/io.hpp> #include <mbgl/util/run_loop.hpp> #include <mbgl/util/color.hpp> +#include <mbgl/renderer/renderer.hpp> +#include <mbgl/gl/headless_frontend.hpp> using namespace mbgl; namespace { +PremultipliedImage namedImage(const std::string& name) { + return decodeImage(util::read_file("test/fixtures/sprites/" + name + ".png")); +} + std::unique_ptr<style::Image> namedMarker(const std::string& name) { - PremultipliedImage image = decodeImage(util::read_file("test/fixtures/sprites/" + name)); - return std::make_unique<style::Image>(std::move(image), 1.0); + return std::make_unique<style::Image>(name, namedImage(name), 1.0); } class AnnotationTest { public: util::RunLoop loop; - HeadlessBackend backend { test::sharedDisplay() }; - BackendScope scope { backend }; - OffscreenView view { backend.getContext() }; StubFileSource fileSource; ThreadPool threadPool { 4 }; - Map map { backend, view.getSize(), 1, fileSource, threadPool, MapMode::Still }; + float pixelRatio { 1 }; + HeadlessFrontend frontend { pixelRatio, fileSource, threadPool }; + Map map { frontend, MapObserver::nullObserver(), frontend.getSize(), pixelRatio, fileSource, + threadPool, MapMode::Still }; void checkRendering(const char * name) { test::checkImage(std::string("test/fixtures/annotations/") + name, - test::render(map, view), 0.0002, 0.1); + frontend.render(map), 0.0002, 0.1); } }; @@ -42,12 +45,12 @@ public: TEST(Annotations, SymbolAnnotation) { AnnotationTest test; - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); - test.map.addAnnotationImage("default_marker", namedMarker("default_marker.png")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.addAnnotationImage(namedMarker("default_marker")); test.map.addAnnotation(SymbolAnnotation { Point<double>(0, 0), "default_marker" }); test.checkRendering("point_annotation"); -// auto size = test.view.getSize(); +// auto size = test.frontend.getSize(); // auto screenBox = ScreenBox { {}, { double(size.width), double(size.height) } }; // for (uint8_t zoom = test.map.getMinZoom(); zoom <= test.map.getMaxZoom(); ++zoom) { // test.map.setZoom(zoom); @@ -64,7 +67,7 @@ TEST(Annotations, LineAnnotation) { annotation.color = Color::red(); annotation.width = { 5 }; - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); test.map.addAnnotation(annotation); test.checkRendering("line_annotation"); @@ -79,7 +82,7 @@ TEST(Annotations, FillAnnotation) { FillAnnotation annotation { polygon }; annotation.color = Color::red(); - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); test.map.addAnnotation(annotation); test.checkRendering("fill_annotation"); @@ -92,7 +95,7 @@ TEST(Annotations, AntimeridianAnnotationSmall) { double antimeridian = 180; test.map.setLatLngZoom(mbgl::LatLng(0, antimeridian), 0); - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); LineString<double> line = {{ { antimeridian, 20 }, { antimeridian, -20 } }}; LineAnnotation lineAnnotation { line }; @@ -113,7 +116,7 @@ TEST(Annotations, AntimeridianAnnotationLarge) { double antimeridian = 180; test.map.setLatLngZoom(mbgl::LatLng(0, antimeridian), 0); - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); LineString<double> line = {{ { antimeridian, 20 }, { antimeridian, -20 } }}; LineAnnotation lineAnnotation { line }; @@ -138,30 +141,20 @@ TEST(Annotations, OverlappingFillAnnotation) { FillAnnotation overlaidAnnotation { polygon }; overlaidAnnotation.color = Color::red(); - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); test.map.addAnnotation(underlaidAnnotation); test.map.addAnnotation(overlaidAnnotation); test.checkRendering("overlapping_fill_annotation"); } -TEST(Annotations, StyleSourcedShapeAnnotation) { - AnnotationTest test; - - Polygon<double> polygon = { {{ { 0, 0 }, { 0, 45 }, { 45, 45 }, { 45, 0 } }} }; - - test.map.setStyleJSON(util::read_file("test/fixtures/api/annotation.json")); - test.map.addAnnotation(StyleSourcedAnnotation { polygon, "annotation" }); - test.checkRendering("style_sourced_shape_annotation"); -} - TEST(Annotations, AddMultiple) { AnnotationTest test; - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); - test.map.addAnnotationImage("default_marker", namedMarker("default_marker.png")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.addAnnotationImage(namedMarker("default_marker")); test.map.addAnnotation(SymbolAnnotation { Point<double> { -10, 0 }, "default_marker" }); - test::render(test.map, test.view); + test.frontend.render(test.map); test.map.addAnnotation(SymbolAnnotation { Point<double> { 10, 0 }, "default_marker" }); test.checkRendering("add_multiple"); @@ -170,8 +163,8 @@ TEST(Annotations, AddMultiple) { TEST(Annotations, NonImmediateAdd) { AnnotationTest test; - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); - test::render(test.map, test.view); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); + test.frontend.render(test.map); Polygon<double> polygon = { {{ { 0, 0 }, { 0, 45 }, { 45, 45 }, { 45, 0 } }} }; FillAnnotation annotation { polygon }; @@ -184,12 +177,12 @@ TEST(Annotations, NonImmediateAdd) { TEST(Annotations, UpdateSymbolAnnotationGeometry) { AnnotationTest test; - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); - test.map.addAnnotationImage("default_marker", namedMarker("default_marker.png")); - test.map.addAnnotationImage("flipped_marker", namedMarker("flipped_marker.png")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.addAnnotationImage(namedMarker("default_marker")); + test.map.addAnnotationImage(namedMarker("flipped_marker")); AnnotationID point = test.map.addAnnotation(SymbolAnnotation { Point<double> { 0, 0 }, "default_marker" }); - test::render(test.map, test.view); + test.frontend.render(test.map); test.map.updateAnnotation(point, SymbolAnnotation { Point<double> { -10, 0 }, "default_marker" }); test.checkRendering("update_point"); @@ -198,12 +191,12 @@ TEST(Annotations, UpdateSymbolAnnotationGeometry) { TEST(Annotations, UpdateSymbolAnnotationIcon) { AnnotationTest test; - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); - test.map.addAnnotationImage("default_marker", namedMarker("default_marker.png")); - test.map.addAnnotationImage("flipped_marker", namedMarker("flipped_marker.png")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.addAnnotationImage(namedMarker("default_marker")); + test.map.addAnnotationImage(namedMarker("flipped_marker")); AnnotationID point = test.map.addAnnotation(SymbolAnnotation { Point<double> { 0, 0 }, "default_marker" }); - test::render(test.map, test.view); + test.frontend.render(test.map); test.map.updateAnnotation(point, SymbolAnnotation { Point<double> { 0, 0 }, "flipped_marker" }); test.checkRendering("update_icon"); @@ -216,10 +209,10 @@ TEST(Annotations, UpdateLineAnnotationGeometry) { annotation.color = Color::red(); annotation.width = { 5 }; - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); AnnotationID line = test.map.addAnnotation(annotation); - test::render(test.map, test.view); + test.frontend.render(test.map); annotation.geometry = LineString<double> {{ { 0, 0 }, { -45, -45 } }}; test.map.updateAnnotation(line, annotation); @@ -233,10 +226,10 @@ TEST(Annotations, UpdateLineAnnotationStyle) { annotation.color = Color::red(); annotation.width = { 5 }; - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); AnnotationID line = test.map.addAnnotation(annotation); - test::render(test.map, test.view); + test.frontend.render(test.map); annotation.color = Color::green(); annotation.width = { 2 }; @@ -250,10 +243,10 @@ TEST(Annotations, UpdateFillAnnotationGeometry) { FillAnnotation annotation { Polygon<double> { {{ { 0, 0 }, { 0, 45 }, { 45, 45 }, { 45, 0 } }} } }; annotation.color = Color::red(); - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); AnnotationID fill = test.map.addAnnotation(annotation); - test::render(test.map, test.view); + test.frontend.render(test.map); annotation.geometry = Polygon<double> { {{ { 0, 0 }, { 0, 45 }, { 45, 0 } }} }; test.map.updateAnnotation(fill, annotation); @@ -267,10 +260,10 @@ TEST(Annotations, UpdateFillAnnotationStyle) { FillAnnotation annotation { polygon }; annotation.color = Color::red(); - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); AnnotationID fill = test.map.addAnnotation(annotation); - test::render(test.map, test.view); + test.frontend.render(test.map); annotation.color = Color::green(); test.map.updateAnnotation(fill, annotation); @@ -280,11 +273,11 @@ TEST(Annotations, UpdateFillAnnotationStyle) { TEST(Annotations, RemovePoint) { AnnotationTest test; - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); - test.map.addAnnotationImage("default_marker", namedMarker("default_marker.png")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.addAnnotationImage(namedMarker("default_marker")); AnnotationID point = test.map.addAnnotation(SymbolAnnotation { Point<double> { 0, 0 }, "default_marker" }); - test::render(test.map, test.view); + test.frontend.render(test.map); test.map.removeAnnotation(point); test.checkRendering("remove_point"); @@ -298,10 +291,10 @@ TEST(Annotations, RemoveShape) { annotation.color = Color::red(); annotation.width = { 5 }; - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); AnnotationID shape = test.map.addAnnotation(annotation); - test::render(test.map, test.view); + test.frontend.render(test.map); test.map.removeAnnotation(shape); test.checkRendering("remove_shape"); @@ -311,40 +304,53 @@ TEST(Annotations, ImmediateRemoveShape) { AnnotationTest test; test.map.removeAnnotation(test.map.addAnnotation(LineAnnotation { LineString<double>() })); - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); - test::render(test.map, test.view); + test.frontend.render(test.map); } TEST(Annotations, SwitchStyle) { AnnotationTest test; - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); - test.map.addAnnotationImage("default_marker", namedMarker("default_marker.png")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.addAnnotationImage(namedMarker("default_marker")); test.map.addAnnotation(SymbolAnnotation { Point<double> { 0, 0 }, "default_marker" }); - test::render(test.map, test.view); + test.frontend.render(test.map); - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); test.checkRendering("switch_style"); } +TEST(Annotations, ReaddImage) { + AnnotationTest test; + + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.addAnnotationImage(namedMarker("default_marker")); + test.map.addAnnotation(SymbolAnnotation { Point<double> { 0, 0 }, "default_marker" }); + + test.frontend.render(test.map); + + test.map.addAnnotationImage(std::make_unique<style::Image>("default_marker", namedImage("flipped_marker"), 1.0)); + test.checkRendering("readd_image"); +} + TEST(Annotations, QueryRenderedFeatures) { AnnotationTest test; - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); - test.map.addAnnotationImage("default_marker", namedMarker("default_marker.png")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.addAnnotationImage(namedMarker("default_marker")); test.map.addAnnotation(SymbolAnnotation { Point<double> { 0, 0 }, "default_marker" }); test.map.addAnnotation(SymbolAnnotation { Point<double> { 0, 50 }, "default_marker" }); - test::render(test.map, test.view); + test.frontend.render(test.map); - auto features = test.map.queryRenderedFeatures(test.map.pixelForLatLng({ 0, 0 })); + auto features = test.frontend.getRenderer()->queryRenderedFeatures(test.map.pixelForLatLng({ 0, 0 })); EXPECT_EQ(features.size(), 1u); EXPECT_TRUE(!!features[0].id); EXPECT_EQ(*features[0].id, uint64_t(0)); - auto features2 = test.map.queryRenderedFeatures(test.map.pixelForLatLng({ 50, 0 })); + auto features2 = test.frontend.getRenderer()->queryRenderedFeatures(test.map.pixelForLatLng({ 50, 0 })); EXPECT_EQ(features2.size(), 1u); EXPECT_TRUE(!!features2[0].id); EXPECT_EQ(*features2[0].id, uint64_t(1)); @@ -353,11 +359,11 @@ TEST(Annotations, QueryRenderedFeatures) { TEST(Annotations, QueryFractionalZoomLevels) { AnnotationTest test; - auto viewSize = test.view.getSize(); + auto viewSize = test.frontend.getSize(); auto box = ScreenBox { {}, { double(viewSize.width), double(viewSize.height) } }; - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); - test.map.addAnnotationImage("default_marker", namedMarker("default_marker.png")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.addAnnotationImage(namedMarker("default_marker")); std::vector<mbgl::AnnotationID> ids; for (int longitude = 0; longitude < 10; ++longitude) { @@ -369,8 +375,8 @@ TEST(Annotations, QueryFractionalZoomLevels) { test.map.setLatLngZoom({ 5, 5 }, 0); for (uint16_t zoomSteps = 10; zoomSteps <= 20; ++zoomSteps) { test.map.setZoom(zoomSteps / 10.0); - test::render(test.map, test.view); - auto features = test.map.queryRenderedFeatures(box); + test.frontend.render(test.map); + auto features = test.frontend.getRenderer()->queryRenderedFeatures(box); // Filter out repeated features. // See 'edge-cases/null-island' query-test for reference. @@ -385,11 +391,11 @@ TEST(Annotations, QueryFractionalZoomLevels) { TEST(Annotations, VisibleFeatures) { AnnotationTest test; - auto viewSize = test.view.getSize(); + auto viewSize = test.frontend.getSize(); auto box = ScreenBox { {}, { double(viewSize.width), double(viewSize.height) } }; - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); - test.map.addAnnotationImage("default_marker", namedMarker("default_marker.png")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.addAnnotationImage(namedMarker("default_marker")); test.map.setLatLngZoom({ 5, 5 }, 3); std::vector<mbgl::AnnotationID> ids; @@ -401,9 +407,9 @@ TEST(Annotations, VisibleFeatures) { // Change bearing *after* adding annotations causes them to be reordered. test.map.setBearing(45); - test::render(test.map, test.view); + test.frontend.render(test.map); - auto features = test.map.queryRenderedFeatures(box, {}); + auto features = test.frontend.getRenderer()->queryRenderedFeatures(box, {}); auto sortID = [](const Feature& lhs, const Feature& rhs) { return lhs.id < rhs.id; }; auto sameID = [](const Feature& lhs, const Feature& rhs) { return lhs.id == rhs.id; }; std::sort(features.begin(), features.end(), sortID); @@ -412,13 +418,19 @@ TEST(Annotations, VisibleFeatures) { test.map.setBearing(0); test.map.setZoom(4); - test::render(test.map, test.view); - features = test.map.queryRenderedFeatures(box); + test.frontend.render(test.map); + features = test.frontend.getRenderer()->queryRenderedFeatures(box); std::sort(features.begin(), features.end(), sortID); features.erase(std::unique(features.begin(), features.end(), sameID), features.end()); EXPECT_EQ(features.size(), ids.size()); } +TEST(Annotations, TopOffsetPixels) { + AnnotationTest test; + + test.map.addAnnotationImage(namedMarker("default_marker")); + EXPECT_EQ(test.map.getTopOffsetPixelsForAnnotationImage("default_marker"), -28); +} TEST(Annotations, DebugEmpty) { // This test should render nothing, not even the tile borders. Tile borders are only rendered @@ -426,7 +438,7 @@ TEST(Annotations, DebugEmpty) { // should not render them. AnnotationTest test; - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); test.map.setDebug(MapDebugOptions::TileBorders); test.map.setZoom(1); @@ -439,10 +451,10 @@ TEST(Annotations, DebugSparse) { // tiles because they're all empty. AnnotationTest test; - test.map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); test.map.setDebug(MapDebugOptions::TileBorders); test.map.setZoom(1); - test.map.addAnnotationImage("default_marker", namedMarker("default_marker.png")); + test.map.addAnnotationImage(namedMarker("default_marker")); test.map.addAnnotation(SymbolAnnotation { Point<double>(10, 10), "default_marker" }); test.checkRendering("debug_sparse"); diff --git a/test/api/api_misuse.test.cpp b/test/api/api_misuse.test.cpp index af703fddfb..690c1548e5 100644 --- a/test/api/api_misuse.test.cpp +++ b/test/api/api_misuse.test.cpp @@ -3,9 +3,8 @@ #include <mbgl/test/fixture_log_observer.hpp> #include <mbgl/map/map.hpp> -#include <mbgl/map/backend_scope.hpp> -#include <mbgl/gl/headless_backend.hpp> -#include <mbgl/gl/offscreen_view.hpp> +#include <mbgl/renderer/backend_scope.hpp> +#include <mbgl/gl/headless_frontend.hpp> #include <mbgl/storage/online_file_source.hpp> #include <mbgl/util/default_thread_pool.hpp> #include <mbgl/util/exception.hpp> @@ -22,15 +21,14 @@ TEST(API, RenderWithoutCallback) { util::RunLoop loop; - HeadlessBackend backend { test::sharedDisplay() }; - BackendScope scope { backend }; - OffscreenView view { backend.getContext(), { 128, 512 } }; StubFileSource fileSource; ThreadPool threadPool(4); + float pixelRatio { 1 }; + HeadlessFrontend frontend { pixelRatio, fileSource, threadPool }; - std::unique_ptr<Map> map = - std::make_unique<Map>(backend, view.getSize(), 1, fileSource, threadPool, MapMode::Still); - map->renderStill(view, nullptr); + auto map = std::make_unique<Map>(frontend, MapObserver::nullObserver(), frontend.getSize(), + pixelRatio, fileSource, threadPool, MapMode::Still); + map->renderStill(nullptr); // Force Map thread to join. map.reset(); @@ -44,31 +42,3 @@ TEST(API, RenderWithoutCallback) { EXPECT_EQ(log->count(logMessage), 1u); } - -TEST(API, RenderWithoutStyle) { - util::RunLoop loop; - - HeadlessBackend backend { test::sharedDisplay() }; - BackendScope scope { backend }; - OffscreenView view { backend.getContext(), { 128, 512 } }; - StubFileSource fileSource; - ThreadPool threadPool(4); - - Map map(backend, view.getSize(), 1, fileSource, threadPool, MapMode::Still); - - std::exception_ptr error; - map.renderStill(view, [&](std::exception_ptr error_) { - error = error_; - loop.stop(); - }); - - loop.run(); - - try { - std::rethrow_exception(error); - } catch (const util::MisuseException& ex) { - EXPECT_EQ(std::string(ex.what()), "Map doesn't have a style"); - } catch (const std::exception&) { - EXPECT_TRUE(false) << "Unhandled exception."; - } -} diff --git a/test/api/custom_layer.test.cpp b/test/api/custom_layer.test.cpp index 5a30220cd7..1c514aeca2 100644 --- a/test/api/custom_layer.test.cpp +++ b/test/api/custom_layer.test.cpp @@ -2,11 +2,10 @@ #include <mbgl/gl/gl.hpp> #include <mbgl/map/map.hpp> -#include <mbgl/map/backend_scope.hpp> -#include <mbgl/gl/headless_backend.hpp> -#include <mbgl/gl/offscreen_view.hpp> #include <mbgl/util/default_thread_pool.hpp> #include <mbgl/storage/default_file_source.hpp> +#include <mbgl/gl/headless_frontend.hpp> +#include <mbgl/style/style.hpp> #include <mbgl/style/layers/custom_layer.hpp> #include <mbgl/style/layers/fill_layer.hpp> #include <mbgl/util/io.hpp> @@ -86,16 +85,15 @@ public: TEST(CustomLayer, Basic) { util::RunLoop loop; - HeadlessBackend backend { test::sharedDisplay() }; - BackendScope scope { backend }; - OffscreenView view { backend.getContext() }; DefaultFileSource fileSource(":memory:", "test/fixtures/api/assets"); ThreadPool threadPool(4); - - Map map(backend, view.getSize(), 1, fileSource, threadPool, MapMode::Still); - map.setStyleJSON(util::read_file("test/fixtures/api/water.json")); + float pixelRatio { 1 }; + HeadlessFrontend frontend { pixelRatio, fileSource, threadPool }; + Map map(frontend, MapObserver::nullObserver(), frontend.getSize(), pixelRatio, fileSource, + threadPool, MapMode::Still); + map.getStyle().loadJSON(util::read_file("test/fixtures/api/water.json")); map.setLatLngZoom({ 37.8, -122.5 }, 10); - map.addLayer(std::make_unique<CustomLayer>( + map.getStyle().addLayer(std::make_unique<CustomLayer>( "custom", [] (void* context) { reinterpret_cast<TestLayer*>(context)->initialize(); @@ -110,7 +108,7 @@ TEST(CustomLayer, Basic) { auto layer = std::make_unique<FillLayer>("landcover", "mapbox"); layer->setSourceLayer("landcover"); layer->setFillColor(Color{ 1.0, 1.0, 0.0, 1.0 }); - map.addLayer(std::move(layer)); + map.getStyle().addLayer(std::move(layer)); - test::checkImage("test/fixtures/custom_layer/basic", test::render(map, view), 0.0006, 0.1); + test::checkImage("test/fixtures/custom_layer/basic", frontend.render(map), 0.0006, 0.1); } diff --git a/test/api/query.test.cpp b/test/api/query.test.cpp index c509753d2d..3f3825eb72 100644 --- a/test/api/query.test.cpp +++ b/test/api/query.test.cpp @@ -1,15 +1,15 @@ #include <mbgl/map/map.hpp> -#include <mbgl/map/backend_scope.hpp> -#include <mbgl/gl/headless_backend.hpp> -#include <mbgl/gl/offscreen_view.hpp> #include <mbgl/util/default_thread_pool.hpp> #include <mbgl/test/stub_file_source.hpp> #include <mbgl/test/util.hpp> #include <mbgl/util/image.hpp> #include <mbgl/util/io.hpp> #include <mbgl/util/run_loop.hpp> +#include <mbgl/style/style.hpp> #include <mbgl/style/image.hpp> #include <mbgl/style/source.hpp> +#include <mbgl/renderer/renderer.hpp> +#include <mbgl/gl/headless_frontend.hpp> using namespace mbgl; using namespace mbgl::style; @@ -19,20 +19,20 @@ namespace { class QueryTest { public: QueryTest() { - map.setStyleJSON(util::read_file("test/fixtures/api/query_style.json")); - map.addImage("test-icon", std::make_unique<style::Image>( + map.getStyle().loadJSON(util::read_file("test/fixtures/api/query_style.json")); + map.getStyle().addImage(std::make_unique<style::Image>("test-icon", decodeImage(util::read_file("test/fixtures/sprites/default_marker.png")), 1.0)); - test::render(map, view); + frontend.render(map); } util::RunLoop loop; - HeadlessBackend backend { test::sharedDisplay() }; - BackendScope scope { backend }; - OffscreenView view { backend.getContext() }; StubFileSource fileSource; ThreadPool threadPool { 4 }; - Map map { backend, view.getSize(), 1, fileSource, threadPool, MapMode::Still }; + float pixelRatio { 1 }; + HeadlessFrontend frontend { pixelRatio, fileSource, threadPool }; + Map map { frontend, MapObserver::nullObserver(), frontend.getSize(), pixelRatio, fileSource, + threadPool, MapMode::Still }; }; } // end namespace @@ -40,10 +40,10 @@ public: TEST(Query, QueryRenderedFeatures) { QueryTest test; - auto features1 = test.map.queryRenderedFeatures(test.map.pixelForLatLng({ 0, 0 })); + auto features1 = test.frontend.getRenderer()->queryRenderedFeatures(test.map.pixelForLatLng({ 0, 0 })); EXPECT_EQ(features1.size(), 4u); - auto features2 = test.map.queryRenderedFeatures(test.map.pixelForLatLng({ 9, 9 })); + auto features2 = test.frontend.getRenderer()->queryRenderedFeatures(test.map.pixelForLatLng({ 9, 9 })); EXPECT_EQ(features2.size(), 0u); } @@ -52,16 +52,16 @@ TEST(Query, QueryRenderedFeaturesFilterLayer) { auto zz = test.map.pixelForLatLng({ 0, 0 }); - auto features1 = test.map.queryRenderedFeatures(zz, {{{ "layer1"}}, {}}); + auto features1 = test.frontend.getRenderer()->queryRenderedFeatures(zz, {{{ "layer1"}}, {}}); EXPECT_EQ(features1.size(), 1u); - auto features2 = test.map.queryRenderedFeatures(zz, {{{ "layer1", "layer2" }}, {}}); + auto features2 = test.frontend.getRenderer()->queryRenderedFeatures(zz, {{{ "layer1", "layer2" }}, {}}); EXPECT_EQ(features2.size(), 2u); - auto features3 = test.map.queryRenderedFeatures(zz, {{{ "foobar" }}, {}}); + auto features3 = test.frontend.getRenderer()->queryRenderedFeatures(zz, {{{ "foobar" }}, {}}); EXPECT_EQ(features3.size(), 0u); - auto features4 = test.map.queryRenderedFeatures(zz, {{{ "foobar", "layer3" }}, {}}); + auto features4 = test.frontend.getRenderer()->queryRenderedFeatures(zz, {{{ "foobar", "layer3" }}, {}}); EXPECT_EQ(features4.size(), 1u); } @@ -71,22 +71,22 @@ TEST(Query, QueryRenderedFeaturesFilter) { auto zz = test.map.pixelForLatLng({ 0, 0 }); const EqualsFilter eqFilter = { "key1", std::string("value1") }; - auto features1 = test.map.queryRenderedFeatures(zz, {{}, { eqFilter }}); + auto features1 = test.frontend.getRenderer()->queryRenderedFeatures(zz, {{}, { eqFilter }}); EXPECT_EQ(features1.size(), 1u); const IdentifierNotEqualsFilter idNotEqFilter = { std::string("feature1") }; - auto features2 = test.map.queryRenderedFeatures(zz, {{{ "layer4" }}, { idNotEqFilter }}); + auto features2 = test.frontend.getRenderer()->queryRenderedFeatures(zz, {{{ "layer4" }}, { idNotEqFilter }}); EXPECT_EQ(features2.size(), 0u); const GreaterThanFilter gtFilter = { "key2", 1.0 }; - auto features3 = test.map.queryRenderedFeatures(zz, {{ }, { gtFilter }}); + auto features3 = test.frontend.getRenderer()->queryRenderedFeatures(zz, {{ }, { gtFilter }}); EXPECT_EQ(features3.size(), 1u); } TEST(Query, QuerySourceFeatures) { QueryTest test; - auto features1 = test.map.querySourceFeatures("source3"); + auto features1 = test.frontend.getRenderer()->querySourceFeatures("source3"); EXPECT_EQ(features1.size(), 1u); } @@ -94,15 +94,15 @@ TEST(Query, QuerySourceFeaturesOptionValidation) { QueryTest test; // GeoJSONSource, doesn't require a layer id - auto features = test.map.querySourceFeatures("source3"); + auto features = test.frontend.getRenderer()->querySourceFeatures("source3"); ASSERT_EQ(features.size(), 1u); // VectorSource, requires a layer id - features = test.map.querySourceFeatures("source5"); + features = test.frontend.getRenderer()->querySourceFeatures("source5"); ASSERT_EQ(features.size(), 0u); // RasterSource, not supported - features = test.map.querySourceFeatures("source6"); + features = test.frontend.getRenderer()->querySourceFeatures("source6"); ASSERT_EQ(features.size(), 0u); } @@ -110,15 +110,15 @@ TEST(Query, QuerySourceFeaturesFilter) { QueryTest test; const EqualsFilter eqFilter = { "key1", std::string("value1") }; - auto features1 = test.map.querySourceFeatures("source4", {{}, { eqFilter }}); + auto features1 = test.frontend.getRenderer()->querySourceFeatures("source4", {{}, { eqFilter }}); EXPECT_EQ(features1.size(), 1u); const IdentifierNotEqualsFilter idNotEqFilter = { std::string("feature1") }; - auto features2 = test.map.querySourceFeatures("source4", {{}, { idNotEqFilter }}); + auto features2 = test.frontend.getRenderer()->querySourceFeatures("source4", {{}, { idNotEqFilter }}); EXPECT_EQ(features2.size(), 0u); const GreaterThanFilter gtFilter = { "key2", 1.0 }; - auto features3 = test.map.querySourceFeatures("source4", {{}, { gtFilter }}); + auto features3 = test.frontend.getRenderer()->querySourceFeatures("source4", {{}, { gtFilter }}); EXPECT_EQ(features3.size(), 1u); } diff --git a/test/api/recycle_map.cpp b/test/api/recycle_map.cpp new file mode 100644 index 0000000000..8cd622fe79 --- /dev/null +++ b/test/api/recycle_map.cpp @@ -0,0 +1,58 @@ +#include <mbgl/test/util.hpp> +#include <mbgl/test/stub_file_source.hpp> + +#include <mbgl/gl/headless_frontend.hpp> +#include <mbgl/map/map.hpp> +#include <mbgl/renderer/backend_scope.hpp> +#include <mbgl/storage/online_file_source.hpp> +#include <mbgl/style/layers/symbol_layer.hpp> +#include <mbgl/style/sources/geojson_source.hpp> +#include <mbgl/style/image.hpp> +#include <mbgl/style/style.hpp> +#include <mbgl/util/default_thread_pool.hpp> +#include <mbgl/util/exception.hpp> +#include <mbgl/util/geometry.hpp> +#include <mbgl/util/geojson.hpp> +#include <mbgl/util/io.hpp> +#include <mbgl/util/run_loop.hpp> + +using namespace mbgl; +using namespace mbgl::style; + + +TEST(API, RecycleMapUpdateImages) { + util::RunLoop loop; + + StubFileSource fileSource; + ThreadPool threadPool(4); + float pixelRatio { 1 }; + + HeadlessFrontend frontend { pixelRatio, fileSource, threadPool }; + auto map = std::make_unique<Map>(frontend, MapObserver::nullObserver(), frontend.getSize(), + pixelRatio, fileSource, threadPool, MapMode::Still); + + EXPECT_TRUE(map); + + auto loadStyle = [&](auto markerName, auto markerPath) { + auto source = std::make_unique<GeoJSONSource>("geometry"); + source->setGeoJSON({ Point<double> { 0, 0 } }); + + auto layer = std::make_unique<SymbolLayer>("geometry", "geometry"); + layer->setIconImage({ markerName }); + + map->getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); + map->getStyle().addSource(std::move(source)); + map->getStyle().addLayer(std::move(layer)); + map->getStyle().addImage(std::make_unique<style::Image>(markerName, decodeImage(util::read_file(markerPath)), 1.0)); + }; + + // default marker + + loadStyle("default_marker", "test/fixtures/sprites/default_marker.png"); + test::checkImage("test/fixtures/recycle_map/default_marker", frontend.render(*map), 0.0006, 0.1); + + // flipped marker + + loadStyle("flipped_marker", "test/fixtures/sprites/flipped_marker.png"); + test::checkImage("test/fixtures/recycle_map/flipped_marker", frontend.render(*map), 0.0006, 0.1); +} diff --git a/test/api/render_missing.test.cpp b/test/api/render_missing.test.cpp deleted file mode 100644 index 6e99501708..0000000000 --- a/test/api/render_missing.test.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include <mbgl/test/util.hpp> -#include <mbgl/test/fixture_log_observer.hpp> - -#include <mbgl/map/map.hpp> -#include <mbgl/map/backend_scope.hpp> -#include <mbgl/gl/headless_backend.hpp> -#include <mbgl/gl/offscreen_view.hpp> -#include <mbgl/util/default_thread_pool.hpp> -#include <mbgl/storage/default_file_source.hpp> -#include <mbgl/util/image.hpp> -#include <mbgl/util/io.hpp> -#include <mbgl/util/run_loop.hpp> - -#include <future> - -#if TEST_HAS_SERVER -#define TEST_REQUIRES_SERVER(name) name -#else -#define TEST_REQUIRES_SERVER(name) DISABLED_ ## name -#endif - -TEST(API, TEST_REQUIRES_SERVER(RenderMissingTile)) { - using namespace mbgl; - - util::RunLoop loop; - - const auto style = util::read_file("test/fixtures/api/water_missing_tiles.json"); - - HeadlessBackend backend { test::sharedDisplay() }; - BackendScope scope { backend }; - OffscreenView view { backend.getContext(), { 256, 512 } }; - DefaultFileSource fileSource(":memory:", "test/fixtures/api/assets"); - ThreadPool threadPool(4); - - Log::setObserver(std::make_unique<FixtureLogObserver>()); - - Map map(backend, view.getSize(), 1, fileSource, threadPool, MapMode::Still); - - std::string message; - - // This host does not respond (== connection error). - // Are you seeing this test fail? Make sure you don't have a server running on port 3001! - map.setStyleJSON(style); - map.renderStill(view, [&](std::exception_ptr err) { - ASSERT_TRUE(err.operator bool()); - try { - std::rethrow_exception(err); - } catch (const std::exception& ex) { - message = ex.what(); - EXPECT_TRUE(message.find("onnect") != std::string::npos); // [C|c]onnect - } - loop.stop(); - }); - - loop.run(); - - auto observer = Log::removeObserver(); - auto flo = dynamic_cast<FixtureLogObserver*>(observer.get()); - EXPECT_EQ(1u, flo->count(FixtureLog::Message( - EventSeverity::Error, Event::Style, -1, - std::string("Failed to load tile 0/0/0=>0 for source mapbox: " + message)))); - auto unchecked = flo->unchecked(); - EXPECT_TRUE(unchecked.empty()) << unchecked; -} diff --git a/test/api/repeated_render.test.cpp b/test/api/repeated_render.test.cpp deleted file mode 100644 index 0b9726d3fa..0000000000 --- a/test/api/repeated_render.test.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include <mbgl/test/util.hpp> -#include <mbgl/test/fixture_log_observer.hpp> - -#include <mbgl/map/map.hpp> -#include <mbgl/map/backend_scope.hpp> -#include <mbgl/gl/headless_backend.hpp> -#include <mbgl/gl/offscreen_view.hpp> -#include <mbgl/util/default_thread_pool.hpp> -#include <mbgl/storage/default_file_source.hpp> -#include <mbgl/util/image.hpp> -#include <mbgl/util/io.hpp> -#include <mbgl/util/run_loop.hpp> - -#include <future> - -using namespace mbgl; - - -TEST(API, RepeatedRender) { - - util::RunLoop loop; - - const auto style = util::read_file("test/fixtures/api/water.json"); - - HeadlessBackend backend { test::sharedDisplay() }; - BackendScope scope { backend }; - OffscreenView view { backend.getContext(), { 256, 512 } }; - DefaultFileSource fileSource(":memory:", "test/fixtures/api/assets"); - ThreadPool threadPool(4); - - Log::setObserver(std::make_unique<FixtureLogObserver>()); - - Map map(backend, view.getSize(), 1, fileSource, threadPool, MapMode::Still); - - { - map.setStyleJSON(style); - PremultipliedImage result; - map.renderStill(view, [&](std::exception_ptr) { - result = view.readStillImage(); - }); - - while (!result.valid()) { - loop.runOnce(); - } - - ASSERT_EQ(256u, result.size.width); - ASSERT_EQ(512u, result.size.height); -#if !TEST_READ_ONLY - util::write_file("test/fixtures/api/1.png", encodePNG(result)); -#endif - } - - { - map.setStyleJSON(style); - PremultipliedImage result; - map.renderStill(view, [&](std::exception_ptr) { - result = view.readStillImage(); - }); - - while (!result.valid()) { - loop.runOnce(); - } - - ASSERT_EQ(256u, result.size.width); - ASSERT_EQ(512u, result.size.height); -#if !TEST_READ_ONLY - util::write_file("test/fixtures/api/2.png", encodePNG(result)); -#endif - } - - auto observer = Log::removeObserver(); - auto flo = dynamic_cast<FixtureLogObserver*>(observer.get()); - auto unchecked = flo->unchecked(); - EXPECT_TRUE(unchecked.empty()) << unchecked; -} diff --git a/test/api/zoom_history.cpp b/test/api/zoom_history.cpp new file mode 100644 index 0000000000..1b1159bf52 --- /dev/null +++ b/test/api/zoom_history.cpp @@ -0,0 +1,71 @@ +#include <mbgl/test/util.hpp> +#include <mbgl/test/stub_file_source.hpp> + +#include <mbgl/gl/headless_frontend.hpp> +#include <mbgl/map/map.hpp> +#include <mbgl/renderer/backend_scope.hpp> +#include <mbgl/storage/online_file_source.hpp> +#include <mbgl/style/function/camera_function.hpp> +#include <mbgl/style/function/exponential_stops.hpp> +#include <mbgl/style/image.hpp> +#include <mbgl/style/layers/line_layer.hpp> +#include <mbgl/style/sources/geojson_source.hpp> +#include <mbgl/style/style.hpp> +#include <mbgl/util/default_thread_pool.hpp> +#include <mbgl/util/exception.hpp> +#include <mbgl/util/geometry.hpp> +#include <mbgl/util/geojson.hpp> +#include <mbgl/util/io.hpp> +#include <mbgl/util/run_loop.hpp> + +using namespace mbgl; +using namespace mbgl::style; + +TEST(API, ZoomHistory) { + util::RunLoop loop; + + StubFileSource fileSource; + ThreadPool threadPool(4); + float pixelRatio { 1 }; + + HeadlessFrontend frontend { pixelRatio, fileSource, threadPool }; + auto map = std::make_unique<Map>(frontend, MapObserver::nullObserver(), frontend.getSize(), + pixelRatio, fileSource, threadPool, MapMode::Still); + + EXPECT_TRUE(map); + + map->getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); + + auto source = std::make_unique<GeoJSONSource>("source"); + source->setGeoJSON({ LineString<double> { { 45, -45 }, { -45, 45 } } }); + map->getStyle().addSource(std::move(source)); + + auto layer = std::make_unique<LineLayer>("layer-1", "source"); + layer->setLineWidth(CameraFunction<float> { ExponentialStops<float> {{ { 0, 8 }, { 1, 16 } }} }); + layer->setLineDasharray({ std::vector<float> { 1, 1 } }); + layer->setLineColor( { Color::black() } ); + map->getStyle().addLayer(std::move(layer)); + + layer = std::make_unique<LineLayer>("layer-2", "source"); + layer->setLineWidth(CameraFunction<float> { ExponentialStops<float> {{ { 0, 4 }, { 1, 8 } }} }); + layer->setLineDasharray({ std::vector<float> { 2, 2 } }); + layer->setLineColor( { Color::red() } ); + map->getStyle().addLayer(std::move(layer)); + + // ZoomHistory.lastIntegerZoom is 1. + map->setZoom(1.0); + frontend.render(*map); + + map->setZoom(0.0); + frontend.render(*map); + + // ZoomHistory.lastIntegerZoom should be 0. + map->setZoom(0.5); + test::checkImage("test/fixtures/zoom_history", frontend.render(*map), 0.0002); + + map->setZoom(1.0); + frontend.render(*map); + + map->setZoom(0.5); + test::checkImage("test/fixtures/zoom_history", frontend.render(*map), 0.0002); +} diff --git a/test/fixtures/annotations/readd_image/expected.png b/test/fixtures/annotations/readd_image/expected.png Binary files differnew file mode 100644 index 0000000000..3c4847f3a7 --- /dev/null +++ b/test/fixtures/annotations/readd_image/expected.png diff --git a/test/fixtures/api/empty-zoomed.json b/test/fixtures/api/empty-zoomed.json new file mode 100644 index 0000000000..02d8fca99e --- /dev/null +++ b/test/fixtures/api/empty-zoomed.json @@ -0,0 +1,6 @@ +{ + "version": 8, + "zoom": 0.5, + "sources": {}, + "layers": [] +} diff --git a/test/fixtures/image_manager/basic/expected.png b/test/fixtures/image_manager/basic/expected.png Binary files differnew file mode 100644 index 0000000000..8c615234dc --- /dev/null +++ b/test/fixtures/image_manager/basic/expected.png diff --git a/test/fixtures/image_manager/updates_after/expected.png b/test/fixtures/image_manager/updates_after/expected.png Binary files differnew file mode 100644 index 0000000000..db588c739b --- /dev/null +++ b/test/fixtures/image_manager/updates_after/expected.png diff --git a/test/fixtures/image_manager/updates_before/expected.png b/test/fixtures/image_manager/updates_before/expected.png Binary files differnew file mode 100644 index 0000000000..1466a92fe7 --- /dev/null +++ b/test/fixtures/image_manager/updates_before/expected.png diff --git a/test/fixtures/map/nocontent/expected.png b/test/fixtures/map/nocontent/expected.png Binary files differnew file mode 100644 index 0000000000..8fa3f0f5db --- /dev/null +++ b/test/fixtures/map/nocontent/expected.png diff --git a/test/fixtures/map/prefetch/empty.json b/test/fixtures/map/prefetch/empty.json new file mode 100644 index 0000000000..61a8fadcdb --- /dev/null +++ b/test/fixtures/map/prefetch/empty.json @@ -0,0 +1,5 @@ +{ + "version": 8, + "sources": {}, + "layers": [] +} diff --git a/test/fixtures/map/prefetch/expected.png b/test/fixtures/map/prefetch/expected.png Binary files differnew file mode 100644 index 0000000000..e1111b37f7 --- /dev/null +++ b/test/fixtures/map/prefetch/expected.png diff --git a/test/fixtures/map/prefetch/style.json b/test/fixtures/map/prefetch/style.json new file mode 100644 index 0000000000..b4e163888c --- /dev/null +++ b/test/fixtures/map/prefetch/style.json @@ -0,0 +1,24 @@ +{ + "version": 8, + "name": "Test", + "sources": { + "raster": { + "type": "raster", + "tiles": [ "{z}" ], + "tileSize": 256, + "maxzoom": 20, + "minzoom": 0 + } + }, + "layers": [{ + "id": "background", + "type": "background", + "paint": { + "background-color": "blue" + } + }, { + "id": "raster", + "type": "raster", + "source": "raster" + }] +} diff --git a/test/fixtures/map/prefetch/tile_green.png b/test/fixtures/map/prefetch/tile_green.png Binary files differnew file mode 100644 index 0000000000..553cd10cd1 --- /dev/null +++ b/test/fixtures/map/prefetch/tile_green.png diff --git a/test/fixtures/map/prefetch/tile_red.png b/test/fixtures/map/prefetch/tile_red.png Binary files differnew file mode 100644 index 0000000000..5fa561fb92 --- /dev/null +++ b/test/fixtures/map/prefetch/tile_red.png diff --git a/test/fixtures/offline_database/v5.db b/test/fixtures/offline_database/v5.db Binary files differnew file mode 100644 index 0000000000..9bba351208 --- /dev/null +++ b/test/fixtures/offline_database/v5.db diff --git a/test/fixtures/offline_download/radar.gif b/test/fixtures/offline_download/radar.gif Binary files differnew file mode 100644 index 0000000000..7398a060c0 --- /dev/null +++ b/test/fixtures/offline_download/radar.gif diff --git a/test/fixtures/offline_download/style.json b/test/fixtures/offline_download/style.json index 978df3aae3..618afd45f0 100644 --- a/test/fixtures/offline_download/style.json +++ b/test/fixtures/offline_download/style.json @@ -5,6 +5,16 @@ "mapbox": { "type": "vector", "url": "http://127.0.0.1:3000/streets.json" + }, + "radar": { + "type": "image", + "url":"http://127.0.0.1:3000/radar.gif", + "coordinates": [ + [-180, -85.0511], + [180, -85.0511], + [180, 85.0511], + [-180, 85.0511] + ] } }, "glyphs": "http://127.0.0.1:3000/{fontstack}/{range}.pbf", diff --git a/test/fixtures/recycle_map/default_marker/expected.png b/test/fixtures/recycle_map/default_marker/expected.png Binary files differnew file mode 100644 index 0000000000..a23b79f8d8 --- /dev/null +++ b/test/fixtures/recycle_map/default_marker/expected.png diff --git a/test/fixtures/recycle_map/flipped_marker/expected.png b/test/fixtures/recycle_map/flipped_marker/expected.png Binary files differnew file mode 100644 index 0000000000..3c4847f3a7 --- /dev/null +++ b/test/fixtures/recycle_map/flipped_marker/expected.png diff --git a/test/fixtures/sprite_atlas/basic/expected.png b/test/fixtures/sprite_atlas/basic/expected.png Binary files differdeleted file mode 100644 index cd13d16df6..0000000000 --- a/test/fixtures/sprite_atlas/basic/expected.png +++ /dev/null diff --git a/test/fixtures/sprite_atlas/size/expected.png b/test/fixtures/sprite_atlas/size/expected.png Binary files differdeleted file mode 100644 index d9ae7dab47..0000000000 --- a/test/fixtures/sprite_atlas/size/expected.png +++ /dev/null diff --git a/test/fixtures/sprite_atlas/updates_after/expected.png b/test/fixtures/sprite_atlas/updates_after/expected.png Binary files differdeleted file mode 100644 index 3c850c0a25..0000000000 --- a/test/fixtures/sprite_atlas/updates_after/expected.png +++ /dev/null diff --git a/test/fixtures/sprite_atlas/updates_before/expected.png b/test/fixtures/sprite_atlas/updates_before/expected.png Binary files differdeleted file mode 100644 index effcd38f1e..0000000000 --- a/test/fixtures/sprite_atlas/updates_before/expected.png +++ /dev/null diff --git a/test/fixtures/style_parser/center-not-latlong.info.json b/test/fixtures/style_parser/center-not-latlong.info.json new file mode 100644 index 0000000000..c79de0a525 --- /dev/null +++ b/test/fixtures/style_parser/center-not-latlong.info.json @@ -0,0 +1,7 @@ +{ + "default": { + "log": [ + [1, "WARNING", "ParseStyle", "center coordinate must be a longitude, latitude pair"] + ] + } +}
\ No newline at end of file diff --git a/test/fixtures/style_parser/center-not-latlong.style.json b/test/fixtures/style_parser/center-not-latlong.style.json new file mode 100644 index 0000000000..eb847868f5 --- /dev/null +++ b/test/fixtures/style_parser/center-not-latlong.style.json @@ -0,0 +1,4 @@ +{ + "version": 8, + "center" : [ 75.123, 181.123] +}
\ No newline at end of file diff --git a/test/fixtures/style_parser/geojson-missing-properties.info.json b/test/fixtures/style_parser/geojson-missing-properties.info.json new file mode 100644 index 0000000000..9c25a2f488 --- /dev/null +++ b/test/fixtures/style_parser/geojson-missing-properties.info.json @@ -0,0 +1,6 @@ +{ + "default": { + "log": [ + ] + } +} diff --git a/test/fixtures/style_parser/geojson-missing-properties.style.json b/test/fixtures/style_parser/geojson-missing-properties.style.json new file mode 100644 index 0000000000..fc4fe97c78 --- /dev/null +++ b/test/fixtures/style_parser/geojson-missing-properties.style.json @@ -0,0 +1,9 @@ +{ + "version": 8, + "sources": { + "mapbox": { + "type": "geojson", + "data": { "type": "Feature", "geometry": { "type": "Point", "coordinates": [100.0, 0.0] } } + } + } +} diff --git a/test/fixtures/style_parser/image-coordinates.info.json b/test/fixtures/style_parser/image-coordinates.info.json new file mode 100644 index 0000000000..5ef44badba --- /dev/null +++ b/test/fixtures/style_parser/image-coordinates.info.json @@ -0,0 +1,7 @@ +{ + "default": { + "log": [ + [1, "WARNING", "ParseStyle", "Image coordinates must be an array of four longitude latitude pairs"] + ] + } +}
\ No newline at end of file diff --git a/test/fixtures/style_parser/image-coordinates.style.json b/test/fixtures/style_parser/image-coordinates.style.json new file mode 100644 index 0000000000..51b0e93ee7 --- /dev/null +++ b/test/fixtures/style_parser/image-coordinates.style.json @@ -0,0 +1,14 @@ +{ + "version": 8, + "center" : [ 75.123, 81.123], + "sources": { + "image": { + "type": "image", + "url": "local://0.png", + "coordinates": [ + [ 12.2344, 87.23434], + [-12.234, 12.41242] + ] + } + } +}
\ No newline at end of file diff --git a/test/fixtures/style_parser/image-url.info.json b/test/fixtures/style_parser/image-url.info.json new file mode 100644 index 0000000000..988e89c011 --- /dev/null +++ b/test/fixtures/style_parser/image-url.info.json @@ -0,0 +1,7 @@ +{ + "default": { + "log": [ + [1, "WARNING", "ParseStyle", "Image source must have a url value"] + ] + } +}
\ No newline at end of file diff --git a/test/fixtures/style_parser/image-url.style.json b/test/fixtures/style_parser/image-url.style.json new file mode 100644 index 0000000000..94614cb7ab --- /dev/null +++ b/test/fixtures/style_parser/image-url.style.json @@ -0,0 +1,9 @@ +{ + "version": 8, + "center" : [ 75.123, 81.123], + "sources": { + "image": { + "type": "image" + } + } +}
\ No newline at end of file diff --git a/test/fixtures/zoom_history/expected.png b/test/fixtures/zoom_history/expected.png Binary files differnew file mode 100644 index 0000000000..100dc508a4 --- /dev/null +++ b/test/fixtures/zoom_history/expected.png diff --git a/test/geometry/binpack.test.cpp b/test/geometry/binpack.test.cpp deleted file mode 100644 index 0b74df7fa9..0000000000 --- a/test/geometry/binpack.test.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include <mbgl/test/util.hpp> - -#include <mbgl/geometry/binpack.hpp> - -#include <iosfwd> -#include <array> - -namespace mbgl { -template <typename T> ::std::ostream& operator<<(::std::ostream& os, const Rect<T>& t) { - return os << "Rect { " << t.x << ", " << t.y << ", " << t.w << ", " << t.h << " }"; -} -} // namespace mbgl - -TEST(BinPack, Allocating) { - mbgl::BinPack<uint16_t> bin(128, 128); - std::array<mbgl::Rect<uint16_t>, 4> rects; - - rects[0] = bin.allocate(32, 48); - ASSERT_EQ(mbgl::Rect<uint16_t>(0, 0, 32, 48), rects[0]); - rects[1] = bin.allocate(8, 17); - ASSERT_EQ(mbgl::Rect<uint16_t>(32, 0, 8, 17), rects[1]); - rects[2] = bin.allocate(8, 17); - ASSERT_EQ(mbgl::Rect<uint16_t>(0, 48, 8, 17), rects[2]); - - bin.release(rects[0]); - rects[0] = bin.allocate(32, 24); - ASSERT_EQ(mbgl::Rect<uint16_t>(0, 0, 32, 24), rects[0]); - rects[3] = bin.allocate(32, 24); - ASSERT_EQ(mbgl::Rect<uint16_t>(32, 17, 32, 24), rects[3]); -} - - -TEST(BinPack, Full) { - mbgl::BinPack<uint16_t> bin(128, 128); - std::vector<mbgl::Rect<uint16_t>> rects; - - for (int j = 0; j < 3; j++) { - for (int i = 0; i < 256; i++) { - auto rect = bin.allocate(8, 8); - ASSERT_TRUE(rect.hasArea()); - rects.push_back(rect); - } - - ASSERT_FALSE(bin.allocate(8, 8).hasArea()); - - for (auto& rect: rects) { - bin.release(rect); - } - rects.clear(); - } -} diff --git a/test/gl/bucket.test.cpp b/test/gl/bucket.test.cpp index e21d82321d..5f9626bc99 100644 --- a/test/gl/bucket.test.cpp +++ b/test/gl/bucket.test.cpp @@ -1,29 +1,94 @@ #include <mbgl/test/util.hpp> +#include <mbgl/test/stub_geometry_tile_feature.hpp> -#include <mbgl/renderer/circle_bucket.hpp> -#include <mbgl/renderer/fill_bucket.hpp> -#include <mbgl/renderer/line_bucket.hpp> -#include <mbgl/renderer/symbol_bucket.hpp> +#include <mbgl/renderer/buckets/circle_bucket.hpp> +#include <mbgl/renderer/buckets/fill_bucket.hpp> +#include <mbgl/renderer/buckets/line_bucket.hpp> +#include <mbgl/renderer/buckets/raster_bucket.hpp> +#include <mbgl/renderer/buckets/symbol_bucket.hpp> #include <mbgl/renderer/bucket_parameters.hpp> #include <mbgl/style/layers/symbol_layer_properties.hpp> +#include <mbgl/gl/context.hpp> #include <mbgl/map/mode.hpp> +namespace mbgl { + +template <class Attributes> +bool operator==(const Segment<Attributes>& lhs, const Segment<Attributes>& rhs) { + return std::tie(lhs.vertexOffset, lhs.indexOffset, lhs.vertexLength, lhs.indexLength) == + std::tie(rhs.vertexOffset, rhs.indexOffset, rhs.vertexLength, rhs.indexLength); +} + +namespace gl { +namespace detail { + +template <class A1, class A2> +bool operator==(const Vertex<A1, A2>& lhs, const Vertex<A1, A2>& rhs) { + return std::tie(lhs.a1, lhs.a2) == std::tie(rhs.a1, rhs.a2); +} + +} // namespace detail +} // namespace gl +} // namespace mbgl + using namespace mbgl; +namespace { + +PropertyMap properties; + +} // namespace + TEST(Buckets, CircleBucket) { - CircleBucket bucket { { {0, 0, 0}, MapMode::Still }, {} }; + gl::Context context; + CircleBucket bucket { { {0, 0, 0}, MapMode::Still, 1.0 }, {} }; ASSERT_FALSE(bucket.hasData()); + ASSERT_FALSE(bucket.needsUpload()); + + GeometryCollection point { { { 0, 0 } } }; + bucket.addFeature(StubGeometryTileFeature { {}, FeatureType::Point, point, properties }, point); + ASSERT_TRUE(bucket.hasData()); + ASSERT_TRUE(bucket.needsUpload()); + + bucket.upload(context); + ASSERT_TRUE(bucket.hasData()); + ASSERT_FALSE(bucket.needsUpload()); } TEST(Buckets, FillBucket) { - FillBucket bucket { { {0, 0, 0}, MapMode::Still }, {} }; + gl::Context context; + FillBucket bucket { { {0, 0, 0}, MapMode::Still, 1.0 }, {} }; ASSERT_FALSE(bucket.hasData()); + ASSERT_FALSE(bucket.needsUpload()); + + GeometryCollection polygon { { { 0, 0 }, { 0, 1 }, { 1, 1 } } }; + bucket.addFeature(StubGeometryTileFeature { {}, FeatureType::Polygon, polygon, properties }, polygon); + ASSERT_TRUE(bucket.hasData()); + ASSERT_TRUE(bucket.needsUpload()); + + bucket.upload(context); + ASSERT_FALSE(bucket.needsUpload()); } TEST(Buckets, LineBucket) { - LineBucket bucket { { {0, 0, 0}, MapMode::Still }, {}, {} }; + gl::Context context; + LineBucket bucket { { {0, 0, 0}, MapMode::Still, 1.0 }, {}, {} }; ASSERT_FALSE(bucket.hasData()); + ASSERT_FALSE(bucket.needsUpload()); + + // Ignore invalid feature type. + GeometryCollection point { { { 0, 0 } } }; + bucket.addFeature(StubGeometryTileFeature { {}, FeatureType::Point, point, properties }, point); + ASSERT_FALSE(bucket.hasData()); + + GeometryCollection line { { { 0, 0 }, { 1, 1 } } }; + bucket.addFeature(StubGeometryTileFeature { {}, FeatureType::LineString, line, properties }, line); + ASSERT_TRUE(bucket.hasData()); + ASSERT_TRUE(bucket.needsUpload()); + + bucket.upload(context); + ASSERT_FALSE(bucket.needsUpload()); } TEST(Buckets, SymbolBucket) { @@ -31,8 +96,180 @@ TEST(Buckets, SymbolBucket) { bool sdfIcons = false; bool iconsNeedLinear = false; + gl::Context context; SymbolBucket bucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear }; ASSERT_FALSE(bucket.hasIconData()); ASSERT_FALSE(bucket.hasTextData()); ASSERT_FALSE(bucket.hasCollisionBoxData()); + ASSERT_FALSE(bucket.hasData()); + ASSERT_FALSE(bucket.needsUpload()); + + // SymbolBucket::addFeature() is a no-op. + GeometryCollection point { { { 0, 0 } } }; + bucket.addFeature(StubGeometryTileFeature { {}, FeatureType::Point, point, properties }, point); + ASSERT_FALSE(bucket.hasData()); + ASSERT_FALSE(bucket.needsUpload()); + + bucket.text.segments.emplace_back(0, 0); + ASSERT_TRUE(bucket.hasTextData()); + ASSERT_TRUE(bucket.hasData()); + ASSERT_TRUE(bucket.needsUpload()); + + bucket.upload(context); + ASSERT_FALSE(bucket.needsUpload()); +} + +TEST(Buckets, RasterBucket) { + gl::Context context; + PremultipliedImage rgba({ 1, 1 }); + + // RasterBucket::hasData() is always true. + RasterBucket bucket = { std::move(rgba) }; + ASSERT_TRUE(bucket.hasData()); + ASSERT_TRUE(bucket.needsUpload()); + + bucket.upload(context); + ASSERT_FALSE(bucket.needsUpload()); + + bucket.clear(); + ASSERT_TRUE(bucket.needsUpload()); } + +TEST(Buckets, RasterBucketMaskEmpty) { + RasterBucket bucket{ nullptr }; + bucket.setMask({}); + EXPECT_EQ((std::vector<RasterLayoutVertex>{}), bucket.vertices.vector()); + EXPECT_EQ((std::vector<uint16_t>{}), bucket.indices.vector()); + SegmentVector<RasterAttributes> expectedSegments; + expectedSegments.emplace_back(0, 0, 0, 0); + EXPECT_EQ(expectedSegments, bucket.segments); +} + +TEST(Buckets, RasterBucketMaskNoChildren) { + RasterBucket bucket{ nullptr }; + bucket.setMask({ CanonicalTileID{ 0, 0, 0 } }); + + // A mask of 0/0/0 doesn't produce buffers since we're instead using the global shared buffers. + EXPECT_EQ((std::vector<RasterLayoutVertex>{}), bucket.vertices.vector()); + EXPECT_EQ((std::vector<uint16_t>{}), bucket.indices.vector()); + EXPECT_EQ((SegmentVector<RasterAttributes>{}), bucket.segments); +} + + TEST(Buckets, RasterBucketMaskTwoChildren) { + RasterBucket bucket{ nullptr }; + bucket.setMask( + { CanonicalTileID{ 1, 0, 0 }, CanonicalTileID{ 1, 1, 1 } }); + + EXPECT_EQ( + (std::vector<RasterLayoutVertex>{ + // 1/0/1 + RasterProgram::layoutVertex({ 0, 0 }, { 0, 0 }), + RasterProgram::layoutVertex({ 4096, 0 }, { 4096, 0 }), + RasterProgram::layoutVertex({ 0, 4096 }, { 0, 4096 }), + RasterProgram::layoutVertex({ 4096, 4096 }, { 4096, 4096 }), + + // 1/1/1 + RasterProgram::layoutVertex({ 4096, 4096 }, { 4096, 4096 }), + RasterProgram::layoutVertex({ 8192, 4096 }, { 8192, 4096 }), + RasterProgram::layoutVertex({ 4096, 8192 }, { 4096, 8192 }), + RasterProgram::layoutVertex({ 8192, 8192 }, { 8192, 8192 }), + }), + bucket.vertices.vector()); + + EXPECT_EQ( + (std::vector<uint16_t>{ + // 1/0/1 + 0, 1, 2, + 1, 2, 3, + + // 1/1/1 + 4, 5, 6, + 5, 6, 7, + }), + bucket.indices.vector()); + + + SegmentVector<RasterAttributes> expectedSegments; + expectedSegments.emplace_back(0, 0, 8, 12); + EXPECT_EQ(expectedSegments, bucket.segments); + } + + TEST(Buckets, RasterBucketMaskComplex) { + RasterBucket bucket{ nullptr }; + bucket.setMask( + { CanonicalTileID{ 1, 0, 1 }, CanonicalTileID{ 1, 1, 0 }, CanonicalTileID{ 2, 2, 3 }, + CanonicalTileID{ 2, 3, 2 }, CanonicalTileID{ 3, 6, 7 }, CanonicalTileID{ 3, 7, 6 } }); + + EXPECT_EQ( + (std::vector<RasterLayoutVertex>{ + // 1/0/1 + RasterProgram::layoutVertex({ 0, 4096 }, { 0, 4096 }), + RasterProgram::layoutVertex({ 4096, 4096 }, { 4096, 4096 }), + RasterProgram::layoutVertex({ 0, 8192 }, { 0, 8192 }), + RasterProgram::layoutVertex({ 4096, 8192 }, { 4096, 8192 }), + + // 1/1/0 + RasterProgram::layoutVertex({ 4096, 0 }, { 4096, 0 }), + RasterProgram::layoutVertex({ 8192, 0 }, { 8192, 0 }), + RasterProgram::layoutVertex({ 4096, 4096 }, { 4096, 4096 }), + RasterProgram::layoutVertex({ 8192, 4096 }, { 8192, 4096 }), + + // 2/2/3 + RasterProgram::layoutVertex({ 4096, 6144 }, { 4096, 6144 }), + RasterProgram::layoutVertex({ 6144, 6144 }, { 6144, 6144 }), + RasterProgram::layoutVertex({ 4096, 8192 }, { 4096, 8192 }), + RasterProgram::layoutVertex({ 6144, 8192 }, { 6144, 8192 }), + + // 2/3/2 + RasterProgram::layoutVertex({ 6144, 4096 }, { 6144, 4096 }), + RasterProgram::layoutVertex({ 8192, 4096 }, { 8192, 4096 }), + RasterProgram::layoutVertex({ 6144, 6144 }, { 6144, 6144 }), + RasterProgram::layoutVertex({ 8192, 6144 }, { 8192, 6144 }), + + // 3/6/7 + RasterProgram::layoutVertex({ 6144, 7168 }, { 6144, 7168 }), + RasterProgram::layoutVertex({ 7168, 7168 }, { 7168, 7168 }), + RasterProgram::layoutVertex({ 6144, 8192 }, { 6144, 8192 }), + RasterProgram::layoutVertex({ 7168, 8192 }, { 7168, 8192 }), + + // 3/7/6 + RasterProgram::layoutVertex({ 7168, 6144 }, { 7168, 6144 }), + RasterProgram::layoutVertex({ 8192, 6144 }, { 8192, 6144 }), + RasterProgram::layoutVertex({ 7168, 7168 }, { 7168, 7168 }), + RasterProgram::layoutVertex({ 8192, 7168 }, { 8192, 7168 }), + }), + bucket.vertices.vector()); + + EXPECT_EQ( + (std::vector<uint16_t>{ + // 1/0/1 + 0, 1, 2, + 1, 2, 3, + + // 1/1/0 + 4, 5, 6, + 5, 6, 7, + + // 2/2/3 + 8, 9, 10, + 9, 10, 11, + + // 2/3/2 + 12, 13, 14, + 13, 14, 15, + + // 3/6/7 + 16, 17, 18, + 17, 18, 19, + + // 3/7/6 + 20, 21, 22, + 21, 22, 23, + }), + bucket.indices.vector()); + + + SegmentVector<RasterAttributes> expectedSegments; + expectedSegments.emplace_back(0, 0, 24, 36); + EXPECT_EQ(expectedSegments, bucket.segments); + } diff --git a/test/gl/object.test.cpp b/test/gl/object.test.cpp index b5a055f4ca..8046927c54 100644 --- a/test/gl/object.test.cpp +++ b/test/gl/object.test.cpp @@ -1,9 +1,7 @@ #include <mbgl/test/util.hpp> -#include <mbgl/map/backend_scope.hpp> +#include <mbgl/renderer/backend_scope.hpp> #include <mbgl/gl/headless_backend.hpp> -#include <mbgl/gl/offscreen_view.hpp> - #include <mbgl/gl/context.hpp> #include <memory> @@ -47,9 +45,8 @@ TEST(GLObject, Value) { } TEST(GLObject, Store) { - HeadlessBackend backend { test::sharedDisplay() }; + HeadlessBackend backend { { 256, 256 } }; BackendScope scope { backend }; - OffscreenView view(backend.getContext()); gl::Context context; EXPECT_TRUE(context.empty()); diff --git a/test/map/map.test.cpp b/test/map/map.test.cpp index c24f736fcd..50d5e50abb 100644 --- a/test/map/map.test.cpp +++ b/test/map/map.test.cpp @@ -4,10 +4,8 @@ #include <mbgl/test/fixture_log_observer.hpp> #include <mbgl/map/map.hpp> -#include <mbgl/map/backend_scope.hpp> -#include <mbgl/gl/headless_backend.hpp> -#include <mbgl/gl/offscreen_view.hpp> #include <mbgl/gl/context.hpp> +#include <mbgl/gl/headless_frontend.hpp> #include <mbgl/util/default_thread_pool.hpp> #include <mbgl/storage/network_status.hpp> #include <mbgl/storage/default_file_source.hpp> @@ -16,6 +14,7 @@ #include <mbgl/util/io.hpp> #include <mbgl/util/run_loop.hpp> #include <mbgl/util/async_task.hpp> +#include <mbgl/style/style.hpp> #include <mbgl/style/image.hpp> #include <mbgl/style/layers/background_layer.hpp> #include <mbgl/util/color.hpp> @@ -24,87 +23,116 @@ using namespace mbgl; using namespace mbgl::style; using namespace std::literals::string_literals; -class BackendTest : public HeadlessBackend { +class StubMapObserver : public MapObserver { public: - BackendTest() : HeadlessBackend(test::sharedDisplay()) {} - + void onWillStartLoadingMap() final { + if (onWillStartLoadingMapCallback) { + onWillStartLoadingMapCallback(); + } + } + + void onDidFinishLoadingMap() final { + if (onDidFinishLoadingMapCallback) { + onDidFinishLoadingMapCallback(); + } + } + void onDidFailLoadingMap(std::exception_ptr) final { if (didFailLoadingMapCallback) { didFailLoadingMapCallback(); } } - + void onDidFinishLoadingStyle() final { if (didFinishLoadingStyleCallback) { didFinishLoadingStyleCallback(); } } + void onDidFinishRenderingFrame(RenderMode mode) final { + if (didFinishRenderingFrame) { + didFinishRenderingFrame(mode); + } + } + + std::function<void()> onWillStartLoadingMapCallback; + std::function<void()> onDidFinishLoadingMapCallback; std::function<void()> didFailLoadingMapCallback; std::function<void()> didFinishLoadingStyleCallback; + std::function<void(RenderMode)> didFinishRenderingFrame; }; -struct MapTest { +template <class FileSource = StubFileSource> +class MapTest { +public: util::RunLoop runLoop; - BackendTest backend; - BackendScope scope { backend }; - OffscreenView view { backend.getContext() }; - StubFileSource fileSource; + FileSource fileSource; ThreadPool threadPool { 4 }; + StubMapObserver observer; + HeadlessFrontend frontend; + Map map; + + MapTest(float pixelRatio = 1, MapMode mode = MapMode::Still) + : frontend(pixelRatio, fileSource, threadPool) + , map(frontend, observer, frontend.getSize(), pixelRatio, fileSource, threadPool, mode) { + } + + template <typename T = FileSource> + MapTest(const std::string& cachePath, const std::string& assetRoot, + float pixelRatio = 1, MapMode mode = MapMode::Still, + typename std::enable_if<std::is_same<T, DefaultFileSource>::value>::type* = 0) + : fileSource { cachePath, assetRoot } + , frontend(pixelRatio, fileSource, threadPool) + , map(frontend, observer, frontend.getSize(), pixelRatio, fileSource, threadPool, mode) { + } }; TEST(Map, LatLngBehavior) { - MapTest test; - Map map(test.backend, test.view.getSize(), 1, test.fileSource, test.threadPool, MapMode::Still); - - map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); + MapTest<> test; - map.setLatLngZoom({ 1, 1 }, 0); - auto latLng1 = map.getLatLng(); + test.map.setLatLngZoom({ 1, 1 }, 0); + auto latLng1 = test.map.getLatLng(); - map.setLatLng({ 1, 1 }); - auto latLng2 = map.getLatLng(); + test.map.setLatLng({ 1, 1 }); + auto latLng2 = test.map.getLatLng(); ASSERT_DOUBLE_EQ(latLng1.latitude(), latLng2.latitude()); ASSERT_DOUBLE_EQ(latLng1.longitude(), latLng2.longitude()); } TEST(Map, LatLngBoundsToCamera) { - MapTest test; - Map map(test.backend, test.view.getSize(), 1, test.fileSource, test.threadPool, MapMode::Still); + MapTest<> test; - map.setLatLngZoom({ 40.712730, -74.005953 }, 16.0); + test.map.setLatLngZoom({ 40.712730, -74.005953 }, 16.0); LatLngBounds bounds = LatLngBounds::hull({15.68169,73.499857}, {53.560711, 134.77281}); - CameraOptions virtualCamera = map.cameraForLatLngBounds(bounds, {}); + CameraOptions virtualCamera = test.map.cameraForLatLngBounds(bounds, {}); ASSERT_TRUE(bounds.contains(*virtualCamera.center)); } TEST(Map, CameraToLatLngBounds) { - MapTest test; - Map map(test.backend, test.view.getSize(), 1, test.fileSource, test.threadPool, MapMode::Still); + MapTest<> test; - map.setLatLngZoom({ 45, 90 }, 16); + test.map.setLatLngZoom({ 45, 90 }, 16); LatLngBounds bounds = LatLngBounds::hull( - map.latLngForPixel({}), - map.latLngForPixel({ double(map.getSize().width), double(map.getSize().height) })); + test.map.latLngForPixel({}), + test.map.latLngForPixel({ double(test.map.getSize().width), double(test.map.getSize().height) })); - CameraOptions camera = map.getCameraOptions({}); + CameraOptions camera = test.map.getCameraOptions({}); - ASSERT_EQ(bounds, map.latLngBoundsForCamera(camera)); + ASSERT_EQ(bounds, test.map.latLngBoundsForCamera(camera)); // Map::cameraForLatLngBounds only sets zoom and center. - CameraOptions virtualCamera = map.cameraForLatLngBounds(bounds, {}); + CameraOptions virtualCamera = test.map.cameraForLatLngBounds(bounds, {}); ASSERT_NEAR(*camera.zoom, *virtualCamera.zoom, 1e-7); ASSERT_NEAR(camera.center->latitude(), virtualCamera.center->latitude(), 1e-7); ASSERT_NEAR(camera.center->longitude(), virtualCamera.center->longitude(), 1e-7); } TEST(Map, Offline) { - MapTest test; - DefaultFileSource fileSource(":memory:", "."); + MapTest<DefaultFileSource> test {":memory:", "."}; auto expiredItem = [] (const std::string& path) { Response response; @@ -114,39 +142,50 @@ TEST(Map, Offline) { }; const std::string prefix = "http://127.0.0.1:3000/"; - fileSource.put(Resource::style(prefix + "style.json"), expiredItem("style.json")); - fileSource.put(Resource::source(prefix + "streets.json"), expiredItem("streets.json")); - fileSource.put(Resource::spriteJSON(prefix + "sprite", 1.0), expiredItem("sprite.json")); - fileSource.put(Resource::spriteImage(prefix + "sprite", 1.0), expiredItem("sprite.png")); - fileSource.put(Resource::tile(prefix + "{z}-{x}-{y}.vector.pbf", 1.0, 0, 0, 0, Tileset::Scheme::XYZ), expiredItem("0-0-0.vector.pbf")); - fileSource.put(Resource::glyphs(prefix + "{fontstack}/{range}.pbf", {{"Helvetica"}}, {0, 255}), expiredItem("glyph.pbf")); + test.fileSource.put(Resource::style(prefix + "style.json"), expiredItem("style.json")); + test.fileSource.put(Resource::source(prefix + "streets.json"), expiredItem("streets.json")); + test.fileSource.put(Resource::spriteJSON(prefix + "sprite", 1.0), expiredItem("sprite.json")); + test.fileSource.put(Resource::spriteImage(prefix + "sprite", 1.0), expiredItem("sprite.png")); + test.fileSource.put(Resource::tile(prefix + "{z}-{x}-{y}.vector.pbf", 1.0, 0, 0, 0, Tileset::Scheme::XYZ), expiredItem("0-0-0.vector.pbf")); + test.fileSource.put(Resource::glyphs(prefix + "{fontstack}/{range}.pbf", {{"Helvetica"}}, {0, 255}), expiredItem("glyph.pbf")); NetworkStatus::Set(NetworkStatus::Status::Offline); - Map map(test.backend, test.view.getSize(), 1, fileSource, test.threadPool, MapMode::Still); - map.setStyleURL(prefix + "style.json"); + test.map.getStyle().loadURL(prefix + "style.json"); test::checkImage("test/fixtures/map/offline", - test::render(map, test.view), + test.frontend.render(test.map), 0.0015, 0.1); NetworkStatus::Set(NetworkStatus::Status::Online); } -TEST(Map, SetStyleInvalidJSON) { - MapTest test; +TEST(Map, SetStyleDefaultCamera) { + MapTest<> test; + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); + EXPECT_DOUBLE_EQ(test.map.getZoom(), 0.0); + EXPECT_DOUBLE_EQ(test.map.getPitch(), 0.0); + EXPECT_DOUBLE_EQ(test.map.getBearing(), 0.0); + EXPECT_EQ(test.map.getLatLng(), LatLng {}); + + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty-zoomed.json")); + EXPECT_DOUBLE_EQ(test.map.getZoom(), 0.0); + + test.map.jumpTo(test.map.getStyle().getDefaultCamera()); + EXPECT_DOUBLE_EQ(test.map.getZoom(), 0.5); +} +TEST(Map, SetStyleInvalidJSON) { Log::setObserver(std::make_unique<FixtureLogObserver>()); bool fail = false; - test.backend.didFailLoadingMapCallback = [&]() { - fail = true; - }; { - Map map(test.backend, test.view.getSize(), 1, test.fileSource, test.threadPool, - MapMode::Still); - map.setStyleJSON("invalid"); + MapTest<> test; + test.observer.didFailLoadingMapCallback = [&]() { + fail = true; + }; + test.map.getStyle().loadJSON("invalid"); } EXPECT_TRUE(fail); @@ -160,7 +199,7 @@ TEST(Map, SetStyleInvalidJSON) { } TEST(Map, SetStyleInvalidURL) { - MapTest test; + MapTest<> test; test.fileSource.styleResponse = [] (const Resource&) { Response response; @@ -170,40 +209,36 @@ TEST(Map, SetStyleInvalidURL) { return response; }; - test.backend.didFailLoadingMapCallback = [&]() { + test.observer.didFailLoadingMapCallback = [&]() { test.runLoop.stop(); }; - Map map(test.backend, test.view.getSize(), 1, test.fileSource, test.threadPool, MapMode::Still); - map.setStyleURL("mapbox://bar"); + test.map.getStyle().loadURL("mapbox://bar"); test.runLoop.run(); } TEST(Map, DoubleStyleLoad) { - MapTest test; + MapTest<> test; - Map map(test.backend, test.view.getSize(), 1, test.fileSource, test.threadPool, MapMode::Still); - map.setStyleJSON(""); - map.setStyleJSON(""); + test.map.getStyle().loadJSON(""); + test.map.getStyle().loadJSON(""); } TEST(Map, StyleFresh) { // The map should not revalidate fresh styles. - MapTest test; - FakeFileSource fileSource; + MapTest<FakeFileSource> test; - Map map(test.backend, test.view.getSize(), 1, fileSource, test.threadPool, MapMode::Still); - map.setStyleURL("mapbox://styles/test"); - EXPECT_EQ(1u, fileSource.requests.size()); + test.map.getStyle().loadURL("mapbox://styles/test"); + EXPECT_EQ(1u, test.fileSource.requests.size()); Response response; response.data = std::make_shared<std::string>(util::read_file("test/fixtures/api/empty.json")); response.expires = Timestamp::max(); - fileSource.respond(Resource::Style, response); - EXPECT_EQ(0u, fileSource.requests.size()); + test.fileSource.respond(Resource::Style, response); + EXPECT_EQ(0u, test.fileSource.requests.size()); } TEST(Map, StyleExpired) { @@ -211,26 +246,24 @@ TEST(Map, StyleExpired) { using namespace std::chrono_literals; - MapTest test; - FakeFileSource fileSource; + MapTest<FakeFileSource> test; - Map map(test.backend, test.view.getSize(), 1, fileSource, test.threadPool, MapMode::Still); - map.setStyleURL("mapbox://styles/test"); - EXPECT_EQ(1u, fileSource.requests.size()); + test.map.getStyle().loadURL("mapbox://styles/test"); + EXPECT_EQ(1u, test.fileSource.requests.size()); Response response; response.data = std::make_shared<std::string>(util::read_file("test/fixtures/api/empty.json")); response.expires = util::now() - 1h; - fileSource.respond(Resource::Style, response); - EXPECT_EQ(1u, fileSource.requests.size()); + test.fileSource.respond(Resource::Style, response); + EXPECT_EQ(1u, test.fileSource.requests.size()); - map.addLayer(std::make_unique<style::BackgroundLayer>("bg")); - EXPECT_EQ(1u, fileSource.requests.size()); + test.map.getStyle().addLayer(std::make_unique<style::BackgroundLayer>("bg")); + EXPECT_EQ(1u, test.fileSource.requests.size()); - fileSource.respond(Resource::Style, response); - EXPECT_EQ(0u, fileSource.requests.size()); - EXPECT_NE(nullptr, map.getLayer("bg")); + test.fileSource.respond(Resource::Style, response); + EXPECT_EQ(0u, test.fileSource.requests.size()); + EXPECT_NE(nullptr, test.map.getStyle().getLayer("bg")); } TEST(Map, StyleExpiredWithAnnotations) { @@ -238,72 +271,111 @@ TEST(Map, StyleExpiredWithAnnotations) { using namespace std::chrono_literals; - MapTest test; - FakeFileSource fileSource; + MapTest<FakeFileSource> test; - Map map(test.backend, test.view.getSize(), 1, fileSource, test.threadPool, MapMode::Still); - map.setStyleURL("mapbox://styles/test"); - EXPECT_EQ(1u, fileSource.requests.size()); + test.map.getStyle().loadURL("mapbox://styles/test"); + EXPECT_EQ(1u, test.fileSource.requests.size()); Response response; response.data = std::make_shared<std::string>(util::read_file("test/fixtures/api/empty.json")); response.expires = util::now() - 1h; - fileSource.respond(Resource::Style, response); - EXPECT_EQ(1u, fileSource.requests.size()); + test.fileSource.respond(Resource::Style, response); + EXPECT_EQ(1u, test.fileSource.requests.size()); - map.addAnnotation(LineAnnotation { LineString<double> {{ { 0, 0 }, { 10, 10 } }} }); - EXPECT_EQ(1u, fileSource.requests.size()); + test.map.addAnnotation(LineAnnotation { LineString<double> {{ { 0, 0 }, { 10, 10 } }} }); + EXPECT_EQ(1u, test.fileSource.requests.size()); - fileSource.respond(Resource::Style, response); - EXPECT_EQ(1u, fileSource.requests.size()); + test.fileSource.respond(Resource::Style, response); + EXPECT_EQ(1u, test.fileSource.requests.size()); +} + +TEST(Map, StyleExpiredWithRender) { + // Rendering should not prevent revalidation of an expired style. + + using namespace std::chrono_literals; + + MapTest<FakeFileSource> test; + + test.map.getStyle().loadURL("mapbox://styles/test"); + EXPECT_EQ(1u, test.fileSource.requests.size()); + + Response response; + response.data = std::make_shared<std::string>(util::read_file("test/fixtures/api/empty.json")); + response.expires = util::now() - 1h; + + test.fileSource.respond(Resource::Style, response); + EXPECT_EQ(1u, test.fileSource.requests.size()); + + test.frontend.render(test.map); + EXPECT_EQ(1u, test.fileSource.requests.size()); + + test.fileSource.respond(Resource::Style, response); + EXPECT_EQ(1u, test.fileSource.requests.size()); } TEST(Map, StyleEarlyMutation) { // An early mutation should not prevent the initial style load. - MapTest test; - FakeFileSource fileSource; + MapTest<FakeFileSource> test; - Map map(test.backend, test.view.getSize(), 1, fileSource, test.threadPool, MapMode::Still); - map.setStyleURL("mapbox://styles/test"); - map.addLayer(std::make_unique<style::BackgroundLayer>("bg")); + test.map.getStyle().loadURL("mapbox://styles/test"); + test.map.getStyle().addLayer(std::make_unique<style::BackgroundLayer>("bg")); Response response; response.data = std::make_shared<std::string>(util::read_file("test/fixtures/api/water.json")); - fileSource.respond(Resource::Style, response); + test.fileSource.respond(Resource::Style, response); + + EXPECT_EQ(0u, test.fileSource.requests.size()); + EXPECT_NE(nullptr, test.map.getStyle().getLayer("water")); +} + +TEST(Map, MapLoadingSignal) { + MapTest<> test; + + bool emitted = false; + test.observer.onWillStartLoadingMapCallback = [&]() { + emitted = true; + }; + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); + EXPECT_TRUE(emitted); +} + +TEST(Map, MapLoadedSignal) { + MapTest<> test { 1, MapMode::Continuous }; + + test.observer.onDidFinishLoadingMapCallback = [&]() { + test.runLoop.stop(); + }; - EXPECT_EQ(0u, fileSource.requests.size()); - EXPECT_NE(nullptr, map.getLayer("water")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); + test.runLoop.run(); } TEST(Map, StyleLoadedSignal) { - MapTest test; - Map map(test.backend, test.view.getSize(), 1, test.fileSource, test.threadPool, MapMode::Still); + MapTest<> test; // The map should emit a signal on style loaded bool emitted = false; - test.backend.didFinishLoadingStyleCallback = [&]() { + test.observer.didFinishLoadingStyleCallback = [&]() { emitted = true; }; - map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); EXPECT_TRUE(emitted); // But not when the style couldn't be parsed emitted = false; - map.setStyleJSON("invalid"); + test.map.getStyle().loadJSON("invalid"); EXPECT_FALSE(emitted); } // Test for https://github.com/mapbox/mapbox-gl-native/issues/7902 TEST(Map, TEST_REQUIRES_SERVER(StyleNetworkErrorRetry)) { - MapTest test; - OnlineFileSource fileSource; + MapTest<OnlineFileSource> test; - Map map(test.backend, test.view.getSize(), 1, fileSource, test.threadPool, MapMode::Still); - map.setStyleURL("http://127.0.0.1:3000/style-fail-once-500"); + test.map.getStyle().loadURL("http://127.0.0.1:3000/style-fail-once-500"); - test.backend.didFinishLoadingStyleCallback = [&]() { + test.observer.didFinishLoadingStyleCallback = [&]() { test.runLoop.stop(); }; @@ -311,21 +383,19 @@ TEST(Map, TEST_REQUIRES_SERVER(StyleNetworkErrorRetry)) { } TEST(Map, TEST_REQUIRES_SERVER(StyleNotFound)) { - MapTest test; - OnlineFileSource fileSource; + MapTest<OnlineFileSource> test; - Map map(test.backend, test.view.getSize(), 1, fileSource, test.threadPool, MapMode::Still); - map.setStyleURL("http://127.0.0.1:3000/style-fail-once-404"); + test.map.getStyle().loadURL("http://127.0.0.1:3000/style-fail-once-404"); using namespace std::chrono_literals; util::Timer timer; // Not found errors should not trigger a retry like other errors. - test.backend.didFinishLoadingStyleCallback = [&]() { + test.observer.didFinishLoadingStyleCallback = [&]() { FAIL() << "Should not retry on not found!"; }; - test.backend.didFailLoadingMapCallback = [&]() { + test.observer.didFailLoadingMapCallback = [&]() { timer.start(Milliseconds(1100), 0s, [&] { test.runLoop.stop(); }); @@ -334,52 +404,48 @@ TEST(Map, TEST_REQUIRES_SERVER(StyleNotFound)) { test.runLoop.run(); // Should also not retry if the response has cache headers. - map.setStyleURL("http://127.0.0.1:3000/style-fail-once-404-cache"); + test.map.getStyle().loadURL("http://127.0.0.1:3000/style-fail-once-404-cache"); test.runLoop.run(); } TEST(Map, AddLayer) { - MapTest test; + MapTest<> test; - Map map(test.backend, test.view.getSize(), 1, test.fileSource, test.threadPool, MapMode::Still); - map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); auto layer = std::make_unique<BackgroundLayer>("background"); layer->setBackgroundColor({ { 1, 0, 0, 1 } }); - map.addLayer(std::move(layer)); + test.map.getStyle().addLayer(std::move(layer)); - test::checkImage("test/fixtures/map/add_layer", test::render(map, test.view)); + test::checkImage("test/fixtures/map/add_layer", test.frontend.render(test.map)); } TEST(Map, WithoutVAOExtension) { - MapTest test; - - test.backend.getContext().disableVAOExtension = true; + MapTest<DefaultFileSource> test { ":memory:", "test/fixtures/api/assets" }; - DefaultFileSource fileSource(":memory:", "test/fixtures/api/assets"); + BackendScope scope { *test.frontend.getBackend() }; + test.frontend.getBackend()->getContext().disableVAOExtension = true; - Map map(test.backend, test.view.getSize(), 1, fileSource, test.threadPool, MapMode::Still); - map.setStyleJSON(util::read_file("test/fixtures/api/water.json")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/water.json")); - test::checkImage("test/fixtures/map/no_vao", test::render(map, test.view), 0.002); + test::checkImage("test/fixtures/map/no_vao", test.frontend.render(test.map), 0.002); } TEST(Map, RemoveLayer) { - MapTest test; + MapTest<> test; - Map map(test.backend, test.view.getSize(), 1, test.fileSource, test.threadPool, MapMode::Still); - map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); + test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json")); auto layer = std::make_unique<BackgroundLayer>("background"); layer->setBackgroundColor({{ 1, 0, 0, 1 }}); - map.addLayer(std::move(layer)); - map.removeLayer("background"); + test.map.getStyle().addLayer(std::move(layer)); + test.map.getStyle().removeLayer("background"); - test::checkImage("test/fixtures/map/remove_layer", test::render(map, test.view)); + test::checkImage("test/fixtures/map/remove_layer", test.frontend.render(test.map)); } TEST(Map, DisabledSources) { - MapTest test; + MapTest<> test; // Always load the same image tile for raster layers. test.fileSource.response = [] (const Resource& res) -> optional<Response> { @@ -392,15 +458,14 @@ TEST(Map, DisabledSources) { return {}; }; - Map map(test.backend, test.view.getSize(), 1, test.fileSource, test.threadPool, MapMode::Still); - map.setZoom(1); + test.map.setZoom(1); // This stylesheet has two raster layers, one that starts at zoom 1, the other at zoom 0. // We first render a map at zoom level 1, which should show both layers (both are "visible" due // to an opacity of 0.5). Then, we are zooming back out to a zoom level of 0.5 and rerender. // The "raster1" layer should not be visible anymore since it has minzoom 1, while "raster2" // should still be there. Both layers have a distinct color through "raster-hue-rotate". - map.setStyleJSON(R"STYLE( + test.map.getStyle().loadJSON(R"STYLE( { "version": 8, "name": "Test", @@ -434,92 +499,15 @@ TEST(Map, DisabledSources) { } )STYLE"); - test::checkImage("test/fixtures/map/disabled_layers/first", test::render(map, test.view)); - map.setZoom(0.5); - test::checkImage("test/fixtures/map/disabled_layers/second", test::render(map, test.view)); -} - -TEST(Map, Classes) { - MapTest test; - - Map map(test.backend, test.view.getSize(), 1, test.fileSource, test.threadPool, MapMode::Still); - map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); - - EXPECT_FALSE(map.getTransitionOptions().duration); - - auto duration = mbgl::Duration(mbgl::Milliseconds(300)); - map.setTransitionOptions({ duration }); - EXPECT_EQ(map.getTransitionOptions().duration, duration); - - map.addClass("test"); - EXPECT_TRUE(map.hasClass("test")); - - map.removeClass("test"); - EXPECT_TRUE(map.getClasses().empty()); - - std::vector<std::string> classes = { "foo", "bar" }; - map.setClasses(classes); - EXPECT_FALSE(map.hasClass("test")); - EXPECT_TRUE(map.hasClass("foo")); - EXPECT_TRUE(map.hasClass("bar")); - - // Does nothing - same style JSON. - map.setStyleJSON(util::read_file("test/fixtures/api/empty.json")); - EXPECT_TRUE(map.hasClass("foo")); - EXPECT_EQ(map.getTransitionOptions().duration, duration); - - map.setStyleJSON(util::read_file("test/fixtures/api/water.json")); - EXPECT_TRUE(map.getClasses().empty()); - EXPECT_FALSE(map.getTransitionOptions().duration); -} - -TEST(Map, AddImage) { - MapTest test; - - Map map(test.backend, test.view.getSize(), 1, test.fileSource, test.threadPool, MapMode::Still); - auto decoded1 = decodeImage(util::read_file("test/fixtures/sprites/default_marker.png")); - auto decoded2 = decodeImage(util::read_file("test/fixtures/sprites/default_marker.png")); - auto image1 = std::make_unique<style::Image>(std::move(decoded1), 1.0); - auto image2 = std::make_unique<style::Image>(std::move(decoded2), 1.0); - - // No-op. - map.addImage("test-icon", std::move(image1)); - - map.setStyleJSON(util::read_file("test/fixtures/api/icon_style.json")); - map.addImage("test-icon", std::move(image2)); - test::checkImage("test/fixtures/map/add_icon", test::render(map, test.view)); -} - -TEST(Map, RemoveImage) { - MapTest test; - - Map map(test.backend, test.view.getSize(), 1, test.fileSource, test.threadPool, MapMode::Still); - auto decoded = decodeImage(util::read_file("test/fixtures/sprites/default_marker.png")); - auto image = std::make_unique<style::Image>(std::move(decoded), 1.0); - - map.setStyleJSON(util::read_file("test/fixtures/api/icon_style.json")); - map.addImage("test-icon", std::move(image)); - map.removeImage("test-icon"); - test::checkImage("test/fixtures/map/remove_icon", test::render(map, test.view)); -} - -TEST(Map, GetImage) { - MapTest test; - - Map map(test.backend, test.view.getSize(), 1, test.fileSource, test.threadPool, MapMode::Still); - auto decoded = decodeImage(util::read_file("test/fixtures/sprites/default_marker.png")); - auto image = std::make_unique<style::Image>(std::move(decoded), 1.0); - - map.setStyleJSON(util::read_file("test/fixtures/api/icon_style.json")); - map.addImage("test-icon", std::move(image)); - test::checkImage("test/fixtures/map/get_icon", map.getImage("test-icon")->image); + test::checkImage("test/fixtures/map/disabled_layers/first", test.frontend.render(test.map)); + test.map.setZoom(0.5); + test::checkImage("test/fixtures/map/disabled_layers/second", test.frontend.render(test.map)); } TEST(Map, DontLoadUnneededTiles) { - MapTest test; + MapTest<> test; - Map map(test.backend, test.view.getSize(), 1, test.fileSource, test.threadPool, MapMode::Still); - map.setStyleJSON(R"STYLE({ + test.map.getStyle().loadJSON(R"STYLE({ "sources": { "a": { "type": "vector", "tiles": [ "a/{z}/{x}/{y}" ] } }, @@ -557,65 +545,84 @@ TEST(Map, DontLoadUnneededTiles) { // Note: using z += 0.1 in the loop doesn't produce accurate floating point numbers. const double z = double(zoom) / 10; tiles.clear(); - map.setZoom(z); - test::render(map, test.view); + test.map.setZoom(z); + test.frontend.render(test.map); EXPECT_EQ(referenceTiles[z], tiles) << "zoom level " << z; } } - -class MockBackend : public HeadlessBackend { -public: - MockBackend(std::shared_ptr<HeadlessDisplay> display_) - : HeadlessBackend(display_) { - } - - std::function<void()> callback; - void invalidate() override { - if (callback) { - callback(); - } - } -}; - TEST(Map, TEST_DISABLED_ON_CI(ContinuousRendering)) { util::RunLoop runLoop; - MockBackend backend { test::sharedDisplay() }; - BackendScope scope { backend }; - OffscreenView view { backend.getContext() }; ThreadPool threadPool { 4 }; DefaultFileSource fileSource(":memory:", "test/fixtures/api/assets"); - Map map(backend, view.getSize(), 1, fileSource, threadPool, MapMode::Continuous); + float pixelRatio { 1 }; using namespace std::chrono_literals; util::Timer emergencyShutoff; emergencyShutoff.start(10s, 0s, [&] { - util::RunLoop::Get()->stop(); + runLoop.stop(); FAIL() << "Did not stop rendering"; }); util::Timer timer; - util::AsyncTask render{[&] { - if (map.isFullyLoaded()) { - // Abort the test after 1 second after the map loading fully. Note that a "fully loaded - // map" doesn't mean that we won't render anymore: we could still render fade in/fade - // out or other animations. - // If we are continuing to render indefinitely, the emergency shutoff above will trigger - // and the test will fail since the regular time will be constantly reset. - timer.start(1s, 0s, [&] { - util::RunLoop::Get()->stop(); - }); - } - BackendScope scope2(backend); - map.render(view); - }}; + HeadlessFrontend frontend(pixelRatio, fileSource, threadPool); - backend.callback = [&] { - render.send(); + StubMapObserver observer; + observer.didFinishRenderingFrame = [&] (MapObserver::RenderMode) { + // Start a timer that ends the test one second from now. If we are continuing to render + // indefinitely, the timer will be constantly restarted and never trigger. Instead, the + // emergency shutoff above will trigger, failing the test. + timer.start(1s, 0s, [&] { + runLoop.stop(); + }); }; - map.setStyleJSON(util::read_file("test/fixtures/api/water.json")); - util::RunLoop::Get()->run(); + Map map(frontend, observer, frontend.getSize(), pixelRatio, fileSource, threadPool, MapMode::Continuous); + map.getStyle().loadJSON(util::read_file("test/fixtures/api/water.json")); + + runLoop.run(); +} + +TEST(Map, NoContentTiles) { + MapTest<DefaultFileSource> test {":memory:", "."}; + + using namespace std::chrono_literals; + + // Insert a 204 No Content response for the 0/0/0 tile + Response response; + response.noContent = true; + response.expires = util::now() + 1h; + test.fileSource.put(Resource::tile("http://example.com/{z}-{x}-{y}.vector.pbf", 1.0, 0, 0, 0, + Tileset::Scheme::XYZ), + response); + + test.map.getStyle().loadJSON(R"STYLE({ + "version": 8, + "name": "Water", + "sources": { + "mapbox": { + "type": "vector", + "tiles": ["http://example.com/{z}-{x}-{y}.vector.pbf"] + } + }, + "layers": [{ + "id": "background", + "type": "background", + "paint": { + "background-color": "red" + } + }, { + "id": "water", + "type": "fill", + "source": "mapbox", + "source-layer": "water" + }] + })STYLE"); + + test::checkImage("test/fixtures/map/nocontent", + test.frontend.render(test.map), + 0.0015, + 0.1); } diff --git a/test/map/prefetch.test.cpp b/test/map/prefetch.test.cpp new file mode 100644 index 0000000000..5c9a22d1bf --- /dev/null +++ b/test/map/prefetch.test.cpp @@ -0,0 +1,89 @@ +#include <mbgl/test/util.hpp> +#include <mbgl/test/stub_file_source.hpp> + +#include <mbgl/map/map.hpp> +#include <mbgl/gl/headless_frontend.hpp> +#include <mbgl/storage/default_file_source.hpp> +#include <mbgl/style/style.hpp> +#include <mbgl/util/default_thread_pool.hpp> +#include <mbgl/util/image.hpp> +#include <mbgl/util/io.hpp> +#include <mbgl/util/run_loop.hpp> + +#include <algorithm> +#include <string> +#include <vector> + +using namespace mbgl; +using namespace mbgl::style; +using namespace std::literals::string_literals; + +TEST(Map, PrefetchTiles) { + util::RunLoop runLoop; + ThreadPool threadPool(4); + StubFileSource fileSource; + HeadlessFrontend frontend { { 512, 512 }, 1, fileSource, threadPool }; + Map map(frontend, MapObserver::nullObserver(), frontend.getSize(), 1, fileSource, threadPool, MapMode::Still); + + std::vector<int> tiles; + + fileSource.response = [&] (const Resource& res) -> optional<Response> { + Response response; + + auto zoom = std::stoi(res.url); + tiles.push_back(zoom); + + // Return a red tile for prefetched tiles or green to the actual tile. + // The end rendering result should be all green because the map is only + // considered fully rendered when only ideal tiles are shown. + if (zoom == int(map.getZoom()) + 1) { + response.data = std::make_shared<std::string>( + util::read_file("test/fixtures/map/prefetch/tile_green.png")); + } else { + response.data = std::make_shared<std::string>( + util::read_file("test/fixtures/map/prefetch/tile_red.png")); + } + + return { std::move(response) }; + }; + + auto checkTilesForZoom = [&](int zoom, const std::vector<int>& expected) { + tiles.clear(); + + // Force tile reloading. + map.getStyle().loadJSON(util::read_file("test/fixtures/map/prefetch/empty.json")); + map.getStyle().loadJSON(util::read_file("test/fixtures/map/prefetch/style.json")); + + map.setLatLngZoom({ 40.726989, -73.992857 }, zoom); // Manhattan + + // Should always render the ideal tiles (i.e. a green map) + test::checkImage("test/fixtures/map/prefetch", frontend.render(map)); + + ASSERT_TRUE(std::is_permutation(tiles.begin(), tiles.end(), expected.begin())); + ASSERT_FALSE(tiles.empty()); + }; + + // Check defaults, should be 4. + ASSERT_EQ(map.getPrefetchZoomDelta(), 4); + checkTilesForZoom(12, { 13, 13, 13, 13, 13, 13, 13, 13, 13, 9 }); + + // Setting it to 0 disables prefetching. + map.setPrefetchZoomDelta(0); + + // No prefetching, raster tiles will use ideal + // tiles instead of the actual zoom level, that is + // why the zoom levels for non-prefetched tiles are + // not the same. + checkTilesForZoom(10, { 11, 11, 11, 11, 11, 11, 11, 11, 11 }); + + map.setPrefetchZoomDelta(5); + checkTilesForZoom(12, { 13, 13, 13, 13, 13, 13, 13, 13, 13, 8 }); + + // Should clamp at `minzoom`. + map.setPrefetchZoomDelta(20); + checkTilesForZoom(10, { 11, 11, 11, 11, 11, 11, 11, 11, 11, 0 }); + + // Disabled again. + map.setPrefetchZoomDelta(0); + checkTilesForZoom(13, { 14, 14, 14, 14, 14, 14, 14, 14, 14 }); +} diff --git a/test/map/transform.test.cpp b/test/map/transform.test.cpp index aa49d250b6..11c2c1cc6b 100644 --- a/test/map/transform.test.cpp +++ b/test/map/transform.test.cpp @@ -28,6 +28,27 @@ TEST(Transform, InvalidZoom) { ASSERT_DOUBLE_EQ(0, transform.getLatLng().latitude()); ASSERT_DOUBLE_EQ(0, transform.getLatLng().longitude()); ASSERT_DOUBLE_EQ(1, transform.getZoom()); + + transform.setZoom(transform.getState().getMaxZoom() + 0.1); + ASSERT_DOUBLE_EQ(transform.getZoom(), transform.getState().getMaxZoom()); + + CameraOptions cameraOptions; + cameraOptions.center = LatLng { util::LATITUDE_MAX, util::LONGITUDE_MAX }; + cameraOptions.zoom = transform.getState().getMaxZoom(); + + // Executing flyTo with an empty size causes frameZoom to be NaN. + transform.flyTo(cameraOptions); + transform.updateTransitions(transform.getTransitionStart() + transform.getTransitionDuration()); + ASSERT_DOUBLE_EQ(transform.getZoom(), transform.getState().getMaxZoom()); + + // Executing flyTo with maximum zoom level to the same zoom level causes + // frameZoom to be bigger than maximum zoom. + transform.resize(Size { 100, 100 }); + transform.flyTo(cameraOptions); + transform.updateTransitions(transform.getTransitionStart() + transform.getTransitionDuration()); + + ASSERT_TRUE(transform.getState().valid()); + ASSERT_DOUBLE_EQ(transform.getState().getMaxZoom(), transform.getZoom()); } diff --git a/test/programs/symbol_program.test.cpp b/test/programs/symbol_program.test.cpp new file mode 100644 index 0000000000..62a2e58d7b --- /dev/null +++ b/test/programs/symbol_program.test.cpp @@ -0,0 +1,57 @@ +#include <mbgl/test/util.hpp> + +#include <mbgl/programs/symbol_program.hpp> + +using namespace mbgl; + +TEST(SymbolProgram, SymbolSizeBinder) { + auto binder = SymbolSizeBinder::create(5.0f, 12.0f, 0.0f); + auto uniformValues = binder->uniformValues(5.5f); + EXPECT_EQ(uniformValues.get<uniforms::u_is_size_zoom_constant>().t, true); + EXPECT_EQ(uniformValues.get<uniforms::u_is_size_feature_constant>().t, true); + EXPECT_EQ(uniformValues.get<uniforms::u_size>().t, 12.0f); + + binder = SymbolSizeBinder::create(1.0f, style::CameraFunction<float>(style::ExponentialStops<float>({ + {0.0f, 8.0f}, + {10.0f, 18.0f} + }, 1.0f)), 0.0f); + uniformValues = binder->uniformValues(1.5f); + EXPECT_EQ(uniformValues.get<uniforms::u_is_size_zoom_constant>().t, false); + EXPECT_EQ(uniformValues.get<uniforms::u_is_size_feature_constant>().t, true); + EXPECT_EQ(uniformValues.get<uniforms::u_size>().t, 9.5f); + + binder = SymbolSizeBinder::create(0.0f, style::CameraFunction<float>(style::ExponentialStops<float>({ + {1.0f, 8.0f}, + {11.0f, 18.0f} + }, 1.0f)), 0.0f); + uniformValues = binder->uniformValues(0.5f); + EXPECT_EQ(uniformValues.get<uniforms::u_is_size_zoom_constant>().t, false); + EXPECT_EQ(uniformValues.get<uniforms::u_is_size_feature_constant>().t, true); + EXPECT_EQ(uniformValues.get<uniforms::u_size>().t, 8.0f); + + binder = SymbolSizeBinder::create(12.0f, style::CameraFunction<float>(style::ExponentialStops<float>({ + {1.0f, 8.0f}, + {11.0f, 18.0f} + }, 1.0f)), 0.0f); + uniformValues = binder->uniformValues(12.5f); + EXPECT_EQ(uniformValues.get<uniforms::u_is_size_zoom_constant>().t, false); + EXPECT_EQ(uniformValues.get<uniforms::u_is_size_feature_constant>().t, true); + EXPECT_EQ(uniformValues.get<uniforms::u_size>().t, 18.0f); + + binder = SymbolSizeBinder::create(0.0f, style::SourceFunction<float>("x", style::ExponentialStops<float>({ + {1.0f, 8.0f}, + {11.0f, 18.0f} + }, 1.0f)), 0.0f); + uniformValues = binder->uniformValues(12.5f); + EXPECT_EQ(uniformValues.get<uniforms::u_is_size_zoom_constant>().t, true); + EXPECT_EQ(uniformValues.get<uniforms::u_is_size_feature_constant>().t, false); + + binder = SymbolSizeBinder::create(5.0f, style::CompositeFunction<float>("x", style::CompositeExponentialStops<float>({ + {1.0f, {{0.0f, 8.0f}, {100.0f, 18.0f}}}, + {11.0f, {{0.0f, 12.0f}, {100.0f, 24.9f}}} + }, 1.0f)), 0.0f); + uniformValues = binder->uniformValues(5.5f); + EXPECT_EQ(uniformValues.get<uniforms::u_is_size_zoom_constant>().t, false); + EXPECT_EQ(uniformValues.get<uniforms::u_is_size_feature_constant>().t, false); + EXPECT_EQ(uniformValues.get<uniforms::u_size_t>().t, 0.45f); +} diff --git a/test/renderer/backend_scope.test.cpp b/test/renderer/backend_scope.test.cpp index 6afd8f12ed..66cf88a9c6 100644 --- a/test/renderer/backend_scope.test.cpp +++ b/test/renderer/backend_scope.test.cpp @@ -1,14 +1,20 @@ #include <mbgl/test/util.hpp> -#include <mbgl/map/backend.hpp> -#include <mbgl/map/backend_scope.hpp> +#include <mbgl/renderer/renderer_backend.hpp> +#include <mbgl/renderer/backend_scope.hpp> #include <functional> using namespace mbgl; -class StubBackend: public Backend { +class StubRendererBackend: public RendererBackend { public: + void bind() override { + } + + mbgl::Size getFramebufferSize() const override { + return mbgl::Size{}; + } void activate() override { if (activateFunction) activateFunction(); @@ -22,8 +28,6 @@ public: if (updateAssumedStateFunction) updateAssumedStateFunction(); } - void invalidate() override {} - gl::ProcAddress initializeExtension(const char* ext) override { if (initializeExtensionFunction) { return initializeExtensionFunction(ext); @@ -45,7 +49,7 @@ TEST(BackendScope, SingleScope) { bool activated; bool deactivated; - StubBackend backend; + StubRendererBackend backend; backend.activateFunction = [&] { activated = true; }; backend.deactivateFunction = [&] { deactivated = true; }; @@ -63,7 +67,7 @@ TEST(BackendScope, NestedScopes) { int activated = 0; int deactivated = 0; - StubBackend backend; + StubRendererBackend backend; backend.activateFunction = [&] { activated++; }; backend.deactivateFunction = [&] { deactivated++; }; @@ -87,15 +91,15 @@ TEST(BackendScope, NestedScopes) { TEST(BackendScope, ChainedScopes) { bool activatedA = false; bool activatedB = false; - - StubBackend backendA; + + StubRendererBackend backendA; backendA.activateFunction = [&] { activatedA = true; }; backendA.deactivateFunction = [&] { activatedA = false; }; - - StubBackend backendB; + + StubRendererBackend backendB; backendB.activateFunction = [&] { activatedB = true; }; backendB.deactivateFunction = [&] { activatedB = false; }; - + { BackendScope scopeA { backendA }; ASSERT_TRUE(activatedA); @@ -107,7 +111,7 @@ TEST(BackendScope, ChainedScopes) { ASSERT_FALSE(activatedB); ASSERT_TRUE(activatedA); } - + ASSERT_FALSE(activatedA); ASSERT_FALSE(activatedB); } diff --git a/test/renderer/group_by_layout.test.cpp b/test/renderer/group_by_layout.test.cpp index 9c8e09e222..958f1bdf24 100644 --- a/test/renderer/group_by_layout.test.cpp +++ b/test/renderer/group_by_layout.test.cpp @@ -13,7 +13,7 @@ static std::vector<std::unique_ptr<RenderLayer>> toRenderLayers(const std::vecto std::vector<std::unique_ptr<RenderLayer>> result; result.reserve(layers.size()); for (auto& layer : layers) { - result.push_back(layer->baseImpl->createRenderLayer()); + result.push_back(RenderLayer::create(layer->baseImpl)); } return result; } diff --git a/test/renderer/image_manager.test.cpp b/test/renderer/image_manager.test.cpp new file mode 100644 index 0000000000..ebe1bcd72f --- /dev/null +++ b/test/renderer/image_manager.test.cpp @@ -0,0 +1,150 @@ +#include <mbgl/test/util.hpp> +#include <mbgl/test/fixture_log_observer.hpp> +#include <mbgl/test/stub_file_source.hpp> +#include <mbgl/test/stub_style_observer.hpp> + +#include <mbgl/renderer/image_manager.hpp> +#include <mbgl/sprite/sprite_parser.hpp> +#include <mbgl/style/image_impl.hpp> +#include <mbgl/util/io.hpp> +#include <mbgl/util/image.hpp> +#include <mbgl/util/run_loop.hpp> +#include <mbgl/util/default_thread_pool.hpp> +#include <mbgl/util/string.hpp> + +#include <utility> + +using namespace mbgl; + +TEST(ImageManager, Missing) { + ImageManager imageManager; + EXPECT_FALSE(imageManager.getImage("doesnotexist")); +} + +TEST(ImageManager, Basic) { + FixtureLog log; + ImageManager imageManager; + + auto images = parseSprite(util::read_file("test/fixtures/annotations/emerald.png"), + util::read_file("test/fixtures/annotations/emerald.json")); + for (auto& image : images) { + imageManager.addImage(image->baseImpl); + } + + auto metro = *imageManager.getPattern("metro"); + EXPECT_EQ(1, metro.tl()[0]); + EXPECT_EQ(1, metro.tl()[1]); + EXPECT_EQ(19, metro.br()[0]); + EXPECT_EQ(19, metro.br()[1]); + EXPECT_EQ(18, metro.displaySize()[0]); + EXPECT_EQ(18, metro.displaySize()[1]); + EXPECT_EQ(1.0f, metro.pixelRatio); + EXPECT_EQ(imageManager.getPixelSize(), imageManager.getAtlasImage().size); + + test::checkImage("test/fixtures/image_manager/basic", imageManager.getAtlasImage()); +} + +TEST(ImageManager, Updates) { + ImageManager imageManager; + + PremultipliedImage imageA({ 16, 12 }); + imageA.fill(255); + imageManager.addImage(makeMutable<style::Image::Impl>("one", std::move(imageA), 1)); + + auto a = *imageManager.getPattern("one"); + EXPECT_EQ(1, a.tl()[0]); + EXPECT_EQ(1, a.tl()[1]); + EXPECT_EQ(17, a.br()[0]); + EXPECT_EQ(13, a.br()[1]); + EXPECT_EQ(16, a.displaySize()[0]); + EXPECT_EQ(12, a.displaySize()[1]); + EXPECT_EQ(1.0f, a.pixelRatio); + test::checkImage("test/fixtures/image_manager/updates_before", imageManager.getAtlasImage()); + + PremultipliedImage imageB({ 5, 5 }); + imageA.fill(200); + imageManager.updateImage(makeMutable<style::Image::Impl>("one", std::move(imageB), 1)); + + auto b = *imageManager.getPattern("one"); + EXPECT_EQ(1, b.tl()[0]); + EXPECT_EQ(1, b.tl()[1]); + EXPECT_EQ(6, b.br()[0]); + EXPECT_EQ(6, b.br()[1]); + EXPECT_EQ(5, b.displaySize()[0]); + EXPECT_EQ(5, b.displaySize()[1]); + EXPECT_EQ(1.0f, b.pixelRatio); + test::checkImage("test/fixtures/image_manager/updates_after", imageManager.getAtlasImage()); +} + +TEST(ImageManager, AddRemove) { + FixtureLog log; + ImageManager imageManager; + + imageManager.addImage(makeMutable<style::Image::Impl>("one", PremultipliedImage({ 16, 16 }), 2)); + imageManager.addImage(makeMutable<style::Image::Impl>("two", PremultipliedImage({ 16, 16 }), 2)); + imageManager.addImage(makeMutable<style::Image::Impl>("three", PremultipliedImage({ 16, 16 }), 2)); + + imageManager.removeImage("one"); + imageManager.removeImage("two"); + + EXPECT_NE(nullptr, imageManager.getImage("three")); + EXPECT_EQ(nullptr, imageManager.getImage("two")); + EXPECT_EQ(nullptr, imageManager.getImage("four")); +} + +TEST(ImageManager, RemoveReleasesBinPackRect) { + FixtureLog log; + ImageManager imageManager; + + imageManager.addImage(makeMutable<style::Image::Impl>("big", PremultipliedImage({ 32, 32 }), 1)); + EXPECT_TRUE(imageManager.getImage("big")); + + imageManager.removeImage("big"); + + imageManager.addImage(makeMutable<style::Image::Impl>("big", PremultipliedImage({ 32, 32 }), 1)); + EXPECT_TRUE(imageManager.getImage("big")); + EXPECT_TRUE(log.empty()); +} + +class StubImageRequestor : public ImageRequestor { +public: + void onImagesAvailable(ImageMap images, uint64_t imageCorrelationID_) final { + if (imagesAvailable && imageCorrelationID == imageCorrelationID_) imagesAvailable(images); + } + + std::function<void (ImageMap)> imagesAvailable; + uint64_t imageCorrelationID = 0; +}; + +TEST(ImageManager, NotifiesRequestorWhenSpriteIsLoaded) { + ImageManager imageManager; + StubImageRequestor requestor; + bool notified = false; + + requestor.imagesAvailable = [&] (ImageMap) { + notified = true; + }; + + uint64_t imageCorrelationID = 0; + imageManager.getImages(requestor, std::make_pair(std::set<std::string> {"one"}, imageCorrelationID)); + ASSERT_FALSE(notified); + + imageManager.setLoaded(true); + ASSERT_TRUE(notified); +} + +TEST(ImageManager, NotifiesRequestorImmediatelyIfDependenciesAreSatisfied) { + ImageManager imageManager; + StubImageRequestor requestor; + bool notified = false; + + requestor.imagesAvailable = [&] (ImageMap) { + notified = true; + }; + + uint64_t imageCorrelationID = 0; + imageManager.addImage(makeMutable<style::Image::Impl>("one", PremultipliedImage({ 16, 16 }), 2)); + imageManager.getImages(requestor, std::make_pair(std::set<std::string> {"one"}, imageCorrelationID)); + + ASSERT_TRUE(notified); +} diff --git a/test/sprite/sprite_atlas.test.cpp b/test/sprite/sprite_atlas.test.cpp deleted file mode 100644 index 08388f0a93..0000000000 --- a/test/sprite/sprite_atlas.test.cpp +++ /dev/null @@ -1,373 +0,0 @@ -#include <mbgl/test/util.hpp> -#include <mbgl/test/fixture_log_observer.hpp> -#include <mbgl/test/stub_file_source.hpp> -#include <mbgl/test/stub_style_observer.hpp> - -#include <mbgl/sprite/sprite_atlas.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> -#include <mbgl/sprite/sprite_parser.hpp> -#include <mbgl/util/io.hpp> -#include <mbgl/util/image.hpp> -#include <mbgl/util/run_loop.hpp> -#include <mbgl/util/default_thread_pool.hpp> -#include <mbgl/util/string.hpp> - -#include <utility> - -using namespace mbgl; - -TEST(SpriteAtlas, Basic) { - FixtureLog log; - SpriteAtlas atlas({ 63, 112 }, 1); - - auto images = parseSprite(util::read_file("test/fixtures/annotations/emerald.png"), - util::read_file("test/fixtures/annotations/emerald.json")); - for (auto& pair : images) { - atlas.addImage(pair.first, std::move(pair.second)); - } - - EXPECT_EQ(1.0f, atlas.getPixelRatio()); - EXPECT_EQ(63u, atlas.getSize().width); - EXPECT_EQ(112u, atlas.getSize().height); - - auto metro = *atlas.getIcon("metro"); - float imagePixelRatio = metro.relativePixelRatio * atlas.getPixelRatio(); - EXPECT_EQ(0, metro.pos.x); - EXPECT_EQ(0, metro.pos.y); - EXPECT_EQ(20, metro.pos.w); - EXPECT_EQ(20, metro.pos.h); - EXPECT_EQ(18, metro.width); - EXPECT_EQ(18, metro.height); - EXPECT_EQ(18u, metro.width * imagePixelRatio); - EXPECT_EQ(18u, metro.height * imagePixelRatio); - EXPECT_EQ(1.0f, imagePixelRatio); - - - EXPECT_EQ(63u, atlas.getAtlasImage().size.width); - EXPECT_EQ(112u, atlas.getAtlasImage().size.height); - - auto pos = *atlas.getIcon("metro"); - EXPECT_DOUBLE_EQ(18, pos.size[0]); - EXPECT_DOUBLE_EQ(18, pos.size[1]); - EXPECT_DOUBLE_EQ(1.0f / 63, pos.tl[0]); - EXPECT_DOUBLE_EQ(1.0f / 112, pos.tl[1]); - EXPECT_DOUBLE_EQ(19.0f / 63, pos.br[0]); - EXPECT_DOUBLE_EQ(19.0f / 112, pos.br[1]); - - auto missing = atlas.getIcon("doesnotexist"); - EXPECT_FALSE(missing); - - EXPECT_EQ(1u, log.count({ - EventSeverity::Info, - Event::Sprite, - int64_t(-1), - "Can't find sprite named 'doesnotexist'", - })); - - // Different wrapping mode produces different image. - auto metro2 = *atlas.getPattern("metro"); - EXPECT_EQ(20, metro2.pos.x); - EXPECT_EQ(0, metro2.pos.y); - EXPECT_EQ(20, metro2.pos.w); - EXPECT_EQ(20, metro2.pos.h); - - test::checkImage("test/fixtures/sprite_atlas/basic", atlas.getAtlasImage()); -} - -TEST(SpriteAtlas, Size) { - SpriteAtlas atlas({ 63, 112 }, 1.4); - - auto images = parseSprite(util::read_file("test/fixtures/annotations/emerald.png"), - util::read_file("test/fixtures/annotations/emerald.json")); - for (auto& pair : images) { - atlas.addImage(pair.first, std::move(pair.second)); - } - - EXPECT_DOUBLE_EQ(1.4f, atlas.getPixelRatio()); - EXPECT_EQ(63u, atlas.getSize().width); - EXPECT_EQ(112u, atlas.getSize().height); - - auto metro = *atlas.getIcon("metro"); - float imagePixelRatio = metro.relativePixelRatio * atlas.getPixelRatio(); - EXPECT_EQ(0, metro.pos.x); - EXPECT_EQ(0, metro.pos.y); - EXPECT_EQ(16, metro.pos.w); - EXPECT_EQ(16, metro.pos.h); - EXPECT_EQ(18, metro.width); - EXPECT_EQ(18, metro.height); - EXPECT_EQ(18u, metro.width * imagePixelRatio); - EXPECT_EQ(18u, metro.height * imagePixelRatio); - EXPECT_EQ(1.0f, imagePixelRatio); - - // Now the image was created lazily. - EXPECT_EQ(89u, atlas.getAtlasImage().size.width); - EXPECT_EQ(157u, atlas.getAtlasImage().size.height); - - test::checkImage("test/fixtures/sprite_atlas/size", atlas.getAtlasImage()); -} - -TEST(SpriteAtlas, Updates) { - SpriteAtlas atlas({ 32, 32 }, 1); - - EXPECT_EQ(1.0f, atlas.getPixelRatio()); - EXPECT_EQ(32u, atlas.getSize().width); - EXPECT_EQ(32u, atlas.getSize().height); - - atlas.addImage("one", std::make_unique<style::Image>(PremultipliedImage({ 16, 12 }), 1)); - auto one = *atlas.getIcon("one"); - float imagePixelRatio = one.relativePixelRatio * atlas.getPixelRatio(); - EXPECT_EQ(0, one.pos.x); - EXPECT_EQ(0, one.pos.y); - EXPECT_EQ(20, one.pos.w); - EXPECT_EQ(16, one.pos.h); - EXPECT_EQ(16, one.width); - EXPECT_EQ(12, one.height); - EXPECT_EQ(16u, one.width * imagePixelRatio); - EXPECT_EQ(12u, one.height * imagePixelRatio); - EXPECT_EQ(1.0f, imagePixelRatio); - - // Now the image was created lazily. - EXPECT_EQ(32u, atlas.getAtlasImage().size.width); - EXPECT_EQ(32u, atlas.getAtlasImage().size.height); - - test::checkImage("test/fixtures/sprite_atlas/updates_before", atlas.getAtlasImage()); - - // Update image - PremultipliedImage image2({ 16, 12 }); - for (size_t i = 0; i < image2.bytes(); i++) { - image2.data.get()[i] = 255; - } - atlas.addImage("one", std::make_unique<style::Image>(std::move(image2), 1)); - - test::checkImage("test/fixtures/sprite_atlas/updates_after", atlas.getAtlasImage()); -} - -TEST(SpriteAtlas, AddRemove) { - FixtureLog log; - SpriteAtlas atlas({ 32, 32 }, 1); - - atlas.addImage("one", std::make_unique<style::Image>(PremultipliedImage({ 16, 16 }), 2)); - atlas.addImage("two", std::make_unique<style::Image>(PremultipliedImage({ 16, 16 }), 2)); - atlas.addImage("three", std::make_unique<style::Image>(PremultipliedImage({ 16, 16 }), 2)); - - atlas.removeImage("one"); - atlas.removeImage("two"); - - EXPECT_NE(nullptr, atlas.getImage("three")); - EXPECT_EQ(nullptr, atlas.getImage("two")); - EXPECT_EQ(nullptr, atlas.getImage("four")); - - EXPECT_EQ(1u, log.count({ - EventSeverity::Info, - Event::Sprite, - int64_t(-1), - "Can't find sprite named 'two'", - })); - EXPECT_EQ(1u, log.count({ - EventSeverity::Info, - Event::Sprite, - int64_t(-1), - "Can't find sprite named 'four'", - })); -} - -TEST(SpriteAtlas, RemoveReleasesBinPackRect) { - FixtureLog log; - - SpriteAtlas atlas({ 36, 36 }, 1); - - atlas.addImage("big", std::make_unique<style::Image>(PremultipliedImage({ 32, 32 }), 1)); - EXPECT_TRUE(atlas.getIcon("big")); - - atlas.removeImage("big"); - - atlas.addImage("big", std::make_unique<style::Image>(PremultipliedImage({ 32, 32 }), 1)); - EXPECT_TRUE(atlas.getIcon("big")); - EXPECT_TRUE(log.empty()); -} - -TEST(SpriteAtlas, OtherPixelRatio) { - FixtureLog log; - SpriteAtlas atlas({ 32, 32 }, 1); - - // Adding mismatched sprite image - atlas.addImage("one", std::make_unique<style::Image>(PremultipliedImage({ 8, 8 }), 2)); -} - -TEST(SpriteAtlas, Replace) { - FixtureLog log; - SpriteAtlas atlas({ 32, 32 }, 1); - - atlas.addImage("sprite", std::make_unique<style::Image>(PremultipliedImage({ 16, 16 }), 2)); - auto image = atlas.getImage("sprite"); - atlas.addImage("sprite", std::make_unique<style::Image>(PremultipliedImage({ 16, 16 }), 2)); - EXPECT_NE(image, atlas.getImage("sprite")); -} - -TEST(SpriteAtlas, ReplaceWithDifferentDimensions) { - FixtureLog log; - SpriteAtlas atlas({ 32, 32 }, 1); - - atlas.addImage("sprite", std::make_unique<style::Image>(PremultipliedImage({ 16, 16 }), 2)); - atlas.addImage("sprite", std::make_unique<style::Image>(PremultipliedImage({ 18, 18 }), 2)); - - EXPECT_EQ(1u, log.count({ - EventSeverity::Warning, - Event::Sprite, - int64_t(-1), - "Can't change sprite dimensions for 'sprite'", - })); -} - -class SpriteAtlasTest { -public: - SpriteAtlasTest() = default; - - util::RunLoop loop; - StubFileSource fileSource; - StubStyleObserver observer; - ThreadPool threadPool { 1 }; - SpriteAtlas spriteAtlas{ { 32, 32 }, 1 }; - - void run() { - // Squelch logging. - Log::setObserver(std::make_unique<Log::NullObserver>()); - - spriteAtlas.setObserver(&observer); - spriteAtlas.load("test/fixtures/resources/sprite", threadPool, fileSource); - - loop.run(); - } - - void end() { - loop.stop(); - } -}; - -Response successfulSpriteImageResponse(const Resource& resource) { - EXPECT_EQ("test/fixtures/resources/sprite.png", resource.url); - Response response; - response.data = std::make_unique<std::string>(util::read_file(resource.url)); - return response; -} - -Response successfulSpriteJSONResponse(const Resource& resource) { - EXPECT_EQ("test/fixtures/resources/sprite.json", resource.url); - Response response; - response.data = std::make_unique<std::string>(util::read_file(resource.url)); - return response; -} - -Response failedSpriteResponse(const Resource&) { - Response response; - response.error = std::make_unique<Response::Error>( - Response::Error::Reason::Other, - "Failed by the test case"); - return response; -} - -Response corruptSpriteResponse(const Resource&) { - Response response; - response.data = std::make_unique<std::string>("CORRUPT"); - return response; -} - -TEST(SpriteAtlas, LoadingSuccess) { - SpriteAtlasTest test; - - test.fileSource.spriteImageResponse = successfulSpriteImageResponse; - test.fileSource.spriteJSONResponse = successfulSpriteJSONResponse; - - test.observer.spriteError = [&] (std::exception_ptr error) { - FAIL() << util::toString(error); - test.end(); - }; - - test.observer.spriteLoaded = [&] () { - EXPECT_EQ(1.0, test.spriteAtlas.getPixelRatio()); - EXPECT_TRUE(test.spriteAtlas.isLoaded()); - test.end(); - }; - - test.run(); -} - -TEST(SpriteAtlas, JSONLoadingFail) { - SpriteAtlasTest test; - - test.fileSource.spriteImageResponse = successfulSpriteImageResponse; - test.fileSource.spriteJSONResponse = failedSpriteResponse; - - test.observer.spriteError = [&] (std::exception_ptr error) { - EXPECT_TRUE(error != nullptr); - EXPECT_EQ("Failed by the test case", util::toString(error)); - EXPECT_FALSE(test.spriteAtlas.isLoaded()); - test.end(); - }; - - test.run(); -} - -TEST(SpriteAtlas, ImageLoadingFail) { - SpriteAtlasTest test; - - test.fileSource.spriteImageResponse = failedSpriteResponse; - test.fileSource.spriteJSONResponse = successfulSpriteJSONResponse; - - test.observer.spriteError = [&] (std::exception_ptr error) { - EXPECT_TRUE(error != nullptr); - EXPECT_EQ("Failed by the test case", util::toString(error)); - EXPECT_FALSE(test.spriteAtlas.isLoaded()); - test.end(); - }; - - test.run(); -} - -TEST(SpriteAtlas, JSONLoadingCorrupted) { - SpriteAtlasTest test; - - test.fileSource.spriteImageResponse = successfulSpriteImageResponse; - test.fileSource.spriteJSONResponse = corruptSpriteResponse; - - test.observer.spriteError = [&] (std::exception_ptr error) { - EXPECT_TRUE(error != nullptr); - EXPECT_EQ("Failed to parse JSON: Invalid value. at offset 0", util::toString(error)); - EXPECT_FALSE(test.spriteAtlas.isLoaded()); - test.end(); - }; - - test.run(); -} - -TEST(SpriteAtlas, ImageLoadingCorrupted) { - SpriteAtlasTest test; - - test.fileSource.spriteImageResponse = corruptSpriteResponse; - test.fileSource.spriteJSONResponse = successfulSpriteJSONResponse; - - test.observer.spriteError = [&] (std::exception_ptr error) { - EXPECT_TRUE(error != nullptr); - // Not asserting on platform-specific error text. - EXPECT_FALSE(test.spriteAtlas.isLoaded()); - test.end(); - }; - - test.run(); -} - -TEST(SpriteAtlas, LoadingCancel) { - SpriteAtlasTest test; - - test.fileSource.spriteImageResponse = - test.fileSource.spriteJSONResponse = [&] (const Resource&) { - test.end(); - return optional<Response>(); - }; - - test.observer.spriteLoaded = [&] () { - FAIL() << "Should never be called"; - }; - - test.run(); -} diff --git a/test/sprite/sprite_loader.test.cpp b/test/sprite/sprite_loader.test.cpp new file mode 100644 index 0000000000..3691572265 --- /dev/null +++ b/test/sprite/sprite_loader.test.cpp @@ -0,0 +1,179 @@ +#include <mbgl/test/util.hpp> +#include <mbgl/test/fixture_log_observer.hpp> +#include <mbgl/test/stub_file_source.hpp> + +#include <mbgl/sprite/sprite_loader.hpp> +#include <mbgl/sprite/sprite_loader_observer.hpp> +#include <mbgl/sprite/sprite_parser.hpp> +#include <mbgl/util/io.hpp> +#include <mbgl/util/image.hpp> +#include <mbgl/util/run_loop.hpp> +#include <mbgl/util/default_thread_pool.hpp> +#include <mbgl/util/string.hpp> + +#include <utility> + +using namespace mbgl; +using namespace mbgl::style; + +class StubSpriteLoaderObserver : public SpriteLoaderObserver { +public: + void onSpriteLoaded(std::vector<std::unique_ptr<style::Image>>&& images) override { + if (spriteLoaded) spriteLoaded(std::move(images)); + } + + void onSpriteError(std::exception_ptr error) override { + if (spriteError) spriteError(error); + } + + std::function<void (std::vector<std::unique_ptr<style::Image>>&&)> spriteLoaded; + std::function<void (std::exception_ptr)> spriteError; +}; + +class SpriteLoaderTest { +public: + SpriteLoaderTest() = default; + + util::RunLoop loop; + StubFileSource fileSource; + StubSpriteLoaderObserver observer; + ThreadPool threadPool { 1 }; + SpriteLoader spriteLoader{ 1 }; + + void run() { + // Squelch logging. + Log::setObserver(std::make_unique<Log::NullObserver>()); + + spriteLoader.setObserver(&observer); + spriteLoader.load("test/fixtures/resources/sprite", threadPool, fileSource); + + loop.run(); + } + + void end() { + loop.stop(); + } +}; + +Response successfulSpriteImageResponse(const Resource& resource) { + EXPECT_EQ("test/fixtures/resources/sprite.png", resource.url); + Response response; + response.data = std::make_unique<std::string>(util::read_file(resource.url)); + return response; +} + +Response successfulSpriteJSONResponse(const Resource& resource) { + EXPECT_EQ("test/fixtures/resources/sprite.json", resource.url); + Response response; + response.data = std::make_unique<std::string>(util::read_file(resource.url)); + return response; +} + +Response failedSpriteResponse(const Resource&) { + Response response; + response.error = std::make_unique<Response::Error>( + Response::Error::Reason::Other, + "Failed by the test case"); + return response; +} + +Response corruptSpriteResponse(const Resource&) { + Response response; + response.data = std::make_unique<std::string>("CORRUPT"); + return response; +} + +TEST(SpriteLoader, LoadingSuccess) { + SpriteLoaderTest test; + + test.fileSource.spriteImageResponse = successfulSpriteImageResponse; + test.fileSource.spriteJSONResponse = successfulSpriteJSONResponse; + + test.observer.spriteError = [&] (std::exception_ptr error) { + FAIL() << util::toString(error); + test.end(); + }; + + test.observer.spriteLoaded = [&] (std::vector<std::unique_ptr<style::Image>>&& images) { + EXPECT_EQ(images.size(), 367u); + test.end(); + }; + + test.run(); +} + +TEST(SpriteLoader, JSONLoadingFail) { + SpriteLoaderTest test; + + test.fileSource.spriteImageResponse = successfulSpriteImageResponse; + test.fileSource.spriteJSONResponse = failedSpriteResponse; + + test.observer.spriteError = [&] (std::exception_ptr error) { + EXPECT_TRUE(error != nullptr); + EXPECT_EQ("Failed by the test case", util::toString(error)); + test.end(); + }; + + test.run(); +} + +TEST(SpriteLoader, ImageLoadingFail) { + SpriteLoaderTest test; + + test.fileSource.spriteImageResponse = failedSpriteResponse; + test.fileSource.spriteJSONResponse = successfulSpriteJSONResponse; + + test.observer.spriteError = [&] (std::exception_ptr error) { + EXPECT_TRUE(error != nullptr); + EXPECT_EQ("Failed by the test case", util::toString(error)); + test.end(); + }; + + test.run(); +} + +TEST(SpriteLoader, JSONLoadingCorrupted) { + SpriteLoaderTest test; + + test.fileSource.spriteImageResponse = successfulSpriteImageResponse; + test.fileSource.spriteJSONResponse = corruptSpriteResponse; + + test.observer.spriteError = [&] (std::exception_ptr error) { + EXPECT_TRUE(error != nullptr); + EXPECT_EQ("Failed to parse JSON: Invalid value. at offset 0", util::toString(error)); + test.end(); + }; + + test.run(); +} + +TEST(SpriteLoader, ImageLoadingCorrupted) { + SpriteLoaderTest test; + + test.fileSource.spriteImageResponse = corruptSpriteResponse; + test.fileSource.spriteJSONResponse = successfulSpriteJSONResponse; + + test.observer.spriteError = [&] (std::exception_ptr error) { + EXPECT_TRUE(error != nullptr); + // Not asserting on platform-specific error text. + test.end(); + }; + + test.run(); +} + +TEST(SpriteLoader, LoadingCancel) { + SpriteLoaderTest test; + + test.fileSource.spriteImageResponse = + test.fileSource.spriteJSONResponse = [&] (const Resource&) { + test.end(); + return optional<Response>(); + }; + + test.observer.spriteLoaded = [&] (const std::vector<std::unique_ptr<style::Image>>&) { + FAIL() << "Should never be called"; + }; + + test.run(); +} diff --git a/test/sprite/sprite_parser.test.cpp b/test/sprite/sprite_parser.test.cpp index bb8e71db95..529e4c75e8 100644 --- a/test/sprite/sprite_parser.test.cpp +++ b/test/sprite/sprite_parser.test.cpp @@ -27,19 +27,19 @@ TEST(Sprite, SpriteImageCreationInvalid) { ASSERT_EQ(200u, image_1x.size.width); ASSERT_EQ(299u, image_1x.size.height); - ASSERT_EQ(nullptr, createStyleImage(image_1x, 0, 0, 0, 16, 1, false)); // width == 0 - ASSERT_EQ(nullptr, createStyleImage(image_1x, 0, 0, 16, 0, 1, false)); // height == 0 - ASSERT_EQ(nullptr, createStyleImage(image_1x, 0, 0, -1, 16, 1, false)); // width < 0 - ASSERT_EQ(nullptr, createStyleImage(image_1x, 0, 0, 16, -1, 1, false)); // height < 0 - ASSERT_EQ(nullptr, createStyleImage(image_1x, 0, 0, 1, 1, 0, false)); // ratio == 0 - ASSERT_EQ(nullptr, createStyleImage(image_1x, 0, 0, 1, 1, -1, false)); // ratio < 0 - ASSERT_EQ(nullptr, createStyleImage(image_1x, 0, 0, 1, 1, 23, false)); // ratio too large - ASSERT_EQ(nullptr, createStyleImage(image_1x, 0, 0, 2048, 16, 1, false)); // too wide - ASSERT_EQ(nullptr, createStyleImage(image_1x, 0, 0, 16, 1025, 1, false)); // too tall - ASSERT_EQ(nullptr, createStyleImage(image_1x, -1, 0, 16, 16, 1, false)); // srcX < 0 - ASSERT_EQ(nullptr, createStyleImage(image_1x, 0, -1, 16, 16, 1, false)); // srcY < 0 - ASSERT_EQ(nullptr, createStyleImage(image_1x, 0, 0, image_1x.size.width + 1, 16, 1, false)); // right edge out of bounds - ASSERT_EQ(nullptr, createStyleImage(image_1x, 0, 0, 16, image_1x.size.height + 1, 1, false)); // bottom edge out of bounds + ASSERT_EQ(nullptr, createStyleImage("test", image_1x, 0, 0, 0, 16, 1, false)); // width == 0 + ASSERT_EQ(nullptr, createStyleImage("test", image_1x, 0, 0, 16, 0, 1, false)); // height == 0 + ASSERT_EQ(nullptr, createStyleImage("test", image_1x, 0, 0, -1, 16, 1, false)); // width < 0 + ASSERT_EQ(nullptr, createStyleImage("test", image_1x, 0, 0, 16, -1, 1, false)); // height < 0 + ASSERT_EQ(nullptr, createStyleImage("test", image_1x, 0, 0, 1, 1, 0, false)); // ratio == 0 + ASSERT_EQ(nullptr, createStyleImage("test", image_1x, 0, 0, 1, 1, -1, false)); // ratio < 0 + ASSERT_EQ(nullptr, createStyleImage("test", image_1x, 0, 0, 1, 1, 23, false)); // ratio too large + ASSERT_EQ(nullptr, createStyleImage("test", image_1x, 0, 0, 2048, 16, 1, false)); // too wide + ASSERT_EQ(nullptr, createStyleImage("test", image_1x, 0, 0, 16, 1025, 1, false)); // too tall + ASSERT_EQ(nullptr, createStyleImage("test", image_1x, -1, 0, 16, 16, 1, false)); // srcX < 0 + ASSERT_EQ(nullptr, createStyleImage("test", image_1x, 0, -1, 16, 16, 1, false)); // srcY < 0 + ASSERT_EQ(nullptr, createStyleImage("test", image_1x, 0, 0, image_1x.size.width + 1, 16, 1, false)); // right edge out of bounds + ASSERT_EQ(nullptr, createStyleImage("test", image_1x, 0, 0, 16, image_1x.size.height + 1, 1, false)); // bottom edge out of bounds EXPECT_EQ(1u, log.count({ EventSeverity::Error, @@ -141,15 +141,13 @@ TEST(Sprite, SpriteImageCreation1x) { ASSERT_EQ(299u, image_1x.size.height); { // "museum_icon":{"x":177,"y":187,"width":18,"height":18,"pixelRatio":1,"sdf":false} - const auto sprite = createStyleImage(image_1x, 177, 187, 18, 18, 1, false); + const auto sprite = createStyleImage("test", image_1x, 177, 187, 18, 18, 1, false); ASSERT_TRUE(sprite.get()); - EXPECT_EQ(18, sprite->getWidth()); - EXPECT_EQ(18, sprite->getHeight()); - EXPECT_EQ(18u, sprite->image.size.width); - EXPECT_EQ(18u, sprite->image.size.height); - EXPECT_EQ(1, sprite->pixelRatio); + EXPECT_EQ(18u, sprite->getImage().size.width); + EXPECT_EQ(18u, sprite->getImage().size.height); + EXPECT_EQ(1, sprite->getPixelRatio()); EXPECT_EQ(readImage("test/fixtures/annotations/result-spriteimagecreation1x-museum.png"), - sprite->image); + sprite->getImage()); } } @@ -157,41 +155,35 @@ TEST(Sprite, SpriteImageCreation2x) { const PremultipliedImage image_2x = decodeImage(util::read_file("test/fixtures/annotations/emerald@2x.png")); // "museum_icon":{"x":354,"y":374,"width":36,"height":36,"pixelRatio":2,"sdf":false} - const auto sprite = createStyleImage(image_2x, 354, 374, 36, 36, 2, false); + const auto sprite = createStyleImage("test", image_2x, 354, 374, 36, 36, 2, false); ASSERT_TRUE(sprite.get()); - EXPECT_EQ(18, sprite->getWidth()); - EXPECT_EQ(18, sprite->getHeight()); - EXPECT_EQ(36u, sprite->image.size.width); - EXPECT_EQ(36u, sprite->image.size.height); - EXPECT_EQ(2, sprite->pixelRatio); + EXPECT_EQ(36u, sprite->getImage().size.width); + EXPECT_EQ(36u, sprite->getImage().size.height); + EXPECT_EQ(2, sprite->getPixelRatio()); EXPECT_EQ(readImage("test/fixtures/annotations/result-spriteimagecreation2x.png"), - sprite->image); + sprite->getImage()); } TEST(Sprite, SpriteImageCreation1_5x) { const PremultipliedImage image_2x = decodeImage(util::read_file("test/fixtures/annotations/emerald@2x.png")); // "museum_icon":{"x":354,"y":374,"width":36,"height":36,"pixelRatio":2,"sdf":false} - const auto sprite = createStyleImage(image_2x, 354, 374, 36, 36, 1.5, false); + const auto sprite = createStyleImage("test", image_2x, 354, 374, 36, 36, 1.5, false); ASSERT_TRUE(sprite.get()); - EXPECT_EQ(24, sprite->getWidth()); - EXPECT_EQ(24, sprite->getHeight()); - EXPECT_EQ(36u, sprite->image.size.width); - EXPECT_EQ(36u, sprite->image.size.height); - EXPECT_EQ(1.5, sprite->pixelRatio); + EXPECT_EQ(36u, sprite->getImage().size.width); + EXPECT_EQ(36u, sprite->getImage().size.height); + EXPECT_EQ(1.5, sprite->getPixelRatio()); EXPECT_EQ(readImage("test/fixtures/annotations/result-spriteimagecreation1_5x-museum.png"), - sprite->image); + sprite->getImage()); // "hospital_icon":{"x":314,"y":518,"width":36,"height":36,"pixelRatio":2,"sdf":false} - const auto sprite2 = createStyleImage(image_2x, 314, 518, 35, 35, 1.5, false); + const auto sprite2 = createStyleImage("test", image_2x, 314, 518, 35, 35, 1.5, false); ASSERT_TRUE(sprite2.get()); - EXPECT_EQ(float(35 / 1.5), sprite2->getWidth()); - EXPECT_EQ(float(35 / 1.5), sprite2->getHeight()); - EXPECT_EQ(35u, sprite2->image.size.width); - EXPECT_EQ(35u, sprite2->image.size.height); - EXPECT_EQ(1.5, sprite2->pixelRatio); + EXPECT_EQ(35u, sprite2->getImage().size.width); + EXPECT_EQ(35u, sprite2->getImage().size.height); + EXPECT_EQ(1.5, sprite2->getPixelRatio()); EXPECT_EQ(readImage("test/fixtures/annotations/result-spriteimagecreation1_5x-hospital.png"), - sprite2->image); + sprite2->getImage()); } TEST(Sprite, SpriteParsing) { @@ -202,7 +194,7 @@ TEST(Sprite, SpriteParsing) { std::set<std::string> names; std::transform(images.begin(), images.end(), std::inserter(names, names.begin()), - [](const auto& pair) { return pair.first; }); + [](const auto& image) { return image->getID(); }); EXPECT_EQ(std::set<std::string>({ "airfield_icon", "airport_icon", @@ -280,13 +272,11 @@ TEST(Sprite, SpriteParsing) { names); { - auto& sprite = images.find("generic-metro")->second; - EXPECT_EQ(18, sprite->getWidth()); - EXPECT_EQ(18, sprite->getHeight()); - EXPECT_EQ(18u, sprite->image.size.width); - EXPECT_EQ(18u, sprite->image.size.height); - EXPECT_EQ(1, sprite->pixelRatio); - EXPECT_EQ(readImage("test/fixtures/annotations/result-spriteparsing.png"), sprite->image); + auto& sprite = *std::find_if(images.begin(), images.end(), [] (const auto& image) { return image->getID() == "generic-metro"; }); + EXPECT_EQ(18u, sprite->getImage().size.width); + EXPECT_EQ(18u, sprite->getImage().size.height); + EXPECT_EQ(1, sprite->getPixelRatio()); + EXPECT_EQ(readImage("test/fixtures/annotations/result-spriteparsing.png"), sprite->getImage()); } } diff --git a/test/src/mbgl/test/conversion_stubs.hpp b/test/src/mbgl/test/conversion_stubs.hpp index e6581c5e53..30395ddb97 100644 --- a/test/src/mbgl/test/conversion_stubs.hpp +++ b/test/src/mbgl/test/conversion_stubs.hpp @@ -17,6 +17,7 @@ using ValueMap = std::unordered_map<std::string, Value>; using ValueVector = std::vector<Value>; class Value : public mbgl::variant<std::string, float, + double, bool, mapbox::util::recursive_wrapper<ValueMap>, mapbox::util::recursive_wrapper<ValueVector>> { @@ -90,6 +91,14 @@ inline optional<float> toNumber(const Value& value) { return {}; } + +inline optional<double> toDouble(const Value& value) { + if (value.is<double>()) { + return value.get<double>(); + } + return {}; +} + inline optional<std::string> toString(const Value& value) { if (value.is<std::string>()) { return value.get<std::string>(); diff --git a/test/src/mbgl/test/fake_file_source.hpp b/test/src/mbgl/test/fake_file_source.hpp index 3ed3f90a17..baae7f9b7e 100644 --- a/test/src/mbgl/test/fake_file_source.hpp +++ b/test/src/mbgl/test/fake_file_source.hpp @@ -42,8 +42,8 @@ public: } bool respond(Resource::Kind kind, const Response& response) { - auto it = std::find_if(requests.begin(), requests.end(), [&] (FakeFileRequest* request) { - return request->resource.kind == kind; + auto it = std::find_if(requests.begin(), requests.end(), [&] (FakeFileRequest* fakeRequest) { + return fakeRequest->resource.kind == kind; }); if (it != requests.end()) { diff --git a/test/src/mbgl/test/fixture_log_observer.cpp b/test/src/mbgl/test/fixture_log_observer.cpp index fc0239bb1c..717d2da753 100644 --- a/test/src/mbgl/test/fixture_log_observer.cpp +++ b/test/src/mbgl/test/fixture_log_observer.cpp @@ -15,9 +15,6 @@ bool FixtureLog::Message::operator==(const Message& rhs) const { return severity == rhs.severity && event == rhs.event && code == rhs.code && msg == rhs.msg; } -FixtureLog::Message::Message() : severity(), event(), code(), msg() { -} - FixtureLog::Observer::Observer(FixtureLog* log_) : log(log_) { } @@ -97,10 +94,10 @@ std::vector<FixtureLog::Message> FixtureLogObserver::unchecked() const { } ::std::ostream& operator<<(::std::ostream& os, const FixtureLog::Message& message) { - os << "[\"" << Enum<EventSeverity>::toString(message.severity) << "\", \""; - os << Enum<Event>::toString(message.event) << "\""; + os << R"([")" << Enum<EventSeverity>::toString(message.severity) << R"(", ")"; + os << Enum<Event>::toString(message.event) << R"(")"; os << ", " << message.code; - os << ", \"" << message.msg << "\""; + os << R"(, ")" << message.msg << R"(")"; return os << "]" << std::endl; } diff --git a/test/src/mbgl/test/fixture_log_observer.hpp b/test/src/mbgl/test/fixture_log_observer.hpp index 96ddc2c54f..328d4753a8 100644 --- a/test/src/mbgl/test/fixture_log_observer.hpp +++ b/test/src/mbgl/test/fixture_log_observer.hpp @@ -12,15 +12,15 @@ namespace mbgl { class FixtureLog { public: struct Message { + Message() = default; Message(EventSeverity severity_, Event event_, int64_t code_, std::string msg_); - Message(); bool operator==(const Message& rhs) const; - const EventSeverity severity; - const Event event; - const int64_t code; - const std::string msg; + const EventSeverity severity {}; + const Event event {}; + const int64_t code {}; + const std::string msg {}; mutable bool checked = false; }; diff --git a/test/src/mbgl/test/getrss.cpp b/test/src/mbgl/test/getrss.cpp index 9f57ad8e7b..c21b653eaa 100644 --- a/test/src/mbgl/test/getrss.cpp +++ b/test/src/mbgl/test/getrss.cpp @@ -80,8 +80,8 @@ size_t getCurrentRSS( ) #elif defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__) /* Linux ---------------------------------------------------- */ long rss = 0L; - FILE* fp = NULL; - if ( (fp = fopen( "/proc/self/statm", "r" )) == NULL ) + FILE* fp = nullptr; + if ( (fp = fopen( "/proc/self/statm", "r" )) == nullptr ) return (size_t)0L; /* Can't open? */ if ( fscanf( fp, "%*s%ld", &rss ) != 1 ) { diff --git a/test/src/mbgl/test/getrss.hpp b/test/src/mbgl/test/getrss.hpp index a4420c4b5f..be45ae889a 100644 --- a/test/src/mbgl/test/getrss.hpp +++ b/test/src/mbgl/test/getrss.hpp @@ -41,5 +41,5 @@ size_t getPeakRSS(); */ size_t getCurrentRSS(); -} -} +} // namespace test +} // namespace mbgl diff --git a/test/src/mbgl/test/stub_file_source.cpp b/test/src/mbgl/test/stub_file_source.cpp index ec0545e88c..7891d5d907 100644 --- a/test/src/mbgl/test/stub_file_source.cpp +++ b/test/src/mbgl/test/stub_file_source.cpp @@ -77,6 +77,9 @@ optional<Response> StubFileSource::defaultResponse(const Resource& resource) { case Resource::Kind::SpriteImage: if (!spriteImageResponse) throw std::runtime_error("unexpected sprite image request"); return spriteImageResponse(resource); + case Resource::Kind::Image: + if (!imageResponse) throw std::runtime_error("unexpected image request"); + return imageResponse(resource); case Resource::Kind::Unknown: throw std::runtime_error("unknown resource type"); } diff --git a/test/src/mbgl/test/stub_file_source.hpp b/test/src/mbgl/test/stub_file_source.hpp index ee4175cc3f..85118e1a77 100644 --- a/test/src/mbgl/test/stub_file_source.hpp +++ b/test/src/mbgl/test/stub_file_source.hpp @@ -29,6 +29,7 @@ public: ResponseFunction glyphsResponse; ResponseFunction spriteJSONResponse; ResponseFunction spriteImageResponse; + ResponseFunction imageResponse; private: // The default behavior is to throw if no per-kind callback has been set. diff --git a/test/src/mbgl/test/stub_geometry_tile_feature.hpp b/test/src/mbgl/test/stub_geometry_tile_feature.hpp index 21d198a96b..0164ab133c 100644 --- a/test/src/mbgl/test/stub_geometry_tile_feature.hpp +++ b/test/src/mbgl/test/stub_geometry_tile_feature.hpp @@ -9,10 +9,17 @@ public: : properties(std::move(properties_)) { } + StubGeometryTileFeature(optional<FeatureIdentifier> id_, FeatureType type_, GeometryCollection geometry_, PropertyMap properties_) + : properties(std::move(properties_)), + id(std::move(id_)), + type(type_), + geometry(std::move(geometry_)) { + } + PropertyMap properties; - optional<FeatureIdentifier> id = {}; + optional<FeatureIdentifier> id; FeatureType type = FeatureType::Point; - GeometryCollection geometry = {}; + GeometryCollection geometry; FeatureType getType() const override { return type; diff --git a/test/src/mbgl/test/stub_layer_observer.hpp b/test/src/mbgl/test/stub_layer_observer.hpp index 9acd4b077a..0fa413aefe 100644 --- a/test/src/mbgl/test/stub_layer_observer.hpp +++ b/test/src/mbgl/test/stub_layer_observer.hpp @@ -10,29 +10,9 @@ using namespace mbgl::style; */ class StubLayerObserver : public style::LayerObserver { public: - void onLayerFilterChanged(Layer& layer) override { - if (layerFilterChanged) layerFilterChanged(layer); + void onLayerChanged(Layer& layer) override { + if (layerChanged) layerChanged(layer); } - void onLayerVisibilityChanged(Layer& layer) override { - if (layerVisibilityChanged) layerVisibilityChanged(layer); - } - - void onLayerPaintPropertyChanged(Layer& layer) override { - if (layerPaintPropertyChanged) layerPaintPropertyChanged(layer); - } - - void onLayerDataDrivenPaintPropertyChanged(Layer& layer) override { - if (layerDataDrivenPaintPropertyChanged) layerDataDrivenPaintPropertyChanged(layer); - } - - void onLayerLayoutPropertyChanged(Layer& layer, const char * property) override { - if (layerLayoutPropertyChanged) layerLayoutPropertyChanged(layer, property); - } - - std::function<void (Layer&)> layerFilterChanged; - std::function<void (Layer&)> layerVisibilityChanged; - std::function<void (Layer&)> layerPaintPropertyChanged; - std::function<void (Layer&)> layerDataDrivenPaintPropertyChanged; - std::function<void (Layer&, const char *)> layerLayoutPropertyChanged; + std::function<void (Layer&)> layerChanged; }; diff --git a/test/src/mbgl/test/stub_style_observer.hpp b/test/src/mbgl/test/stub_style_observer.hpp index 7e22c68823..b97911cdb0 100644 --- a/test/src/mbgl/test/stub_style_observer.hpp +++ b/test/src/mbgl/test/stub_style_observer.hpp @@ -10,22 +10,6 @@ using namespace mbgl::style; */ class StubStyleObserver : public style::Observer { public: - void onGlyphsLoaded(const FontStack& fontStack, const GlyphRange& glyphRange) override { - if (glyphsLoaded) glyphsLoaded(fontStack, glyphRange); - } - - void onGlyphsError(const FontStack& fontStack, const GlyphRange& glyphRange, std::exception_ptr error) override { - if (glyphsError) glyphsError(fontStack, glyphRange, error); - } - - void onSpriteLoaded() override { - if (spriteLoaded) spriteLoaded(); - } - - void onSpriteError(std::exception_ptr error) override { - if (spriteError) spriteError(error); - } - void onSourceLoaded(Source& source) override { if (sourceLoaded) sourceLoaded(source); } @@ -46,10 +30,6 @@ public: if (resourceError) resourceError(error); }; - std::function<void (const FontStack&, const GlyphRange&)> glyphsLoaded; - std::function<void (const FontStack&, const GlyphRange&, std::exception_ptr)> glyphsError; - std::function<void ()> spriteLoaded; - std::function<void (std::exception_ptr)> spriteError; std::function<void (Source&)> sourceLoaded; std::function<void (Source&)> sourceChanged; std::function<void (Source&, std::exception_ptr)> sourceError; diff --git a/test/src/mbgl/test/util.cpp b/test/src/mbgl/test/util.cpp index 7ca2d72504..028a0a9d51 100644 --- a/test/src/mbgl/test/util.cpp +++ b/test/src/mbgl/test/util.cpp @@ -1,13 +1,8 @@ #include <mbgl/test/util.hpp> -#include <mbgl/map/map.hpp> -#include <mbgl/gl/offscreen_view.hpp> -#include <mbgl/gl/headless_display.hpp> #include <mbgl/util/logging.hpp> #include <mbgl/util/image.hpp> #include <mbgl/util/io.hpp> -#include <mbgl/util/chrono.hpp> -#include <mbgl/util/run_loop.hpp> #include <mapbox/pixelmatch.hpp> @@ -99,24 +94,6 @@ Server::~Server() { } } -std::shared_ptr<HeadlessDisplay> sharedDisplay() { - static auto display = std::make_shared<HeadlessDisplay>(); - return display; -} - -PremultipliedImage render(Map& map, OffscreenView& view) { - PremultipliedImage result; - map.renderStill(view, [&](std::exception_ptr) { - result = view.readStillImage(); - }); - - while (!result.valid()) { - util::RunLoop::Get()->runOnce(); - } - - return result; -} - void checkImage(const std::string& base, const PremultipliedImage& actual, double imageThreshold, diff --git a/test/src/mbgl/test/util.hpp b/test/src/mbgl/test/util.hpp index 8673155fe4..7a8d78897e 100644 --- a/test/src/mbgl/test/util.hpp +++ b/test/src/mbgl/test/util.hpp @@ -54,11 +54,6 @@ #include <gtest/gtest.h> namespace mbgl { - -class Map; -class OffscreenView; -class HeadlessDisplay; - namespace test { class Server { @@ -70,10 +65,6 @@ private: int fd = -1; }; -std::shared_ptr<HeadlessDisplay> sharedDisplay(); - -PremultipliedImage render(Map&, OffscreenView&); - void checkImage(const std::string& base, const PremultipliedImage& actual, double imageThreshold = 0, diff --git a/test/storage/asset_file_source.test.cpp b/test/storage/asset_file_source.test.cpp index 010a2c9dc7..a39d2963d2 100644 --- a/test/storage/asset_file_source.test.cpp +++ b/test/storage/asset_file_source.test.cpp @@ -3,8 +3,10 @@ #include <mbgl/util/chrono.hpp> #include <mbgl/util/run_loop.hpp> #include <mbgl/util/thread.hpp> +#include <mbgl/actor/actor_ref.hpp> #include <gtest/gtest.h> +#include <atomic> using namespace mbgl; @@ -20,16 +22,11 @@ TEST(AssetFileSource, Load) { #else unsigned numThreads = 50; #endif - - auto callback = [&] { - if (!--numThreads) { - loop.stop(); - } - }; + std::atomic_uint completed(numThreads); class TestWorker { public: - TestWorker(mbgl::AssetFileSource* fs_) : fs(fs_) {} + TestWorker(ActorRef<TestWorker>, mbgl::AssetFileSource* fs_) : fs(fs_) {} void run(std::function<void()> endCallback) { const std::string asset("asset://nonempty"); @@ -60,16 +57,12 @@ TEST(AssetFileSource, Load) { }; std::vector<std::unique_ptr<util::Thread<TestWorker>>> threads; - std::vector<std::unique_ptr<mbgl::AsyncRequest>> requests; - util::ThreadContext context = { "Test" }; for (unsigned i = 0; i < numThreads; ++i) { std::unique_ptr<util::Thread<TestWorker>> thread = - std::make_unique<util::Thread<TestWorker>>(context, &fs); - - requests.push_back( - thread->invokeWithCallback(&TestWorker::run, callback)); + std::make_unique<util::Thread<TestWorker>>("Test", &fs); + thread->actor().invoke(&TestWorker::run, [&] { if (!--completed) loop.stop(); }); threads.push_back(std::move(thread)); } diff --git a/test/storage/default_file_source.test.cpp b/test/storage/default_file_source.test.cpp index 03f1076559..b5686b5ffe 100644 --- a/test/storage/default_file_source.test.cpp +++ b/test/storage/default_file_source.test.cpp @@ -1,5 +1,7 @@ +#include <mbgl/actor/actor.hpp> #include <mbgl/test/util.hpp> #include <mbgl/storage/default_file_source.hpp> +#include <mbgl/storage/resource_transform.hpp> #include <mbgl/util/run_loop.hpp> using namespace mbgl; @@ -20,6 +22,7 @@ TEST(DefaultFileSource, TEST_REQUIRES_SERVER(CacheResponse)) { ASSERT_TRUE(res.data.get()); EXPECT_EQ("Response 1", *res.data); EXPECT_TRUE(bool(res.expires)); + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); response = res; @@ -32,6 +35,7 @@ TEST(DefaultFileSource, TEST_REQUIRES_SERVER(CacheResponse)) { ASSERT_TRUE(res2.data.get()); EXPECT_EQ(*response.data, *res2.data); EXPECT_EQ(response.expires, res2.expires); + EXPECT_EQ(response.mustRevalidate, res2.mustRevalidate); EXPECT_EQ(response.modified, res2.modified); EXPECT_EQ(response.etag, res2.etag); @@ -49,35 +53,53 @@ TEST(DefaultFileSource, TEST_REQUIRES_SERVER(CacheRevalidateSame)) { const Resource revalidateSame { Resource::Unknown, "http://127.0.0.1:3000/revalidate-same" }; std::unique_ptr<AsyncRequest> req1; std::unique_ptr<AsyncRequest> req2; - uint16_t counter = 0; + bool gotResponse = false; // First request causes the response to get cached. req1 = fs.request(revalidateSame, [&](Response res) { req1.reset(); EXPECT_EQ(nullptr, res.error); + EXPECT_FALSE(res.notModified); ASSERT_TRUE(res.data.get()); EXPECT_EQ("Response", *res.data); EXPECT_FALSE(bool(res.expires)); + EXPECT_TRUE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_EQ("snowfall", *res.etag); - // Second request returns the cached response, then immediately revalidates. - req2 = fs.request(revalidateSame, [&, res](Response res2) { - if (counter == 0) { - ++counter; + // The first response is stored in the cache, but it has 'must-revalidate' set. This means + // it can't return the cached response right away and we must wait for the revalidation + // request to complete. We can distinguish the cached response from the revalided response + // because the latter has an expiration date, while the cached response doesn't. + req2 = fs.request(revalidateSame, [&](Response res2) { + if (!gotResponse) { + // Even though we could find the response in the database, we send a revalidation + // request and get a 304 response. Since we haven't sent a reply yet, we're forcing + // notModified to be false so that implementations can continue to use the + // notModified flag to skip parsing new data. + gotResponse = true; + EXPECT_EQ(nullptr, res2.error); EXPECT_FALSE(res2.notModified); + ASSERT_TRUE(res2.data.get()); + EXPECT_EQ("Response", *res2.data); + EXPECT_TRUE(bool(res2.expires)); + EXPECT_TRUE(res2.mustRevalidate); + EXPECT_FALSE(bool(res2.modified)); + EXPECT_EQ("snowfall", *res2.etag); } else { + // The test server sends a Cache-Control header with a max-age of 1 second. This + // means that our OnlineFileSource implementation will request the tile again after + // 1 second. This time, our request already had a prior response, so we don't need + // to send the data again, and instead can actually forward the notModified flag. req2.reset(); - EXPECT_EQ(nullptr, res2.error); EXPECT_TRUE(res2.notModified); - ASSERT_FALSE(res2.data.get()); + EXPECT_FALSE(res2.data.get()); EXPECT_TRUE(bool(res2.expires)); + EXPECT_TRUE(res2.mustRevalidate); EXPECT_FALSE(bool(res2.modified)); - // We're not sending the ETag in the 304 reply, but it should still be there. EXPECT_EQ("snowfall", *res2.etag); - loop.stop(); } }); @@ -94,34 +116,53 @@ TEST(DefaultFileSource, TEST_REQUIRES_SERVER(CacheRevalidateModified)) { "http://127.0.0.1:3000/revalidate-modified" }; std::unique_ptr<AsyncRequest> req1; std::unique_ptr<AsyncRequest> req2; - uint16_t counter = 0; + bool gotResponse = false; // First request causes the response to get cached. req1 = fs.request(revalidateModified, [&](Response res) { req1.reset(); EXPECT_EQ(nullptr, res.error); + EXPECT_FALSE(res.notModified); ASSERT_TRUE(res.data.get()); EXPECT_EQ("Response", *res.data); EXPECT_FALSE(bool(res.expires)); + EXPECT_TRUE(res.mustRevalidate); EXPECT_EQ(Timestamp{ Seconds(1420070400) }, *res.modified); EXPECT_FALSE(res.etag); - // Second request returns the cached response, then immediately revalidates. + // The first response is stored in the cache, but it has 'must-revalidate' set. This means + // it can't return the cached response right away and we must wait for the revalidation + // request to complete. We can distinguish the cached response from the revalided response + // because the latter has an expiration date, while the cached response doesn't. req2 = fs.request(revalidateModified, [&, res](Response res2) { - if (counter == 0) { - ++counter; + if (!gotResponse) { + // Even though we could find the response in the database, we send a revalidation + // request and get a 304 response. Since we haven't sent a reply yet, we're forcing + // notModified to be false so that implementations can continue to use the + // notModified flag to skip parsing new data. + gotResponse = true; + EXPECT_EQ(nullptr, res2.error); EXPECT_FALSE(res2.notModified); + ASSERT_TRUE(res2.data.get()); + EXPECT_EQ("Response", *res2.data); + EXPECT_TRUE(bool(res2.expires)); + EXPECT_TRUE(res2.mustRevalidate); + EXPECT_EQ(Timestamp{ Seconds(1420070400) }, *res2.modified); + EXPECT_FALSE(res2.etag); } else { + // The test server sends a Cache-Control header with a max-age of 1 second. This + // means that our OnlineFileSource implementation will request the tile again after + // 1 second. This time, our request already had a prior response, so we don't need + // to send the data again, and instead can actually forward the notModified flag. req2.reset(); - EXPECT_EQ(nullptr, res2.error); EXPECT_TRUE(res2.notModified); - ASSERT_FALSE(res2.data.get()); + EXPECT_FALSE(res2.data.get()); EXPECT_TRUE(bool(res2.expires)); + EXPECT_TRUE(res2.mustRevalidate); EXPECT_EQ(Timestamp{ Seconds(1420070400) }, *res2.modified); EXPECT_FALSE(res2.etag); - loop.stop(); } }); @@ -137,7 +178,6 @@ TEST(DefaultFileSource, TEST_REQUIRES_SERVER(CacheRevalidateEtag)) { const Resource revalidateEtag { Resource::Unknown, "http://127.0.0.1:3000/revalidate-etag" }; std::unique_ptr<AsyncRequest> req1; std::unique_ptr<AsyncRequest> req2; - uint16_t counter = 0; // First request causes the response to get cached. req1 = fs.request(revalidateEtag, [&](Response res) { @@ -147,27 +187,24 @@ TEST(DefaultFileSource, TEST_REQUIRES_SERVER(CacheRevalidateEtag)) { ASSERT_TRUE(res.data.get()); EXPECT_EQ("Response 1", *res.data); EXPECT_FALSE(bool(res.expires)); + EXPECT_TRUE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_EQ("response-1", *res.etag); - // Second request returns the cached response, then immediately revalidates. + // Second request does not return the cached response, since it had Cache-Control: must-revalidate set. req2 = fs.request(revalidateEtag, [&, res](Response res2) { - if (counter == 0) { - ++counter; - EXPECT_FALSE(res2.notModified); - } else { - req2.reset(); + req2.reset(); - EXPECT_EQ(nullptr, res2.error); - ASSERT_TRUE(res2.data.get()); - EXPECT_NE(res.data, res2.data); - EXPECT_EQ("Response 2", *res2.data); - EXPECT_FALSE(bool(res2.expires)); - EXPECT_FALSE(bool(res2.modified)); - EXPECT_EQ("response-2", *res2.etag); + EXPECT_EQ(nullptr, res2.error); + ASSERT_TRUE(res2.data.get()); + EXPECT_NE(res.data, res2.data); + EXPECT_EQ("Response 2", *res2.data); + EXPECT_FALSE(bool(res2.expires)); + EXPECT_TRUE(res2.mustRevalidate); + EXPECT_FALSE(bool(res2.modified)); + EXPECT_EQ("response-2", *res2.etag); - loop.stop(); - } + loop.stop(); }); }); @@ -201,6 +238,7 @@ TEST(DefaultFileSource, TEST_REQUIRES_SERVER(HTTPIssue1369)) { ASSERT_TRUE(res.data.get()); EXPECT_EQ("Hello World!", *res.data); EXPECT_FALSE(bool(res.expires)); + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); loop.stop(); @@ -230,6 +268,7 @@ TEST(DefaultFileSource, OptionalNonExpired) { EXPECT_EQ("Cached value", *res.data); ASSERT_TRUE(bool(res.expires)); EXPECT_EQ(*response.expires, *res.expires); + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); loop.stop(); @@ -259,6 +298,7 @@ TEST(DefaultFileSource, OptionalExpired) { EXPECT_EQ("Cached value", *res.data); ASSERT_TRUE(bool(res.expires)); EXPECT_EQ(*response.expires, *res.expires); + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); loop.stop(); @@ -299,6 +339,7 @@ TEST(DefaultFileSource, OptionalNotFound) { EXPECT_EQ("Not found in offline database", res.error->message); EXPECT_FALSE(res.data); EXPECT_FALSE(bool(res.expires)); + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); loop.stop(); @@ -332,6 +373,7 @@ TEST(DefaultFileSource, TEST_REQUIRES_SERVER(NoCacheRefreshEtagNotModified)) { EXPECT_FALSE(res.data.get()); ASSERT_TRUE(bool(res.expires)); EXPECT_LT(util::now(), *res.expires); + EXPECT_TRUE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); ASSERT_TRUE(bool(res.etag)); EXPECT_EQ("snowfall", *res.etag); @@ -366,6 +408,7 @@ TEST(DefaultFileSource, TEST_REQUIRES_SERVER(NoCacheRefreshEtagModified)) { ASSERT_TRUE(res.data.get()); EXPECT_EQ("Response", *res.data); EXPECT_FALSE(bool(res.expires)); + EXPECT_TRUE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); ASSERT_TRUE(bool(res.etag)); EXPECT_EQ("snowfall", *res.etag); @@ -401,6 +444,7 @@ TEST(DefaultFileSource, TEST_REQUIRES_SERVER(NoCacheFull)) { ASSERT_TRUE(res.data.get()); EXPECT_EQ("Response", *res.data); EXPECT_FALSE(bool(res.expires)); + EXPECT_TRUE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); ASSERT_TRUE(bool(res.etag)); EXPECT_EQ("snowfall", *res.etag); @@ -435,6 +479,7 @@ TEST(DefaultFileSource, TEST_REQUIRES_SERVER(NoCacheRefreshModifiedNotModified)) EXPECT_FALSE(res.data.get()); ASSERT_TRUE(bool(res.expires)); EXPECT_LT(util::now(), *res.expires); + EXPECT_TRUE(res.mustRevalidate); ASSERT_TRUE(bool(res.modified)); EXPECT_EQ(Timestamp{ Seconds(1420070400) }, *res.modified); EXPECT_FALSE(bool(res.etag)); @@ -469,6 +514,7 @@ TEST(DefaultFileSource, TEST_REQUIRES_SERVER(NoCacheRefreshModifiedModified)) { ASSERT_TRUE(res.data.get()); EXPECT_EQ("Response", *res.data); EXPECT_FALSE(bool(res.expires)); + EXPECT_TRUE(res.mustRevalidate); EXPECT_EQ(Timestamp{ Seconds(1420070400) }, *res.modified); EXPECT_FALSE(res.etag); loop.stop(); @@ -482,7 +528,7 @@ TEST(DefaultFileSource, TEST_REQUIRES_SERVER(SetResourceTransform)) { DefaultFileSource fs(":memory:", "."); // Translates the URL "localhost://test to http://127.0.0.1:3000/test - fs.setResourceTransform([](Resource::Kind, std::string&& url) -> std::string { + Actor<ResourceTransform> transform(loop, [](Resource::Kind, const std::string&& url) -> std::string { if (url == "localhost://test") { return "http://127.0.0.1:3000/test"; } else { @@ -490,19 +536,116 @@ TEST(DefaultFileSource, TEST_REQUIRES_SERVER(SetResourceTransform)) { } }); - const Resource resource { Resource::Unknown, "localhost://test" }; + fs.setResourceTransform(transform.self()); + const Resource resource1 { Resource::Unknown, "localhost://test" }; std::unique_ptr<AsyncRequest> req; - req = fs.request(resource, [&](Response res) { + req = fs.request(resource1, [&](Response res) { req.reset(); EXPECT_EQ(nullptr, res.error); ASSERT_TRUE(res.data.get()); EXPECT_EQ("Hello World!", *res.data); EXPECT_FALSE(bool(res.expires)); + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); loop.stop(); }); loop.run(); + + fs.setResourceTransform({}); + const Resource resource2 { Resource::Unknown, "http://127.0.0.1:3000/test" }; + + req = fs.request(resource2, [&](Response res) { + req.reset(); + EXPECT_EQ(nullptr, res.error); + ASSERT_TRUE(res.data.get()); + EXPECT_EQ("Hello World!", *res.data); + EXPECT_FALSE(bool(res.expires)); + EXPECT_FALSE(res.mustRevalidate); + EXPECT_FALSE(bool(res.modified)); + EXPECT_FALSE(bool(res.etag)); + loop.stop(); + }); + + loop.run(); +} + +// Test that a stale cache file that has must-revalidate set will trigger a response. +TEST(DefaultFileSource, TEST_REQUIRES_SERVER(RespondToStaleMustRevalidate)) { + util::RunLoop loop; + DefaultFileSource fs(":memory:", "."); + + Resource resource { Resource::Unknown, "http://127.0.0.1:3000/revalidate-same" }; + resource.necessity = Resource::Necessity::Optional; + + // using namespace std::chrono_literals; + + // Put an existing value in the cache that has expired, and has must-revalidate set. + Response response; + response.data = std::make_shared<std::string>("Cached value"); + response.modified = Timestamp(Seconds(1417392000)); // December 1, 2014 + response.expires = Timestamp(Seconds(1417392000)); + response.mustRevalidate = true; + response.etag.emplace("snowfall"); + fs.put(resource, response); + + std::unique_ptr<AsyncRequest> req; + req = fs.request(resource, [&](Response res) { + req.reset(); + ASSERT_TRUE(res.error.get()); + EXPECT_EQ(Response::Error::Reason::NotFound, res.error->reason); + EXPECT_EQ("Cached resource is unusable", res.error->message); + EXPECT_FALSE(res.notModified); + ASSERT_TRUE(res.data.get()); + EXPECT_EQ("Cached value", *res.data); + ASSERT_TRUE(res.expires); + EXPECT_EQ(Timestamp{ Seconds(1417392000) }, *res.expires); + EXPECT_TRUE(res.mustRevalidate); + ASSERT_TRUE(res.modified); + EXPECT_EQ(Timestamp{ Seconds(1417392000) }, *res.modified); + ASSERT_TRUE(res.etag); + EXPECT_EQ("snowfall", *res.etag); + + resource.priorEtag = res.etag; + resource.priorModified = res.modified; + resource.priorExpires = res.expires; + resource.priorData = res.data; + + loop.stop(); + }); + + loop.run(); + + // Now run this request again, with the data we gathered from the previous stale/unusable + // request. We're replacing the data so that we can check that the DefaultFileSource doesn't + // attempt another database access if we already have the value. + resource.necessity = Resource::Necessity::Required; + resource.priorData = std::make_shared<std::string>("Prior value"); + + req = fs.request(resource, [&](Response res) { + req.reset(); + ASSERT_EQ(nullptr, res.error.get()); + // Since the data was found in the cache, we're doing a revalidation request. Yet, since + // this request hasn't returned data before, we're setting notModified to false in the + // OnlineFileSource to ensure that requestors know that this is the first time they're + // seeing this data. + EXPECT_FALSE(res.notModified); + ASSERT_TRUE(res.data.get()); + // Ensure that it's the value that we manually inserted into the cache rather than the value + // the server returns, since we should be executing a revalidation request which doesn't + // return new data, only a 304 Not Modified response. + EXPECT_EQ("Prior value", *res.data); + ASSERT_TRUE(res.expires); + EXPECT_LE(util::now(), *res.expires); + EXPECT_TRUE(res.mustRevalidate); + ASSERT_TRUE(res.modified); + EXPECT_EQ(Timestamp{ Seconds(1417392000) }, *res.modified); + ASSERT_TRUE(res.etag); + EXPECT_EQ("snowfall", *res.etag); + loop.stop(); + }); + + loop.run(); } diff --git a/test/storage/http_file_source.test.cpp b/test/storage/http_file_source.test.cpp index 5b081d7d57..006b7a0fb3 100644 --- a/test/storage/http_file_source.test.cpp +++ b/test/storage/http_file_source.test.cpp @@ -26,6 +26,7 @@ TEST(HTTPFileSource, TEST_REQUIRES_SERVER(HTTP200)) { ASSERT_TRUE(res.data.get()); EXPECT_EQ("Hello World!", *res.data); EXPECT_FALSE(bool(res.expires)); + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); loop.stop(); @@ -44,6 +45,7 @@ TEST(HTTPFileSource, TEST_REQUIRES_SERVER(HTTP404)) { EXPECT_EQ("HTTP status code 404", res.error->message); EXPECT_FALSE(bool(res.data)); EXPECT_FALSE(bool(res.expires)); + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); loop.stop(); @@ -61,6 +63,7 @@ TEST(HTTPFileSource, TEST_REQUIRES_SERVER(HTTPTile404)) { EXPECT_FALSE(bool(res.error)); EXPECT_FALSE(bool(res.data)); EXPECT_FALSE(bool(res.expires)); + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); loop.stop(); @@ -78,6 +81,7 @@ TEST(HTTPFileSource, TEST_REQUIRES_SERVER(HTTP200EmptyData)) { EXPECT_FALSE(bool(res.error)); EXPECT_EQ(*res.data, std::string()); EXPECT_FALSE(bool(res.expires)); + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); loop.stop(); @@ -95,6 +99,7 @@ TEST(HTTPFileSource, TEST_REQUIRES_SERVER(HTTP204)) { EXPECT_FALSE(bool(res.error)); EXPECT_FALSE(bool(res.data)); EXPECT_FALSE(bool(res.expires)); + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); loop.stop(); @@ -113,6 +118,7 @@ TEST(HTTPFileSource, TEST_REQUIRES_SERVER(HTTP500)) { EXPECT_EQ("HTTP status code 500", res.error->message); EXPECT_FALSE(bool(res.data)); EXPECT_FALSE(bool(res.expires)); + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); loop.stop(); @@ -131,6 +137,7 @@ TEST(HTTPFileSource, TEST_REQUIRES_SERVER(ExpiresParsing)) { ASSERT_TRUE(res.data.get()); EXPECT_EQ("Hello World!", *res.data); EXPECT_EQ(Timestamp{ Seconds(1420797926) }, res.expires); + EXPECT_FALSE(res.mustRevalidate); EXPECT_EQ(Timestamp{ Seconds(1420794326) }, res.modified); EXPECT_EQ("foo", *res.etag); loop.stop(); @@ -148,6 +155,7 @@ TEST(HTTPFileSource, TEST_REQUIRES_SERVER(CacheControlParsing)) { ASSERT_TRUE(res.data.get()); EXPECT_EQ("Hello World!", *res.data); EXPECT_GT(Seconds(2), util::abs(*res.expires - util::now() - Seconds(120))) << "Expiration date isn't about 120 seconds in the future"; + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); loop.stop(); @@ -176,6 +184,7 @@ TEST(HTTPFileSource, TEST_REQUIRES_SERVER(Load)) { ASSERT_TRUE(res.data.get()); EXPECT_EQ(std::string("Request ") + std::to_string(current), *res.data); EXPECT_FALSE(bool(res.expires)); + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); diff --git a/test/storage/local_file_source.test.cpp b/test/storage/local_file_source.test.cpp index 1b90e5bb1e..4d509e6c7d 100644 --- a/test/storage/local_file_source.test.cpp +++ b/test/storage/local_file_source.test.cpp @@ -3,7 +3,7 @@ #include <mbgl/util/run_loop.hpp> #include <unistd.h> -#include <limits.h> +#include <climits> #include <gtest/gtest.h> namespace { diff --git a/test/storage/offline.test.cpp b/test/storage/offline.test.cpp index 0faaabc298..e7ebe5199f 100644 --- a/test/storage/offline.test.cpp +++ b/test/storage/offline.test.cpp @@ -52,3 +52,11 @@ TEST(OfflineTilePyramidRegionDefinition, TileCoverWrapped) { EXPECT_EQ((std::vector<CanonicalTileID>{ { 0, 0, 0 } }), region.tileCover(SourceType::Vector, 512, { 0, 22 })); } + +TEST(OfflineTilePyramidRegionDefinition, TileCount) { + OfflineTilePyramidRegionDefinition region("", sanFranciscoWrapped, 0, 22, 1.0); + + //These numbers match the count from tileCover().size(). + EXPECT_EQ(38424u, region.tileCount(SourceType::Vector, 512, { 10, 18 })); + EXPECT_EQ(9675240u, region.tileCount(SourceType::Vector, 512, { 3, 22 })); +} diff --git a/test/storage/offline_database.test.cpp b/test/storage/offline_database.test.cpp index fbf515679e..d99c1f946f 100644 --- a/test/storage/offline_database.test.cpp +++ b/test/storage/offline_database.test.cpp @@ -650,19 +650,19 @@ TEST(OfflineDatabase, MigrateFromV2Schema) { // v2.db is a v2 database containing a single offline region with a small number of resources. - deleteFile("test/fixtures/offline_database/v5.db"); - writeFile("test/fixtures/offline_database/v5.db", util::read_file("test/fixtures/offline_database/v2.db")); + deleteFile("test/fixtures/offline_database/migrated.db"); + writeFile("test/fixtures/offline_database/migrated.db", util::read_file("test/fixtures/offline_database/v2.db")); { - OfflineDatabase db("test/fixtures/offline_database/v5.db", 0); + OfflineDatabase db("test/fixtures/offline_database/migrated.db", 0); auto regions = db.listRegions(); for (auto& region : regions) { db.deleteRegion(std::move(region)); } } - EXPECT_EQ(5, databaseUserVersion("test/fixtures/offline_database/v5.db")); - EXPECT_LT(databasePageCount("test/fixtures/offline_database/v5.db"), + EXPECT_EQ(6, databaseUserVersion("test/fixtures/offline_database/migrated.db")); + EXPECT_LT(databasePageCount("test/fixtures/offline_database/migrated.db"), databasePageCount("test/fixtures/offline_database/v2.db")); } @@ -671,18 +671,18 @@ TEST(OfflineDatabase, MigrateFromV3Schema) { // v3.db is a v3 database, migrated from v2. - deleteFile("test/fixtures/offline_database/v5.db"); - writeFile("test/fixtures/offline_database/v5.db", util::read_file("test/fixtures/offline_database/v3.db")); + deleteFile("test/fixtures/offline_database/migrated.db"); + writeFile("test/fixtures/offline_database/migrated.db", util::read_file("test/fixtures/offline_database/v3.db")); { - OfflineDatabase db("test/fixtures/offline_database/v5.db", 0); + OfflineDatabase db("test/fixtures/offline_database/migrated.db", 0); auto regions = db.listRegions(); for (auto& region : regions) { db.deleteRegion(std::move(region)); } } - EXPECT_EQ(5, databaseUserVersion("test/fixtures/offline_database/v5.db")); + EXPECT_EQ(6, databaseUserVersion("test/fixtures/offline_database/migrated.db")); } TEST(OfflineDatabase, MigrateFromV4Schema) { @@ -690,24 +690,52 @@ TEST(OfflineDatabase, MigrateFromV4Schema) { // v4.db is a v4 database, migrated from v2 & v3. This database used `journal_mode = WAL` and `synchronous = NORMAL`. - deleteFile("test/fixtures/offline_database/v5.db"); - writeFile("test/fixtures/offline_database/v5.db", util::read_file("test/fixtures/offline_database/v4.db")); + deleteFile("test/fixtures/offline_database/migrated.db"); + writeFile("test/fixtures/offline_database/migrated.db", util::read_file("test/fixtures/offline_database/v4.db")); { - OfflineDatabase db("test/fixtures/offline_database/v5.db", 0); + OfflineDatabase db("test/fixtures/offline_database/migrated.db", 0); auto regions = db.listRegions(); for (auto& region : regions) { db.deleteRegion(std::move(region)); } } - EXPECT_EQ(5, databaseUserVersion("test/fixtures/offline_database/v5.db")); + EXPECT_EQ(6, databaseUserVersion("test/fixtures/offline_database/migrated.db")); // Journal mode should be DELETE after migration to v5. - EXPECT_EQ("delete", databaseJournalMode("test/fixtures/offline_database/v5.db")); + EXPECT_EQ("delete", databaseJournalMode("test/fixtures/offline_database/migrated.db")); // Synchronous setting should be FULL (2) after migration to v5. - EXPECT_EQ(2, databaseSyncMode("test/fixtures/offline_database/v5.db")); + EXPECT_EQ(2, databaseSyncMode("test/fixtures/offline_database/migrated.db")); +} + + +TEST(OfflineDatabase, MigrateFromV5Schema) { + using namespace mbgl; + + // v5.db is a v5 database, migrated from v2, v3 & v4. + + deleteFile("test/fixtures/offline_database/migrated.db"); + writeFile("test/fixtures/offline_database/migrated.db", util::read_file("test/fixtures/offline_database/v5.db")); + + { + OfflineDatabase db("test/fixtures/offline_database/migrated.db", 0); + auto regions = db.listRegions(); + for (auto& region : regions) { + db.deleteRegion(std::move(region)); + } + } + + EXPECT_EQ(6, databaseUserVersion("test/fixtures/offline_database/migrated.db")); + + EXPECT_EQ((std::vector<std::string>{ "id", "url_template", "pixel_ratio", "z", "x", "y", + "expires", "modified", "etag", "data", "compressed", + "accessed", "must_revalidate" }), + databaseTableColumns("test/fixtures/offline_database/migrated.db", "tiles")); + EXPECT_EQ((std::vector<std::string>{ "id", "url", "kind", "expires", "modified", "etag", "data", + "compressed", "accessed", "must_revalidate" }), + databaseTableColumns("test/fixtures/offline_database/migrated.db", "resources")); } TEST(OfflineDatabase, DowngradeSchema) { @@ -723,13 +751,13 @@ TEST(OfflineDatabase, DowngradeSchema) { OfflineDatabase db("test/fixtures/offline_database/migrated.db", 0); } - EXPECT_EQ(5, databaseUserVersion("test/fixtures/offline_database/migrated.db")); + EXPECT_EQ(6, databaseUserVersion("test/fixtures/offline_database/migrated.db")); EXPECT_EQ((std::vector<std::string>{ "id", "url_template", "pixel_ratio", "z", "x", "y", "expires", "modified", "etag", "data", "compressed", - "accessed" }), + "accessed", "must_revalidate" }), databaseTableColumns("test/fixtures/offline_database/migrated.db", "tiles")); EXPECT_EQ((std::vector<std::string>{ "id", "url", "kind", "expires", "modified", "etag", "data", - "compressed", "accessed" }), + "compressed", "accessed", "must_revalidate" }), databaseTableColumns("test/fixtures/offline_database/migrated.db", "resources")); } diff --git a/test/storage/offline_download.test.cpp b/test/storage/offline_download.test.cpp index 27e57771c8..57780eba40 100644 --- a/test/storage/offline_download.test.cpp +++ b/test/storage/offline_download.test.cpp @@ -191,6 +191,11 @@ TEST(OfflineDownload, Activate) { return test.response("sprite.png"); }; + test.fileSource.imageResponse = [&] (const Resource& resource) { + EXPECT_EQ("http://127.0.0.1:3000/radar.gif", resource.url); + return test.response("radar.gif"); + }; + test.fileSource.spriteJSONResponse = [&] (const Resource& resource) { EXPECT_EQ("http://127.0.0.1:3000/sprite.json", resource.url); return test.response("sprite.json"); @@ -219,7 +224,7 @@ TEST(OfflineDownload, Activate) { observer->statusChangedFn = [&] (OfflineRegionStatus status) { if (status.complete()) { - EXPECT_EQ(261u, status.completedResourceCount); // 256 glyphs, 1 tile, 1 style, source, sprite image, and sprite json + EXPECT_EQ(262u, status.completedResourceCount); // 256 glyphs, 1 tile, 1 style, source, image, sprite image, and sprite json EXPECT_EQ(test.size, status.completedResourceSize); download.setState(OfflineRegionDownloadState::Inactive); @@ -299,7 +304,7 @@ TEST(OfflineDownload, GetStatusStyleComplete) { EXPECT_EQ(OfflineRegionDownloadState::Inactive, status.downloadState); EXPECT_EQ(1u, status.completedResourceCount); EXPECT_EQ(test.size, status.completedResourceSize); - EXPECT_EQ(260u, status.requiredResourceCount); + EXPECT_EQ(261u, status.requiredResourceCount); EXPECT_FALSE(status.requiredResourceCountIsPrecise); EXPECT_FALSE(status.complete()); } @@ -325,7 +330,7 @@ TEST(OfflineDownload, GetStatusStyleAndSourceComplete) { EXPECT_EQ(OfflineRegionDownloadState::Inactive, status.downloadState); EXPECT_EQ(2u, status.completedResourceCount); EXPECT_EQ(test.size, status.completedResourceSize); - EXPECT_EQ(261u, status.requiredResourceCount); + EXPECT_EQ(262u, status.requiredResourceCount); EXPECT_TRUE(status.requiredResourceCountIsPrecise); EXPECT_FALSE(status.complete()); } diff --git a/test/storage/online_file_source.test.cpp b/test/storage/online_file_source.test.cpp index 1a1d2d42f8..70bfe3ac95 100644 --- a/test/storage/online_file_source.test.cpp +++ b/test/storage/online_file_source.test.cpp @@ -36,6 +36,7 @@ TEST(OnlineFileSource, TEST_REQUIRES_SERVER(CancelMultiple)) { ASSERT_TRUE(res.data.get()); EXPECT_EQ("Hello World!", *res.data); EXPECT_FALSE(bool(res.expires)); + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); loop.stop(); @@ -62,6 +63,7 @@ TEST(OnlineFileSource, TEST_REQUIRES_SERVER(TemporaryError)) { EXPECT_EQ("HTTP status code 500", res.error->message); ASSERT_FALSE(bool(res.data)); EXPECT_FALSE(bool(res.expires)); + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); } break; @@ -73,6 +75,7 @@ TEST(OnlineFileSource, TEST_REQUIRES_SERVER(TemporaryError)) { ASSERT_TRUE(res.data.get()); EXPECT_EQ("Hello World!", *res.data); EXPECT_FALSE(bool(res.expires)); + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); loop.stop(); @@ -99,6 +102,7 @@ TEST(OnlineFileSource, TEST_REQUIRES_SERVER(ConnectionError)) { EXPECT_EQ(Response::Error::Reason::Connection, res.error->reason); ASSERT_FALSE(res.data.get()); EXPECT_FALSE(bool(res.expires)); + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); @@ -126,6 +130,7 @@ TEST(OnlineFileSource, TEST_REQUIRES_SERVER(Timeout)) { ASSERT_TRUE(res.data.get()); EXPECT_EQ("Hello World!", *res.data); EXPECT_TRUE(bool(res.expires)); + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); if (counter == 4) { @@ -150,6 +155,7 @@ TEST(OnlineFileSource, TEST_REQUIRES_SERVER(RetryDelayOnExpiredTile)) { counter++; EXPECT_EQ(nullptr, res.error); EXPECT_GT(util::now(), *res.expires); + EXPECT_FALSE(res.mustRevalidate); }); util::Timer timer; @@ -170,6 +176,7 @@ TEST(OnlineFileSource, TEST_REQUIRES_SERVER(RetryOnClockSkew)) { const Resource resource { Resource::Unknown, "http://127.0.0.1:3000/clockskew" }; std::unique_ptr<AsyncRequest> req1 = fs.request(resource, [&](Response res) { + EXPECT_FALSE(res.mustRevalidate); switch (counter++) { case 0: { EXPECT_EQ(nullptr, res.error); @@ -240,6 +247,7 @@ TEST(OnlineFileSource, TEST_REQUIRES_SERVER(Load)) { ASSERT_TRUE(res.data.get()); EXPECT_EQ(std::string("Request ") + std::to_string(current), *res.data); EXPECT_FALSE(bool(res.expires)); + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); @@ -277,6 +285,7 @@ TEST(OnlineFileSource, TEST_REQUIRES_SERVER(NetworkStatusChange)) { ASSERT_TRUE(res.data.get()); EXPECT_EQ("Response", *res.data); EXPECT_FALSE(bool(res.expires)); + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); loop.stop(); @@ -315,6 +324,7 @@ TEST(OnlineFileSource, TEST_REQUIRES_SERVER(NetworkStatusChangePreempt)) { EXPECT_EQ(Response::Error::Reason::Connection, res.error->reason); ASSERT_FALSE(res.data.get()); EXPECT_FALSE(bool(res.expires)); + EXPECT_FALSE(res.mustRevalidate); EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); diff --git a/test/storage/resource.test.cpp b/test/storage/resource.test.cpp index 1c15fe6503..5a27aa98a5 100644 --- a/test/storage/resource.test.cpp +++ b/test/storage/resource.test.cpp @@ -117,6 +117,13 @@ TEST(Resource, SpriteImage) { EXPECT_EQ("http://example.com/sprite@2x.png", resource.url); } +TEST(Resource, Image) { + using namespace mbgl; + Resource resource = Resource::image("http://example.com/sprite.jpg"); + EXPECT_EQ(Resource::Kind::Image, resource.kind); + EXPECT_EQ("http://example.com/sprite.jpg", resource.url); +} + TEST(Resource, SpriteJSON) { using namespace mbgl; Resource resource = Resource::spriteJSON("http://example.com/sprite", 2.0); diff --git a/test/storage/server.js b/test/storage/server.js index b54ff835ec..d6429e4635 100755 --- a/test/storage/server.js +++ b/test/storage/server.js @@ -44,8 +44,8 @@ app.get('/cache', function(req, res) { app.get('/revalidate-same', function(req, res) { if (req.headers['if-none-match'] == 'snowfall') { - // Second request can be cached for 30 seconds. - res.setHeader('Cache-Control', 'max-age=30'); + // Second request can be cached for 1 second. + res.setHeader('Cache-Control', 'max-age=1, must-revalidate'); res.status(304).end(); } else { // First request must always be revalidated. @@ -67,7 +67,7 @@ app.get('/revalidate-modified', function(req, res) { if (req.headers['if-modified-since']) { var modified_since = new Date(req.headers['if-modified-since']); if (modified_since >= jan1) { - res.setHeader('Cache-Control', 'max-age=30'); + res.setHeader('Cache-Control', 'max-age=1, must-revalidate'); res.status(304).end(); return; } diff --git a/test/storage/sqlite.test.cpp b/test/storage/sqlite.test.cpp index dbd7a09868..36715a2fd0 100644 --- a/test/storage/sqlite.test.cpp +++ b/test/storage/sqlite.test.cpp @@ -25,3 +25,14 @@ TEST(SQLite, Statement) { ASSERT_EQ(stmt2.lastInsertRowId(), 2); ASSERT_EQ(stmt2.changes(), 1u); } + +TEST(SQLite, TEST_REQUIRES_WRITE(CantOpenException)) { + try { + // Should throw a CANTOPEN when the database doesn't exist, + // make sure all the backends behave the same way. + mapbox::sqlite::Database("test/fixtures/offline_database/foobar123.db", mapbox::sqlite::ReadOnly); + FAIL(); + } catch (mapbox::sqlite::Exception& ex) { + ASSERT_EQ(ex.code, mapbox::sqlite::Exception::Code::CANTOPEN); + } +} diff --git a/test/style/conversion/function.test.cpp b/test/style/conversion/function.test.cpp index 08637d40cb..1eff94d939 100644 --- a/test/style/conversion/function.test.cpp +++ b/test/style/conversion/function.test.cpp @@ -19,26 +19,26 @@ TEST(StyleConversion, Function) { return convert<CameraFunction<float>, JSValue>(doc, error); }; - auto fn1 = parseFunction("{\"stops\":[]}"); + auto fn1 = parseFunction(R"({"stops":[]})"); ASSERT_FALSE(fn1); ASSERT_EQ("function must have at least one stop", error.message); - auto fn2 = parseFunction("{\"stops\":[1]}"); + auto fn2 = parseFunction(R"({"stops":[1]})"); ASSERT_FALSE(fn2); ASSERT_EQ("function stop must be an array", error.message); - auto fn3 = parseFunction("{\"stops\":[[]]}"); + auto fn3 = parseFunction(R"({"stops":[[]]})"); ASSERT_FALSE(fn3); ASSERT_EQ("function stop must have two elements", error.message); - auto fn4 = parseFunction("{\"stops\":[[-1,-1]]}"); + auto fn4 = parseFunction(R"({"stops":[[-1,-1]]})"); ASSERT_TRUE(bool(fn4)); - auto fn5 = parseFunction("{\"stops\":[[0,1,2]]}"); + auto fn5 = parseFunction(R"({"stops":[[0,1,2]]})"); ASSERT_FALSE(fn5); ASSERT_EQ("function stop must have two elements", error.message); - auto fn6 = parseFunction("{\"stops\":[[0,\"x\"]]}"); + auto fn6 = parseFunction(R"({"stops":[[0,"x"]]})"); ASSERT_FALSE(fn6); ASSERT_EQ("value must be a number", error.message); @@ -50,7 +50,7 @@ TEST(StyleConversion, Function) { ASSERT_FALSE(fn8); ASSERT_EQ("function must be an object", error.message); - auto fn9 = parseFunction("{\"stops\":[[0,0]],\"base\":false}"); + auto fn9 = parseFunction(R"({"stops":[[0,0]],"base":false})"); ASSERT_FALSE(fn9); ASSERT_EQ("function base must be a number", error.message); } diff --git a/test/style/conversion/layer.test.cpp b/test/style/conversion/layer.test.cpp index ae8d4058ab..d51d7d33e2 100644 --- a/test/style/conversion/layer.test.cpp +++ b/test/style/conversion/layer.test.cpp @@ -27,21 +27,11 @@ TEST(StyleConversion, LayerTransition) { "duration": 400, "delay": 500 } - }, - "paint.class": { - "background-color-transition": { - "duration": 100 - } } })JSON"); - ASSERT_EQ(400ms, *layer->as<BackgroundLayer>()->impl->cascading - .get<BackgroundColor>().getTransition({}).duration); - ASSERT_EQ(500ms, *layer->as<BackgroundLayer>()->impl->cascading - .get<BackgroundColor>().getTransition({}).delay); - - ASSERT_EQ(100ms, *layer->as<BackgroundLayer>()->impl->cascading - .get<BackgroundColor>().getTransition({"class"}).duration); - ASSERT_FALSE(bool(layer->as<BackgroundLayer>()->impl->cascading - .get<BackgroundColor>().getTransition({"class"}).delay)); + ASSERT_EQ(400ms, *layer->as<BackgroundLayer>()->impl().paint + .get<BackgroundColor>().options.duration); + ASSERT_EQ(500ms, *layer->as<BackgroundLayer>()->impl().paint + .get<BackgroundColor>().options.delay); } diff --git a/test/style/conversion/light.test.cpp b/test/style/conversion/light.test.cpp index a2185906d6..28e22b3550 100644 --- a/test/style/conversion/light.test.cpp +++ b/test/style/conversion/light.test.cpp @@ -30,7 +30,7 @@ TEST(StyleConversion, Light) { } { - auto light = parseLight("{\"color\":{\"stops\":[[14,\"blue\"],[16,\"red\"]]},\"intensity\":0.3,\"position\":[3,90,90]}"); + auto light = parseLight(R"({"color":{"stops":[[14,"blue"],[16,"red"]]},"intensity":0.3,"position":[3,90,90]})"); ASSERT_TRUE((bool) light); ASSERT_TRUE(light->getAnchor().isUndefined()); @@ -54,7 +54,7 @@ TEST(StyleConversion, Light) { } { - auto light = parseLight("{\"color\":\"blue\",\"intensity\":0.3,\"color-transition\":{\"duration\":1000}}"); + auto light = parseLight(R"({"color":"blue","intensity":0.3,"color-transition":{"duration":1000}})"); ASSERT_TRUE((bool) light); ASSERT_FALSE(light->getColor().isUndefined()); @@ -65,35 +65,35 @@ TEST(StyleConversion, Light) { } { - auto light = parseLight("{\"intensity\":false}"); + auto light = parseLight(R"({"intensity":false})"); ASSERT_FALSE((bool) light); ASSERT_EQ("value must be a number", error.message); } { - auto light = parseLight("{\"intensity\":{\"stops\":[[15,\"red\"],[17,\"blue\"]]}}"); + auto light = parseLight(R"({"intensity":{"stops":[[15,"red"],[17,"blue"]]}})"); ASSERT_FALSE((bool) light); ASSERT_EQ("value must be a number", error.message); } { - auto light = parseLight("{\"color\":5}"); + auto light = parseLight(R"({"color":5})"); ASSERT_FALSE((bool) light); ASSERT_EQ("value must be a string", error.message); } { - auto light = parseLight("{\"position\":[0,5]}"); + auto light = parseLight(R"({"position":[0,5]})"); ASSERT_FALSE((bool) light); ASSERT_EQ("value must be an array of 3 numbers", error.message); } { - auto light = parseLight("{\"anchor\":\"something\"}"); + auto light = parseLight(R"({"anchor":"something"})"); ASSERT_FALSE((bool) light); ASSERT_EQ("value must be a valid enumeration value", error.message); diff --git a/test/style/conversion/stringify.test.cpp b/test/style/conversion/stringify.test.cpp index 1dae20b26b..0b2940a0e0 100644 --- a/test/style/conversion/stringify.test.cpp +++ b/test/style/conversion/stringify.test.cpp @@ -121,10 +121,17 @@ TEST(Stringify, PropertyValue) { } TEST(Stringify, Layout) { - ASSERT_EQ(stringify(SymbolLayoutProperties()), "{}"); - - SymbolLayoutProperties layout; - layout.unevaluated.get<SymbolAvoidEdges>() = true; - layout.unevaluated.get<IconPadding>() = 2.0; + auto stringify = [] (const SymbolLayoutProperties::Unevaluated& layout) { + rapidjson::StringBuffer s; + rapidjson::Writer<rapidjson::StringBuffer> writer(s); + layout.stringify(writer); + return std::string(s.GetString()); + }; + + ASSERT_EQ(stringify(SymbolLayoutProperties::Unevaluated()), "{}"); + + SymbolLayoutProperties::Unevaluated layout; + layout.get<SymbolAvoidEdges>() = true; + layout.get<IconPadding>() = 2.0; ASSERT_EQ(stringify(layout), "{\"symbol-avoid-edges\":true,\"icon-padding\":2.0}"); } diff --git a/test/style/filter.test.cpp b/test/style/filter.test.cpp index c70792d8ef..96de125945 100644 --- a/test/style/filter.test.cpp +++ b/test/style/filter.test.cpp @@ -29,13 +29,13 @@ Feature feature(const PropertyMap& properties, const Geometry<double>& geometry } TEST(Filter, EqualsString) { - Filter f = parse("[\"==\", \"foo\", \"bar\"]"); + Filter f = parse(R"(["==", "foo", "bar"])"); ASSERT_TRUE(f(feature({{ "foo", std::string("bar") }}))); ASSERT_FALSE(f(feature({{ "foo", std::string("baz") }}))); } TEST(Filter, EqualsNumber) { - Filter f = parse("[\"==\", \"foo\", 0]"); + Filter f = parse(R"(["==", "foo", 0])"); ASSERT_TRUE(f(feature({{ "foo", int64_t(0) }}))); ASSERT_TRUE(f(feature({{ "foo", uint64_t(0) }}))); ASSERT_TRUE(f(feature({{ "foo", double(0) }}))); @@ -50,13 +50,13 @@ TEST(Filter, EqualsNumber) { } TEST(Filter, EqualsType) { - Filter f = parse("[\"==\", \"$type\", \"LineString\"]"); + Filter f = parse(R"(["==", "$type", "LineString"])"); ASSERT_FALSE(f(feature({{}}, Point<double>()))); ASSERT_TRUE(f(feature({{}}, LineString<double>()))); } TEST(Filter, InType) { - Filter f = parse("[\"in\", \"$type\", \"LineString\", \"Polygon\"]"); + Filter f = parse(R"(["in", "$type", "LineString", "Polygon"])"); ASSERT_FALSE(f(feature({{}}, Point<double>()))); ASSERT_TRUE(f(feature({{}}, LineString<double>()))); ASSERT_TRUE(f(feature({{}}, Polygon<double>()))); diff --git a/test/style/function/exponential_stops.test.cpp b/test/style/function/exponential_stops.test.cpp new file mode 100644 index 0000000000..81438ec952 --- /dev/null +++ b/test/style/function/exponential_stops.test.cpp @@ -0,0 +1,20 @@ +#include <mbgl/test/util.hpp> + +#include <mbgl/style/function/exponential_stops.hpp> + +using namespace mbgl; +using namespace mbgl::style; + +TEST(ExponentialStops, Empty) { + ExponentialStops<float> stops; + EXPECT_FALSE(bool(stops.evaluate(0))); +} + +TEST(ExponentialStops, NonNumericInput) { + ExponentialStops<float> stops(std::map<float, float> {{0.0f, 0.0f}}); + EXPECT_FALSE(bool(stops.evaluate(Value(NullValue())))); + EXPECT_FALSE(bool(stops.evaluate(Value(false)))); + EXPECT_FALSE(bool(stops.evaluate(Value(std::string())))); + EXPECT_FALSE(bool(stops.evaluate(Value(std::vector<Value>())))); + EXPECT_FALSE(bool(stops.evaluate(Value(std::unordered_map<std::string, Value>())))); +} diff --git a/test/style/function/interval_stops.test.cpp b/test/style/function/interval_stops.test.cpp new file mode 100644 index 0000000000..8a5e74b8b6 --- /dev/null +++ b/test/style/function/interval_stops.test.cpp @@ -0,0 +1,20 @@ +#include <mbgl/test/util.hpp> + +#include <mbgl/style/function/interval_stops.hpp> + +using namespace mbgl; +using namespace mbgl::style; + +TEST(IntervalStops, Empty) { + IntervalStops<float> stops; + EXPECT_FALSE(bool(stops.evaluate(0))); +} + +TEST(IntervalStops, NonNumericInput) { + IntervalStops<float> stops(std::map<float, float> {{0.0f, 0.0f}}); + EXPECT_FALSE(bool(stops.evaluate(Value(NullValue())))); + EXPECT_FALSE(bool(stops.evaluate(Value(false)))); + EXPECT_FALSE(bool(stops.evaluate(Value(std::string())))); + EXPECT_FALSE(bool(stops.evaluate(Value(std::vector<Value>())))); + EXPECT_FALSE(bool(stops.evaluate(Value(std::unordered_map<std::string, Value>())))); +} diff --git a/test/style/function/source_function.test.cpp b/test/style/function/source_function.test.cpp index 260620c8d0..46ad961002 100644 --- a/test/style/function/source_function.test.cpp +++ b/test/style/function/source_function.test.cpp @@ -76,11 +76,14 @@ TEST(SourceFunction, Categorical) { EXPECT_EQ(0.0f, SourceFunction<float>("property", CategoricalStops<float>({{ int64_t(1), 1.0f }})) .evaluate(oneString, 0.0f)); - EXPECT_EQ(0.0f, SourceFunction<float>("property", CategoricalStops<float>({{ "1"s, 1.0f }})) + CategoricalStops<float>::Stops stops; + stops["1"s] = 1.0f; + + EXPECT_EQ(0.0f, SourceFunction<float>("property", CategoricalStops<float>(stops)) .evaluate(oneInteger, 0.0f)); - EXPECT_EQ(0.0f, SourceFunction<float>("property", CategoricalStops<float>({{ "1"s, 1.0f }})) + EXPECT_EQ(0.0f, SourceFunction<float>("property", CategoricalStops<float>(stops)) .evaluate(oneDouble, 0.0f)); - EXPECT_EQ(1.0f, SourceFunction<float>("property", CategoricalStops<float>({{ "1"s, 1.0f }})) + EXPECT_EQ(1.0f, SourceFunction<float>("property", CategoricalStops<float>(stops)) .evaluate(oneString, 0.0f)); EXPECT_EQ(1.0f, SourceFunction<float>("property", CategoricalStops<float>({{ true, 1.0f }})) diff --git a/test/style/paint_property.test.cpp b/test/style/properties.test.cpp index fcca05f3bd..279fadb8c2 100644 --- a/test/style/paint_property.test.cpp +++ b/test/style/properties.test.cpp @@ -1,13 +1,14 @@ #include <mbgl/test/util.hpp> -#include <mbgl/style/paint_property.hpp> -#include <mbgl/renderer/transitioning_property.hpp> +#include <mbgl/style/properties.hpp> +#include <mbgl/renderer/property_evaluator.hpp> +#include <mbgl/renderer/data_driven_property_evaluator.hpp> using namespace mbgl; using namespace mbgl::style; using namespace std::literals::chrono_literals; -float evaluate(TransitioningProperty<PropertyValue<float>>& property, Duration delta = Duration::zero()) { +float evaluate(Transitioning<PropertyValue<float>>& property, Duration delta = Duration::zero()) { ZoomHistory zoomHistory; zoomHistory.update(0, TimePoint::min() + delta); @@ -25,7 +26,7 @@ float evaluate(TransitioningProperty<PropertyValue<float>>& property, Duration d return property.evaluate(evaluator, parameters.now); } -PossiblyEvaluatedPropertyValue<float> evaluate(TransitioningProperty<DataDrivenPropertyValue<float>>& property, Duration delta = Duration::zero()) { +PossiblyEvaluatedPropertyValue<float> evaluate(Transitioning<DataDrivenPropertyValue<float>>& property, Duration delta = Duration::zero()) { ZoomHistory zoomHistory; zoomHistory.update(0, TimePoint::min() + delta); @@ -43,15 +44,15 @@ PossiblyEvaluatedPropertyValue<float> evaluate(TransitioningProperty<DataDrivenP return property.evaluate(evaluator, parameters.now); } -TEST(TransitioningProperty, EvaluateDefaultValue) { - TransitioningProperty<PropertyValue<float>> property; +TEST(TransitioningPropertyValue, EvaluateDefaultValue) { + Transitioning<PropertyValue<float>> property; ASSERT_EQ(0.0f, evaluate(property)); } -TEST(TransitioningProperty, EvaluateUntransitionedConstant) { - TransitioningProperty<PropertyValue<float>> property { +TEST(TransitioningPropertyValue, EvaluateUntransitionedConstant) { + Transitioning<PropertyValue<float>> property { PropertyValue<float>(1.0f), - TransitioningProperty<PropertyValue<float>>(), + Transitioning<PropertyValue<float>>(), TransitionOptions(), TimePoint::min() }; @@ -59,18 +60,18 @@ TEST(TransitioningProperty, EvaluateUntransitionedConstant) { ASSERT_EQ(1.0f, evaluate(property)); } -TEST(TransitioningProperty, EvaluateTransitionedConstantWithoutDelay) { +TEST(TransitioningPropertyValue, EvaluateTransitionedConstantWithoutDelay) { TransitionOptions transition; transition.duration = { 1000ms }; - TransitioningProperty<PropertyValue<float>> t0 { + Transitioning<PropertyValue<float>> t0 { PropertyValue<float>(0.0f), - TransitioningProperty<PropertyValue<float>>(), + Transitioning<PropertyValue<float>>(), TransitionOptions(), TimePoint::min() }; - TransitioningProperty<PropertyValue<float>> t1 { + Transitioning<PropertyValue<float>> t1 { PropertyValue<float>(1.0f), t0, transition, @@ -82,19 +83,19 @@ TEST(TransitioningProperty, EvaluateTransitionedConstantWithoutDelay) { ASSERT_FLOAT_EQ(1.0f, evaluate(t1, 1500ms)); } -TEST(TransitioningProperty, EvaluateTransitionedConstantWithDelay) { +TEST(TransitioningPropertyValue, EvaluateTransitionedConstantWithDelay) { TransitionOptions transition; transition.delay = { 1000ms }; transition.duration = { 1000ms }; - TransitioningProperty<PropertyValue<float>> t0 { + Transitioning<PropertyValue<float>> t0 { PropertyValue<float>(0.0f), - TransitioningProperty<PropertyValue<float>>(), + Transitioning<PropertyValue<float>>(), TransitionOptions(), TimePoint::min() }; - TransitioningProperty<PropertyValue<float>> t1 { + Transitioning<PropertyValue<float>> t1 { PropertyValue<float>(1.0f), t0, transition, @@ -108,14 +109,14 @@ TEST(TransitioningProperty, EvaluateTransitionedConstantWithDelay) { ASSERT_FLOAT_EQ(1.0f, evaluate(t1, 2500ms)); } -TEST(TransitioningProperty, EvaluateDataDrivenValue) { +TEST(TransitioningDataDrivenPropertyValue, Evaluate) { TransitionOptions transition; transition.delay = { 1000ms }; transition.duration = { 1000ms }; - TransitioningProperty<DataDrivenPropertyValue<float>> t0 { + Transitioning<DataDrivenPropertyValue<float>> t0 { DataDrivenPropertyValue<float>(0.0f), - TransitioningProperty<DataDrivenPropertyValue<float>>(), + Transitioning<DataDrivenPropertyValue<float>>(), TransitionOptions(), TimePoint::min() }; @@ -125,7 +126,7 @@ TEST(TransitioningProperty, EvaluateDataDrivenValue) { IdentityStops<float>() }; - TransitioningProperty<DataDrivenPropertyValue<float>> t1 { + Transitioning<DataDrivenPropertyValue<float>> t1 { DataDrivenPropertyValue<float>(sourceFunction), t0, transition, diff --git a/test/style/source.test.cpp b/test/style/source.test.cpp index c60a473589..919260ffe9 100644 --- a/test/style/source.test.cpp +++ b/test/style/source.test.cpp @@ -3,10 +3,14 @@ #include <mbgl/test/stub_style_observer.hpp> #include <mbgl/test/stub_render_source_observer.hpp> +#include <mbgl/style/style.hpp> #include <mbgl/style/source_impl.hpp> #include <mbgl/style/sources/raster_source.hpp> #include <mbgl/style/sources/vector_source.hpp> #include <mbgl/style/sources/geojson_source.hpp> +#include <mbgl/style/sources/image_source.hpp> +#include <mbgl/style/layers/raster_layer.cpp> +#include <mbgl/style/layers/line_layer.hpp> #include <mbgl/renderer/sources/render_raster_source.hpp> #include <mbgl/renderer/sources/render_vector_source.hpp> @@ -16,6 +20,9 @@ #include <mbgl/util/run_loop.hpp> #include <mbgl/util/string.hpp> #include <mbgl/util/io.hpp> +#include <mbgl/util/premultiply.hpp> +#include <mbgl/util/image.hpp> + #include <mbgl/util/tileset.hpp> #include <mbgl/util/default_thread_pool.hpp> #include <mbgl/util/logging.hpp> @@ -23,12 +30,10 @@ #include <mbgl/util/range.hpp> #include <mbgl/map/transform.hpp> -#include <mbgl/style/style.hpp> -#include <mbgl/style/layers/line_layer.hpp> #include <mbgl/annotation/annotation_manager.hpp> #include <mbgl/annotation/annotation_source.hpp> - -#include <mapbox/geojsonvt.hpp> +#include <mbgl/renderer/image_manager.hpp> +#include <mbgl/text/glyph_manager.hpp> #include <cstdint> @@ -43,8 +48,10 @@ public: Transform transform; TransformState transformState; ThreadPool threadPool { 1 }; - AnnotationManager annotationManager { 1.0 }; - style::Style style { threadPool, fileSource, 1.0 }; + Style style { loop, fileSource, 1 }; + AnnotationManager annotationManager { style }; + ImageManager imageManager; + GlyphManager glyphManager { fileSource }; TileParameters tileParameters { 1.0, @@ -54,7 +61,9 @@ public: fileSource, MapMode::Continuous, annotationManager, - style + imageManager, + glyphManager, + 0 }; SourceTest() { @@ -95,8 +104,8 @@ TEST(Source, LoadingFail) { }; VectorSource source("source", "url"); - source.baseImpl->setObserver(&test.styleObserver); - source.baseImpl->loadDescription(test.fileSource); + source.setObserver(&test.styleObserver); + source.loadDescription(test.fileSource); test.run(); } @@ -118,8 +127,8 @@ TEST(Source, LoadingCorrupt) { }; VectorSource source("source", "url"); - source.baseImpl->setObserver(&test.styleObserver); - source.baseImpl->loadDescription(test.fileSource); + source.setObserver(&test.styleObserver); + source.loadDescription(test.fileSource); test.run(); } @@ -133,14 +142,17 @@ TEST(Source, RasterTileEmpty) { return response; }; + RasterLayer layer("id", "source"); + std::vector<Immutable<Layer::Impl>> layers {{ layer.baseImpl }}; + Tileset tileset; tileset.tiles = { "tiles" }; RasterSource source("source", tileset, 512); - source.baseImpl->loadDescription(test.fileSource); + source.loadDescription(test.fileSource); test.renderSourceObserver.tileChanged = [&] (RenderSource& source_, const OverscaledTileID&) { - EXPECT_EQ("source", source_.baseImpl.id); + EXPECT_EQ("source", source_.baseImpl->id); test.end(); }; @@ -148,9 +160,13 @@ TEST(Source, RasterTileEmpty) { FAIL() << "Should never be called"; }; - RenderRasterSource renderSource(*source.impl); - renderSource.setObserver(&test.renderSourceObserver); - renderSource.updateTiles(test.tileParameters); + auto renderSource = RenderSource::create(source.baseImpl); + renderSource->setObserver(&test.renderSourceObserver); + renderSource->update(source.baseImpl, + layers, + true, + true, + test.tileParameters); test.run(); } @@ -164,14 +180,19 @@ TEST(Source, VectorTileEmpty) { return response; }; + LineLayer layer("id", "source"); + layer.setSourceLayer("water"); + + std::vector<Immutable<Layer::Impl>> layers {{ layer.baseImpl }}; + Tileset tileset; tileset.tiles = { "tiles" }; VectorSource source("source", tileset); - source.baseImpl->loadDescription(test.fileSource); + source.loadDescription(test.fileSource); test.renderSourceObserver.tileChanged = [&] (RenderSource& source_, const OverscaledTileID&) { - EXPECT_EQ("source", source_.baseImpl.id); + EXPECT_EQ("source", source_.baseImpl->id); test.end(); }; @@ -179,9 +200,13 @@ TEST(Source, VectorTileEmpty) { FAIL() << "Should never be called"; }; - RenderVectorSource renderSource(*source.impl); - renderSource.setObserver(&test.renderSourceObserver); - renderSource.updateTiles(test.tileParameters); + auto renderSource = RenderSource::create(source.baseImpl); + renderSource->setObserver(&test.renderSourceObserver); + renderSource->update(source.baseImpl, + layers, + true, + true, + test.tileParameters); test.run(); } @@ -197,22 +222,29 @@ TEST(Source, RasterTileFail) { return response; }; + RasterLayer layer("id", "source"); + std::vector<Immutable<Layer::Impl>> layers {{ layer.baseImpl }}; + Tileset tileset; tileset.tiles = { "tiles" }; RasterSource source("source", tileset, 512); - source.baseImpl->loadDescription(test.fileSource); + source.loadDescription(test.fileSource); test.renderSourceObserver.tileError = [&] (RenderSource& source_, const OverscaledTileID& tileID, std::exception_ptr error) { - EXPECT_EQ(SourceType::Raster, source_.baseImpl.type); + EXPECT_EQ(SourceType::Raster, source_.baseImpl->type); EXPECT_EQ(OverscaledTileID(0, 0, 0), tileID); EXPECT_EQ("Failed by the test case", util::toString(error)); test.end(); }; - RenderRasterSource renderSource(*source.impl); - renderSource.setObserver(&test.renderSourceObserver); - renderSource.updateTiles(test.tileParameters); + auto renderSource = RenderSource::create(source.baseImpl); + renderSource->setObserver(&test.renderSourceObserver); + renderSource->update(source.baseImpl, + layers, + true, + true, + test.tileParameters); test.run(); } @@ -228,22 +260,31 @@ TEST(Source, VectorTileFail) { return response; }; + LineLayer layer("id", "source"); + layer.setSourceLayer("water"); + + std::vector<Immutable<Layer::Impl>> layers {{ layer.baseImpl }}; + Tileset tileset; tileset.tiles = { "tiles" }; VectorSource source("source", tileset); - source.baseImpl->loadDescription(test.fileSource); + source.loadDescription(test.fileSource); test.renderSourceObserver.tileError = [&] (RenderSource& source_, const OverscaledTileID& tileID, std::exception_ptr error) { - EXPECT_EQ(SourceType::Vector, source_.baseImpl.type); + EXPECT_EQ(SourceType::Vector, source_.baseImpl->type); EXPECT_EQ(OverscaledTileID(0, 0, 0), tileID); EXPECT_EQ("Failed by the test case", util::toString(error)); test.end(); }; - RenderVectorSource renderSource(*source.impl); - renderSource.setObserver(&test.renderSourceObserver); - renderSource.updateTiles(test.tileParameters); + auto renderSource = RenderSource::create(source.baseImpl); + renderSource->setObserver(&test.renderSourceObserver); + renderSource->update(source.baseImpl, + layers, + true, + true, + test.tileParameters); test.run(); } @@ -257,23 +298,30 @@ TEST(Source, RasterTileCorrupt) { return response; }; + RasterLayer layer("id", "source"); + std::vector<Immutable<Layer::Impl>> layers {{ layer.baseImpl }}; + Tileset tileset; tileset.tiles = { "tiles" }; RasterSource source("source", tileset, 512); - source.baseImpl->loadDescription(test.fileSource); + source.loadDescription(test.fileSource); test.renderSourceObserver.tileError = [&] (RenderSource& source_, const OverscaledTileID& tileID, std::exception_ptr error) { - EXPECT_EQ(source_.baseImpl.type, SourceType::Raster); + EXPECT_EQ(source_.baseImpl->type, SourceType::Raster); EXPECT_EQ(OverscaledTileID(0, 0, 0), tileID); EXPECT_TRUE(bool(error)); // Not asserting on platform-specific error text. test.end(); }; - RenderRasterSource renderSource(*source.impl); - renderSource.setObserver(&test.renderSourceObserver); - renderSource.updateTiles(test.tileParameters); + auto renderSource = RenderSource::create(source.baseImpl); + renderSource->setObserver(&test.renderSourceObserver); + renderSource->update(source.baseImpl, + layers, + true, + true, + test.tileParameters); test.run(); } @@ -287,27 +335,31 @@ TEST(Source, VectorTileCorrupt) { return response; }; - // Need to have at least one layer that uses the source. - auto layer = std::make_unique<LineLayer>("id", "source"); - layer->setSourceLayer("water"); - test.style.addLayer(std::move(layer)); + LineLayer layer("id", "source"); + layer.setSourceLayer("water"); + + std::vector<Immutable<Layer::Impl>> layers {{ layer.baseImpl }}; Tileset tileset; tileset.tiles = { "tiles" }; VectorSource source("source", tileset); - source.baseImpl->loadDescription(test.fileSource); + source.loadDescription(test.fileSource); test.renderSourceObserver.tileError = [&] (RenderSource& source_, const OverscaledTileID& tileID, std::exception_ptr error) { - EXPECT_EQ(source_.baseImpl.type, SourceType::Vector); + EXPECT_EQ(source_.baseImpl->type, SourceType::Vector); EXPECT_EQ(OverscaledTileID(0, 0, 0), tileID); EXPECT_EQ(util::toString(error), "unknown pbf field type exception"); test.end(); }; - RenderVectorSource renderSource(*source.impl); - renderSource.setObserver(&test.renderSourceObserver); - renderSource.updateTiles(test.tileParameters); + auto renderSource = RenderSource::create(source.baseImpl); + renderSource->setObserver(&test.renderSourceObserver); + renderSource->update(source.baseImpl, + layers, + true, + true, + test.tileParameters); test.run(); } @@ -320,11 +372,14 @@ TEST(Source, RasterTileCancel) { return optional<Response>(); }; + RasterLayer layer("id", "source"); + std::vector<Immutable<Layer::Impl>> layers {{ layer.baseImpl }}; + Tileset tileset; tileset.tiles = { "tiles" }; RasterSource source("source", tileset, 512); - source.baseImpl->loadDescription(test.fileSource); + source.loadDescription(test.fileSource); test.renderSourceObserver.tileChanged = [&] (RenderSource&, const OverscaledTileID&) { FAIL() << "Should never be called"; @@ -334,9 +389,13 @@ TEST(Source, RasterTileCancel) { FAIL() << "Should never be called"; }; - RenderRasterSource renderSource(*source.impl); - renderSource.setObserver(&test.renderSourceObserver); - renderSource.updateTiles(test.tileParameters); + auto renderSource = RenderSource::create(source.baseImpl); + renderSource->setObserver(&test.renderSourceObserver); + renderSource->update(source.baseImpl, + layers, + true, + true, + test.tileParameters); test.run(); } @@ -349,11 +408,16 @@ TEST(Source, VectorTileCancel) { return optional<Response>(); }; + LineLayer layer("id", "source"); + layer.setSourceLayer("water"); + + std::vector<Immutable<Layer::Impl>> layers {{ layer.baseImpl }}; + Tileset tileset; tileset.tiles = { "tiles" }; VectorSource source("source", tileset); - source.baseImpl->loadDescription(test.fileSource); + source.loadDescription(test.fileSource); test.renderSourceObserver.tileChanged = [&] (RenderSource&, const OverscaledTileID&) { FAIL() << "Should never be called"; @@ -363,9 +427,13 @@ TEST(Source, VectorTileCancel) { FAIL() << "Should never be called"; }; - RenderVectorSource renderSource(*source.impl); - renderSource.setObserver(&test.renderSourceObserver); - renderSource.updateTiles(test.tileParameters); + auto renderSource = RenderSource::create(source.baseImpl); + renderSource->setObserver(&test.renderSourceObserver); + renderSource->update(source.baseImpl, + layers, + true, + true, + test.tileParameters); test.run(); } @@ -373,6 +441,9 @@ TEST(Source, VectorTileCancel) { TEST(Source, RasterTileAttribution) { SourceTest test; + RasterLayer layer("id", "source"); + std::vector<Immutable<Layer::Impl>> layers {{ layer.baseImpl }}; + std::string mapboxOSM = ("<a href='https://www.mapbox.com/about/maps/' target='_blank'>© Mapbox</a> " "<a href='http://www.openstreetmap.org/about/' target='_blank'>©️ OpenStreetMap</a>"); @@ -398,11 +469,15 @@ TEST(Source, RasterTileAttribution) { }; RasterSource source("source", "url", 512); - source.baseImpl->setObserver(&test.styleObserver); - source.baseImpl->loadDescription(test.fileSource); + source.setObserver(&test.styleObserver); + source.loadDescription(test.fileSource); - RenderRasterSource renderSource(*source.impl); - renderSource.updateTiles(test.tileParameters); + auto renderSource = RenderSource::create(source.baseImpl); + renderSource->update(source.baseImpl, + layers, + true, + true, + test.tileParameters); test.run(); } @@ -413,7 +488,7 @@ TEST(Source, GeoJSonSourceUrlUpdate) { test.fileSource.sourceResponse = [&] (const Resource& resource) { EXPECT_EQ("url", resource.url); Response response; - response.data = std::make_unique<std::string>("{\"geometry\": {\"type\": \"Point\", \"coordinates\": [1.1, 1.1]}, \"type\": \"Feature\", \"properties\": {}}"); + response.data = std::make_unique<std::string>(R"({"geometry": {"type": "Point", "coordinates": [1.1, 1.1]}, "type": "Feature", "properties": {}})"); return response; }; @@ -423,10 +498,10 @@ TEST(Source, GeoJSonSourceUrlUpdate) { }; GeoJSONSource source("source"); - source.baseImpl->setObserver(&test.styleObserver); + source.setObserver(&test.styleObserver); // Load initial, so the source state will be loaded=true - source.baseImpl->loadDescription(test.fileSource); + source.loadDescription(test.fileSource); // Schedule an update test.loop.invoke([&] () { @@ -436,3 +511,39 @@ TEST(Source, GeoJSonSourceUrlUpdate) { test.run(); } + +TEST(Source, ImageSourceImageUpdate) { + SourceTest test; + + test.fileSource.response = [&] (const Resource& resource) { + EXPECT_EQ("http://url", resource.url); + Response response; + response.data = std::make_unique<std::string>(util::read_file("test/fixtures/image/no_profile.png")); + return response; + }; + test.styleObserver.sourceChanged = [&] (Source&) { + // Should be called (test will hang if it doesn't) + test.end(); + }; + std::array<LatLng, 4> coords; + + ImageSource source("source", coords); + source.setURL("http://url"); + source.setObserver(&test.styleObserver); + + // Load initial, so the source state will be loaded=true + source.loadDescription(test.fileSource); + PremultipliedImage rgba({ 1, 1 }); + rgba.data[0] = 255; + rgba.data[1] = 254; + rgba.data[2] = 253; + rgba.data[3] = 0; + + // Schedule an update + test.loop.invoke([&] () { + // Update the url + source.setImage(std::move(rgba)); + }); + + test.run(); +} diff --git a/test/style/style.test.cpp b/test/style/style.test.cpp index 841c7b291b..9bdab37ac6 100644 --- a/test/style/style.test.cpp +++ b/test/style/style.test.cpp @@ -2,7 +2,7 @@ #include <mbgl/test/stub_file_source.hpp> #include <mbgl/test/fixture_log_observer.hpp> -#include <mbgl/style/style.hpp> +#include <mbgl/style/style_impl.hpp> #include <mbgl/style/source_impl.hpp> #include <mbgl/style/sources/vector_source.hpp> #include <mbgl/style/layer.hpp> @@ -21,34 +21,34 @@ TEST(Style, Properties) { ThreadPool threadPool{ 1 }; StubFileSource fileSource; - Style style { threadPool, fileSource, 1.0 }; + Style::Impl style { threadPool, fileSource, 1.0 }; - style.setJSON(R"STYLE({"name": "Test"})STYLE"); + style.loadJSON(R"STYLE({"name": "Test"})STYLE"); ASSERT_EQ("Test", style.getName()); - style.setJSON(R"STYLE({"center": [10, 20]})STYLE"); + style.loadJSON(R"STYLE({"center": [10, 20]})STYLE"); ASSERT_EQ("", style.getName()); - ASSERT_EQ((LatLng{20, 10}), style.getDefaultLatLng()); + ASSERT_EQ((LatLng{20, 10}), *style.getDefaultCamera().center); - style.setJSON(R"STYLE({"bearing": 24})STYLE"); + style.loadJSON(R"STYLE({"bearing": 24})STYLE"); ASSERT_EQ("", style.getName()); - ASSERT_EQ((LatLng{0, 0}), style.getDefaultLatLng()); - ASSERT_EQ(24, style.getDefaultBearing()); + ASSERT_EQ(LatLng {}, *style.getDefaultCamera().center); + ASSERT_EQ(24, *style.getDefaultCamera().angle); - style.setJSON(R"STYLE({"zoom": 13.3})STYLE"); + style.loadJSON(R"STYLE({"zoom": 13.3})STYLE"); ASSERT_EQ("", style.getName()); - ASSERT_EQ(13.3, style.getDefaultZoom()); + ASSERT_EQ(13.3, *style.getDefaultCamera().zoom); - style.setJSON(R"STYLE({"pitch": 60})STYLE"); + style.loadJSON(R"STYLE({"pitch": 60})STYLE"); ASSERT_EQ("", style.getName()); - ASSERT_EQ(60, style.getDefaultPitch()); + ASSERT_EQ(60, *style.getDefaultCamera().pitch); - style.setJSON(R"STYLE({"name": 23, "center": {}, "bearing": "north", "zoom": null, "pitch": "wide"})STYLE"); + style.loadJSON(R"STYLE({"name": 23, "center": {}, "bearing": "north", "zoom": null, "pitch": "wide"})STYLE"); ASSERT_EQ("", style.getName()); - ASSERT_EQ((LatLng{0, 0}), style.getDefaultLatLng()); - ASSERT_EQ(0, style.getDefaultBearing()); - ASSERT_EQ(0, style.getDefaultZoom()); - ASSERT_EQ(0, style.getDefaultPitch()); + ASSERT_EQ(LatLng {}, *style.getDefaultCamera().center); + ASSERT_EQ(0, *style.getDefaultCamera().zoom); + ASSERT_EQ(0, *style.getDefaultCamera().angle); + ASSERT_EQ(0, *style.getDefaultCamera().pitch); } TEST(Style, DuplicateSource) { @@ -56,9 +56,9 @@ TEST(Style, DuplicateSource) { ThreadPool threadPool{ 1 }; StubFileSource fileSource; - Style style { threadPool, fileSource, 1.0 }; + Style::Impl style { threadPool, fileSource, 1.0 }; - style.setJSON(util::read_file("test/fixtures/resources/style-unused-sources.json")); + style.loadJSON(util::read_file("test/fixtures/resources/style-unused-sources.json")); style.addSource(std::make_unique<VectorSource>("sourceId", "mapbox://mapbox.mapbox-terrain-v2")); @@ -78,9 +78,9 @@ TEST(Style, RemoveSourceInUse) { ThreadPool threadPool{ 1 }; StubFileSource fileSource; - Style style { threadPool, fileSource, 1.0 }; + Style::Impl style { threadPool, fileSource, 1.0 }; - style.setJSON(util::read_file("test/fixtures/resources/style-unused-sources.json")); + style.loadJSON(util::read_file("test/fixtures/resources/style-unused-sources.json")); style.addSource(std::make_unique<VectorSource>("sourceId", "mapbox://mapbox.mapbox-terrain-v2")); style.addLayer(std::make_unique<LineLayer>("layerId", "sourceId")); diff --git a/test/style/style_image.test.cpp b/test/style/style_image.test.cpp index 319120df83..e49bf37582 100644 --- a/test/style/style_image.test.cpp +++ b/test/style/style_image.test.cpp @@ -8,7 +8,7 @@ using namespace mbgl; TEST(StyleImage, ZeroWidth) { try { - style::Image(PremultipliedImage({ 0, 16 }), 2.0); + style::Image("test", PremultipliedImage({ 0, 16 }), 2.0); FAIL() << "Expected exception"; } catch (util::SpriteImageException& ex) { EXPECT_STREQ("Sprite image dimensions may not be zero", ex.what()); @@ -17,7 +17,7 @@ TEST(StyleImage, ZeroWidth) { TEST(StyleImage, ZeroHeight) { try { - style::Image(PremultipliedImage({ 16, 0 }), 2.0); + style::Image("test", PremultipliedImage({ 16, 0 }), 2.0); FAIL() << "Expected exception"; } catch (util::SpriteImageException& ex) { EXPECT_STREQ("Sprite image dimensions may not be zero", ex.what()); @@ -26,7 +26,7 @@ TEST(StyleImage, ZeroHeight) { TEST(StyleImage, ZeroRatio) { try { - style::Image(PremultipliedImage({ 16, 16 }), 0.0); + style::Image("test", PremultipliedImage({ 16, 16 }), 0.0); FAIL() << "Expected exception"; } catch (util::SpriteImageException& ex) { EXPECT_STREQ("Sprite pixelRatio may not be <= 0", ex.what()); @@ -34,19 +34,15 @@ TEST(StyleImage, ZeroRatio) { } TEST(StyleImage, Retina) { - style::Image image(PremultipliedImage({ 32, 24 }), 2.0); - EXPECT_EQ(16, image.getWidth()); - EXPECT_EQ(32u, image.image.size.width); - EXPECT_EQ(12, image.getHeight()); - EXPECT_EQ(24u, image.image.size.height); - EXPECT_EQ(2, image.pixelRatio); + style::Image image("test", PremultipliedImage({ 32, 24 }), 2.0); + EXPECT_EQ(32u, image.getImage().size.width); + EXPECT_EQ(24u, image.getImage().size.height); + EXPECT_EQ(2, image.getPixelRatio()); } TEST(StyleImage, FractionalRatio) { - style::Image image(PremultipliedImage({ 20, 12 }), 1.5); - EXPECT_EQ(float(20.0 / 1.5), image.getWidth()); - EXPECT_EQ(20u, image.image.size.width); - EXPECT_EQ(float(12.0 / 1.5), image.getHeight()); - EXPECT_EQ(12u, image.image.size.height); - EXPECT_EQ(1.5, image.pixelRatio); + style::Image image("test", PremultipliedImage({ 20, 12 }), 1.5); + EXPECT_EQ(20u, image.getImage().size.width); + EXPECT_EQ(12u, image.getImage().size.height); + EXPECT_EQ(1.5, image.getPixelRatio()); } diff --git a/test/style/style_layer.test.cpp b/test/style/style_layer.test.cpp index 657dc24a70..77acca2868 100644 --- a/test/style/style_layer.test.cpp +++ b/test/style/style_layer.test.cpp @@ -1,7 +1,7 @@ #include <mbgl/test/util.hpp> #include <mbgl/test/stub_layer_observer.hpp> #include <mbgl/test/stub_file_source.hpp> -#include <mbgl/style/style.hpp> +#include <mbgl/style/style_impl.hpp> #include <mbgl/style/layers/background_layer.hpp> #include <mbgl/style/layers/background_layer_impl.hpp> #include <mbgl/style/layers/circle_layer.hpp> @@ -28,15 +28,6 @@ using namespace mbgl::style; namespace { -template <class T, class... Params> void testClone(Params... params) { - auto layer = std::make_unique<T>(std::forward<Params>(params)...); - auto clone = layer->baseImpl->clone(); - EXPECT_NE(layer.get(), clone.get()); - EXPECT_TRUE(reinterpret_cast<typename T::Impl*>(clone->baseImpl.get())); - layer->impl->id = "test"; - EXPECT_EQ("test", layer->baseImpl->clone()->getID()); -} - const auto color = Color { 1, 0, 0, 1 }; const auto opacity = 1.0f; const auto radius = 1.0f; @@ -61,16 +52,6 @@ const auto duration = 1.0f; } // namespace -TEST(Layer, Clone) { - testClone<BackgroundLayer>("background"); - testClone<CircleLayer>("circle", "source"); - testClone<CustomLayer>("custom", [](void*){}, [](void*, const CustomLayerRenderParameters&){}, [](void*){}, nullptr), - testClone<FillLayer>("fill", "source"); - testClone<LineLayer>("line", "source"); - testClone<RasterLayer>("raster", "source"); - testClone<SymbolLayer>("symbol", "source"); -} - TEST(Layer, BackgroundProperties) { auto layer = std::make_unique<BackgroundLayer>("background"); EXPECT_TRUE(layer->is<BackgroundLayer>()); @@ -222,11 +203,11 @@ TEST(Layer, RasterProperties) { TEST(Layer, Observer) { auto layer = std::make_unique<LineLayer>("line", "source"); StubLayerObserver observer; - layer->baseImpl->setObserver(&observer); + layer->setObserver(&observer); // Notifies observer on filter change. bool filterChanged = false; - observer.layerFilterChanged = [&] (Layer& layer_) { + observer.layerChanged = [&] (Layer& layer_) { EXPECT_EQ(layer.get(), &layer_); filterChanged = true; }; @@ -235,7 +216,7 @@ TEST(Layer, Observer) { // Notifies observer on visibility change. bool visibilityChanged = false; - observer.layerVisibilityChanged = [&] (Layer& layer_) { + observer.layerChanged = [&] (Layer& layer_) { EXPECT_EQ(layer.get(), &layer_); visibilityChanged = true; }; @@ -244,7 +225,7 @@ TEST(Layer, Observer) { // Notifies observer on paint property change. bool paintPropertyChanged = false; - observer.layerPaintPropertyChanged = [&] (Layer& layer_) { + observer.layerChanged = [&] (Layer& layer_) { EXPECT_EQ(layer.get(), &layer_); paintPropertyChanged = true; }; @@ -253,7 +234,7 @@ TEST(Layer, Observer) { // Notifies observer on layout property change. bool layoutPropertyChanged = false; - observer.layerLayoutPropertyChanged = [&] (Layer& layer_, const char *) { + observer.layerChanged = [&] (Layer& layer_) { EXPECT_EQ(layer.get(), &layer_); layoutPropertyChanged = true; }; @@ -262,16 +243,28 @@ TEST(Layer, Observer) { // Does not notify observer on no-op visibility change. visibilityChanged = false; + observer.layerChanged = [&] (Layer& layer_) { + EXPECT_EQ(layer.get(), &layer_); + visibilityChanged = true; + }; layer->setVisibility(VisibilityType::None); EXPECT_FALSE(visibilityChanged); // Does not notify observer on no-op paint property change. paintPropertyChanged = false; + observer.layerChanged = [&] (Layer& layer_) { + EXPECT_EQ(layer.get(), &layer_); + paintPropertyChanged = true; + }; layer->setLineColor(color); EXPECT_FALSE(paintPropertyChanged); // Does not notify observer on no-op layout property change. layoutPropertyChanged = false; + observer.layerChanged = [&] (Layer& layer_) { + EXPECT_EQ(layer.get(), &layer_); + layoutPropertyChanged = true; + }; layer->setLineCap(lineCap); EXPECT_FALSE(layoutPropertyChanged); } @@ -282,8 +275,8 @@ TEST(Layer, DuplicateLayer) { // Setup style ThreadPool threadPool{ 1 }; StubFileSource fileSource; - Style style { threadPool, fileSource, 1.0 }; - style.setJSON(util::read_file("test/fixtures/resources/style-unused-sources.json")); + Style::Impl style { threadPool, fileSource, 1.0 }; + style.loadJSON(util::read_file("test/fixtures/resources/style-unused-sources.json")); // Add initial layer style.addLayer(std::make_unique<LineLayer>("line", "unusedsource")); diff --git a/test/style/style_parser.test.cpp b/test/style/style_parser.test.cpp index e3c1da582f..5fa81b47e9 100644 --- a/test/style/style_parser.test.cpp +++ b/test/style/style_parser.test.cpp @@ -16,8 +16,8 @@ using namespace mbgl; -typedef std::pair<uint32_t, std::string> Message; -typedef std::vector<Message> Messages; +using Message = std::pair<uint32_t, std::string>; +using Messages = std::vector<Message>; class StyleParserTest : public ::testing::TestWithParam<std::string> {}; diff --git a/test/text/glyph_atlas.test.cpp b/test/text/glyph_loader.test.cpp index 5aff1ee441..be197ebb46 100644 --- a/test/text/glyph_atlas.test.cpp +++ b/test/text/glyph_loader.test.cpp @@ -1,8 +1,7 @@ #include <mbgl/test/util.hpp> #include <mbgl/test/stub_file_source.hpp> -#include <mbgl/test/stub_style_observer.hpp> -#include <mbgl/text/glyph_atlas.hpp> +#include <mbgl/text/glyph_manager.hpp> #include <mbgl/util/run_loop.hpp> #include <mbgl/util/string.hpp> #include <mbgl/util/io.hpp> @@ -10,30 +9,44 @@ using namespace mbgl; +class StubGlyphManagerObserver : public GlyphManagerObserver { +public: + void onGlyphsLoaded(const FontStack& fontStack, const GlyphRange& glyphRange) override { + if (glyphsLoaded) glyphsLoaded(fontStack, glyphRange); + } + + void onGlyphsError(const FontStack& fontStack, const GlyphRange& glyphRange, std::exception_ptr error) override { + if (glyphsError) glyphsError(fontStack, glyphRange, error); + } + + std::function<void (const FontStack&, const GlyphRange&)> glyphsLoaded; + std::function<void (const FontStack&, const GlyphRange&, std::exception_ptr)> glyphsError; +}; + class StubGlyphRequestor : public GlyphRequestor { public: - void onGlyphsAvailable(GlyphPositionMap positions) override { - if (glyphsAvailable) glyphsAvailable(std::move(positions)); + void onGlyphsAvailable(GlyphMap glyphs) override { + if (glyphsAvailable) glyphsAvailable(std::move(glyphs)); } - std::function<void (GlyphPositionMap)> glyphsAvailable; + std::function<void (GlyphMap)> glyphsAvailable; }; -class GlyphAtlasTest { +class GlyphManagerTest { public: util::RunLoop loop; StubFileSource fileSource; - StubStyleObserver observer; + StubGlyphManagerObserver observer; StubGlyphRequestor requestor; - GlyphAtlas glyphAtlas{ { 32, 32 }, fileSource }; + GlyphManager glyphManager { fileSource }; void run(const std::string& url, GlyphDependencies dependencies) { // Squelch logging. Log::setObserver(std::make_unique<Log::NullObserver>()); - glyphAtlas.setURL(url); - glyphAtlas.setObserver(&observer); - glyphAtlas.getGlyphs(requestor, std::move(dependencies)); + glyphManager.setURL(url); + glyphManager.setObserver(&observer); + glyphManager.getGlyphs(requestor, std::move(dependencies)); loop.run(); } @@ -43,8 +56,8 @@ public: } }; -TEST(GlyphAtlas, LoadingSuccess) { - GlyphAtlasTest test; +TEST(GlyphManager, LoadingSuccess) { + GlyphManagerTest test; test.fileSource.glyphsResponse = [&] (const Resource& resource) { EXPECT_EQ(Resource::Kind::Glyphs, resource.kind); @@ -63,8 +76,8 @@ TEST(GlyphAtlas, LoadingSuccess) { ASSERT_EQ(range, GlyphRange(0, 255)); }; - test.requestor.glyphsAvailable = [&] (GlyphPositionMap positions) { - const auto& testPositions = positions.at({{"Test Stack"}}); + test.requestor.glyphsAvailable = [&] (GlyphMap glyphs) { + const auto& testPositions = glyphs.at({{"Test Stack"}}); ASSERT_EQ(testPositions.size(), 3u); ASSERT_EQ(testPositions.count(u'a'), 1u); @@ -82,8 +95,8 @@ TEST(GlyphAtlas, LoadingSuccess) { }); } -TEST(GlyphAtlas, LoadingFail) { - GlyphAtlasTest test; +TEST(GlyphManager, LoadingFail) { + GlyphManagerTest test; test.fileSource.glyphsResponse = [&] (const Resource&) { Response response; @@ -103,7 +116,7 @@ TEST(GlyphAtlas, LoadingFail) { test.end(); }; - test.requestor.glyphsAvailable = [&] (GlyphPositionMap) { + test.requestor.glyphsAvailable = [&] (GlyphMap) { FAIL(); test.end(); }; @@ -115,8 +128,8 @@ TEST(GlyphAtlas, LoadingFail) { }); } -TEST(GlyphAtlas, LoadingCorrupted) { - GlyphAtlasTest test; +TEST(GlyphManager, LoadingCorrupted) { + GlyphManagerTest test; test.fileSource.glyphsResponse = [&] (const Resource&) { Response response; @@ -134,7 +147,7 @@ TEST(GlyphAtlas, LoadingCorrupted) { test.end(); }; - test.requestor.glyphsAvailable = [&] (GlyphPositionMap) { + test.requestor.glyphsAvailable = [&] (GlyphMap) { FAIL(); test.end(); }; @@ -146,8 +159,8 @@ TEST(GlyphAtlas, LoadingCorrupted) { }); } -TEST(GlyphAtlas, LoadingCancel) { - GlyphAtlasTest test; +TEST(GlyphManager, LoadingCancel) { + GlyphManagerTest test; test.fileSource.glyphsResponse = [&] (const Resource&) { test.end(); @@ -165,8 +178,8 @@ TEST(GlyphAtlas, LoadingCancel) { }); } -TEST(GlyphAtlas, LoadingInvalid) { - GlyphAtlasTest test; +TEST(GlyphManager, LoadingInvalid) { + GlyphManagerTest test; test.fileSource.glyphsResponse = [&] (const Resource& resource) { EXPECT_EQ(Resource::Kind::Glyphs, resource.kind); @@ -185,8 +198,8 @@ TEST(GlyphAtlas, LoadingInvalid) { ASSERT_EQ(range, GlyphRange(0, 255)); }; - test.requestor.glyphsAvailable = [&] (GlyphPositionMap positions) { - const auto& testPositions = positions.at({{"Test Stack"}}); + test.requestor.glyphsAvailable = [&] (GlyphMap glyphs) { + const auto& testPositions = glyphs.at({{"Test Stack"}}); ASSERT_EQ(testPositions.size(), 2u); ASSERT_FALSE(bool(testPositions.at(u'A'))); diff --git a/test/text/quads.test.cpp b/test/text/quads.test.cpp index 83fd249535..8eedd9bd2e 100644 --- a/test/text/quads.test.cpp +++ b/test/text/quads.test.cpp @@ -1,5 +1,5 @@ #include <mbgl/geometry/anchor.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> +#include <mbgl/style/image_impl.hpp> #include <mbgl/test/util.hpp> #include <mbgl/text/quads.hpp> #include <mbgl/text/shaping.hpp> @@ -12,48 +12,37 @@ using namespace mbgl::style; TEST(getIconQuads, normal) { SymbolLayoutProperties::Evaluated layout; Anchor anchor(2.0, 3.0, 0.0, 0.5f, 0); - SpriteAtlasElement image = { - Rect<uint16_t>( 0, 0, 15, 11 ), - style::Image(PremultipliedImage({1,1}), 1.0), - { 0, 0 }, - 1.0f + ImagePosition image = { + mapbox::Bin(-1, 15, 11, 0, 0, 0, 0), + style::Image::Impl("test", PremultipliedImage({1,1}), 1.0) }; - auto shapedIcon = PositionedIcon::shapeIcon(image, {{ -6.5f, -4.5f }}, 0); - ASSERT_TRUE(shapedIcon); + auto shapedIcon = PositionedIcon::shapeIcon(image, {{ -6.5f, -4.5f }}, SymbolAnchorType::Center, 0); GeometryCoordinates line; Shaping shapedText; SymbolQuad quad = - getIconQuad(anchor, *shapedIcon, line, layout, 16.0f, SymbolPlacementType::Point, shapedText); - - ASSERT_EQ(quad.anchorPoint.x, 2); - ASSERT_EQ(quad.anchorPoint.y, 3); - ASSERT_EQ(quad.tl.x, -8); - ASSERT_EQ(quad.tl.y, -6); - ASSERT_EQ(quad.tr.x, 7); - ASSERT_EQ(quad.tr.y, -6); - ASSERT_EQ(quad.bl.x, -8); - ASSERT_EQ(quad.bl.y, 5); - ASSERT_EQ(quad.br.x, 7); - ASSERT_EQ(quad.br.y, 5); - ASSERT_EQ(quad.anchorAngle, 0.0f); - ASSERT_EQ(quad.glyphAngle, 0.0f); - ASSERT_EQ(quad.minScale, 0.5f); + getIconQuad(shapedIcon, layout, 16.0f, shapedText); + + EXPECT_EQ(quad.tl.x, -14); + EXPECT_EQ(quad.tl.y, -10); + EXPECT_EQ(quad.tr.x, 1); + EXPECT_EQ(quad.tr.y, -10); + EXPECT_EQ(quad.bl.x, -14); + EXPECT_EQ(quad.bl.y, 1); + EXPECT_EQ(quad.br.x, 1); + EXPECT_EQ(quad.br.y, 1); } TEST(getIconQuads, style) { Anchor anchor(0.0, 0.0, 0.0, 0.5f, 0); - SpriteAtlasElement image = { - Rect<uint16_t>( 0, 0, 20, 20 ), - style::Image(PremultipliedImage({1,1}), 1.0), - { 0, 0 }, - 1.0f + ImagePosition image = { + mapbox::Bin(-1, 20, 20, 0, 0, 0, 0), + style::Image::Impl("test", PremultipliedImage({1,1}), 1.0) }; - auto shapedIcon = PositionedIcon::shapeIcon(image, {{ -9.5f, -9.5f }}, 0); - ASSERT_TRUE(shapedIcon); + auto shapedIcon = PositionedIcon::shapeIcon(image, {{ -9.5f, -9.5f }}, SymbolAnchorType::Center, 0); GeometryCoordinates line; Shaping shapedText; @@ -61,27 +50,22 @@ TEST(getIconQuads, style) { shapedText.bottom = 30.0f; shapedText.left = -60.0f; shapedText.right = 20.0f; - shapedText.positionedGlyphs.emplace_back(PositionedGlyph(32, 0.0f, 0.0f, 0)); + shapedText.positionedGlyphs.emplace_back(PositionedGlyph(32, 0.0f, 0.0f, false)); // none { SymbolLayoutProperties::Evaluated layout; SymbolQuad quad = - getIconQuad(anchor, *shapedIcon, line, layout, 12.0f, SymbolPlacementType::Point, shapedText); - - ASSERT_EQ(quad.anchorPoint.x, 0); - ASSERT_EQ(quad.anchorPoint.y, 0); - ASSERT_EQ(quad.tl.x, -11); - ASSERT_EQ(quad.tl.y, -11); - ASSERT_EQ(quad.tr.x, 9); - ASSERT_EQ(quad.tr.y, -11); - ASSERT_EQ(quad.bl.x, -11); - ASSERT_EQ(quad.bl.y, 9); - ASSERT_EQ(quad.br.x, 9); - ASSERT_EQ(quad.br.y, 9); - ASSERT_EQ(quad.anchorAngle, 0.0f); - ASSERT_EQ(quad.glyphAngle, 0.0f); - ASSERT_EQ(quad.minScale, 0.5f); + getIconQuad(shapedIcon, layout, 12.0f, shapedText); + + EXPECT_EQ(quad.tl.x, -19.5); + EXPECT_EQ(quad.tl.y, -19.5); + EXPECT_EQ(quad.tr.x, 0.5); + EXPECT_EQ(quad.tr.y, -19.5); + EXPECT_EQ(quad.bl.x, -19.5); + EXPECT_EQ(quad.bl.y, 0.5); + EXPECT_EQ(quad.br.x, 0.5); + EXPECT_EQ(quad.br.y, 0.5); } // width @@ -90,16 +74,16 @@ TEST(getIconQuads, style) { layout.get<TextSize>() = 24.0f; layout.get<IconTextFit>() = IconTextFitType::Width; SymbolQuad quad = - getIconQuad(anchor, *shapedIcon, line, layout, 24.0f, SymbolPlacementType::Point, shapedText); - - ASSERT_EQ(quad.tl.x, -60); - ASSERT_EQ(quad.tl.y, 0); - ASSERT_EQ(quad.tr.x, 20); - ASSERT_EQ(quad.tr.y, 0); - ASSERT_EQ(quad.bl.x, -60); - ASSERT_EQ(quad.bl.y, 20); - ASSERT_EQ(quad.br.x, 20); - ASSERT_EQ(quad.br.y, 20); + getIconQuad(shapedIcon, layout, 24.0f, shapedText); + + EXPECT_EQ(quad.tl.x, -60); + EXPECT_EQ(quad.tl.y, 0); + EXPECT_EQ(quad.tr.x, 20); + EXPECT_EQ(quad.tr.y, 0); + EXPECT_EQ(quad.bl.x, -60); + EXPECT_EQ(quad.bl.y, 20); + EXPECT_EQ(quad.br.x, 20); + EXPECT_EQ(quad.br.y, 20); } // width x textSize @@ -108,16 +92,16 @@ TEST(getIconQuads, style) { layout.get<TextSize>() = 12.0f; layout.get<IconTextFit>() = IconTextFitType::Width; SymbolQuad quad = - getIconQuad(anchor, *shapedIcon, line, layout, 12.0f, SymbolPlacementType::Point, shapedText); - - ASSERT_EQ(quad.tl.x, -30); - ASSERT_EQ(quad.tl.y, -5); - ASSERT_EQ(quad.tr.x, 10); - ASSERT_EQ(quad.tr.y, -5); - ASSERT_EQ(quad.bl.x, -30); - ASSERT_EQ(quad.bl.y, 15); - ASSERT_EQ(quad.br.x, 10); - ASSERT_EQ(quad.br.y, 15); + getIconQuad(shapedIcon, layout, 12.0f, shapedText); + + EXPECT_EQ(quad.tl.x, -30); + EXPECT_EQ(quad.tl.y, -5); + EXPECT_EQ(quad.tr.x, 10); + EXPECT_EQ(quad.tr.y, -5); + EXPECT_EQ(quad.bl.x, -30); + EXPECT_EQ(quad.bl.y, 15); + EXPECT_EQ(quad.br.x, 10); + EXPECT_EQ(quad.br.y, 15); } // width x textSize + padding @@ -130,16 +114,16 @@ TEST(getIconQuads, style) { layout.get<IconTextFitPadding>()[2] = 5.0f; layout.get<IconTextFitPadding>()[3] = 10.0f; SymbolQuad quad = - getIconQuad(anchor, *shapedIcon, line, layout, 12.0f, SymbolPlacementType::Point, shapedText); - - ASSERT_EQ(quad.tl.x, -40); - ASSERT_EQ(quad.tl.y, -10); - ASSERT_EQ(quad.tr.x, 20); - ASSERT_EQ(quad.tr.y, -10); - ASSERT_EQ(quad.bl.x, -40); - ASSERT_EQ(quad.bl.y, 20); - ASSERT_EQ(quad.br.x, 20); - ASSERT_EQ(quad.br.y, 20); + getIconQuad(shapedIcon, layout, 12.0f, shapedText); + + EXPECT_EQ(quad.tl.x, -40); + EXPECT_EQ(quad.tl.y, -10); + EXPECT_EQ(quad.tr.x, 20); + EXPECT_EQ(quad.tr.y, -10); + EXPECT_EQ(quad.bl.x, -40); + EXPECT_EQ(quad.bl.y, 20); + EXPECT_EQ(quad.br.x, 20); + EXPECT_EQ(quad.br.y, 20); } // height @@ -148,16 +132,16 @@ TEST(getIconQuads, style) { layout.get<TextSize>() = 24.0f; layout.get<IconTextFit>() = IconTextFitType::Height; SymbolQuad quad = - getIconQuad(anchor, *shapedIcon, line, layout, 24.0f, SymbolPlacementType::Point, shapedText); - - ASSERT_EQ(quad.tl.x, -30); - ASSERT_EQ(quad.tl.y, -10); - ASSERT_EQ(quad.tr.x, -10); - ASSERT_EQ(quad.tr.y, -10); - ASSERT_EQ(quad.bl.x, -30); - ASSERT_EQ(quad.bl.y, 30); - ASSERT_EQ(quad.br.x, -10); - ASSERT_EQ(quad.br.y, 30); + getIconQuad(shapedIcon, layout, 24.0f, shapedText); + + EXPECT_EQ(quad.tl.x, -30); + EXPECT_EQ(quad.tl.y, -10); + EXPECT_EQ(quad.tr.x, -10); + EXPECT_EQ(quad.tr.y, -10); + EXPECT_EQ(quad.bl.x, -30); + EXPECT_EQ(quad.bl.y, 30); + EXPECT_EQ(quad.br.x, -10); + EXPECT_EQ(quad.br.y, 30); } // height x textSize @@ -166,16 +150,16 @@ TEST(getIconQuads, style) { layout.get<TextSize>() = 12.0f; layout.get<IconTextFit>() = IconTextFitType::Height; SymbolQuad quad = - getIconQuad(anchor, *shapedIcon, line, layout, 12.0f, SymbolPlacementType::Point, shapedText); - - ASSERT_EQ(quad.tl.x, -20); - ASSERT_EQ(quad.tl.y, -5); - ASSERT_EQ(quad.tr.x, 0); - ASSERT_EQ(quad.tr.y, -5); - ASSERT_EQ(quad.bl.x, -20); - ASSERT_EQ(quad.bl.y, 15); - ASSERT_EQ(quad.br.x, 0); - ASSERT_EQ(quad.br.y, 15); + getIconQuad(shapedIcon, layout, 12.0f, shapedText); + + EXPECT_EQ(quad.tl.x, -20); + EXPECT_EQ(quad.tl.y, -5); + EXPECT_EQ(quad.tr.x, 0); + EXPECT_EQ(quad.tr.y, -5); + EXPECT_EQ(quad.bl.x, -20); + EXPECT_EQ(quad.bl.y, 15); + EXPECT_EQ(quad.br.x, 0); + EXPECT_EQ(quad.br.y, 15); } // height x textSize + padding @@ -188,16 +172,16 @@ TEST(getIconQuads, style) { layout.get<IconTextFitPadding>()[2] = 5.0f; layout.get<IconTextFitPadding>()[3] = 10.0f; SymbolQuad quad = - getIconQuad(anchor, *shapedIcon, line, layout, 12.0f, SymbolPlacementType::Point, shapedText); - - ASSERT_EQ(quad.tl.x, -30); - ASSERT_EQ(quad.tl.y, -10); - ASSERT_EQ(quad.tr.x, 10); - ASSERT_EQ(quad.tr.y, -10); - ASSERT_EQ(quad.bl.x, -30); - ASSERT_EQ(quad.bl.y, 20); - ASSERT_EQ(quad.br.x, 10); - ASSERT_EQ(quad.br.y, 20); + getIconQuad(shapedIcon, layout, 12.0f, shapedText); + + EXPECT_EQ(quad.tl.x, -30); + EXPECT_EQ(quad.tl.y, -10); + EXPECT_EQ(quad.tr.x, 10); + EXPECT_EQ(quad.tr.y, -10); + EXPECT_EQ(quad.bl.x, -30); + EXPECT_EQ(quad.bl.y, 20); + EXPECT_EQ(quad.br.x, 10); + EXPECT_EQ(quad.br.y, 20); } // both @@ -206,16 +190,16 @@ TEST(getIconQuads, style) { layout.get<TextSize>() = 24.0f; layout.get<IconTextFit>() = IconTextFitType::Both; SymbolQuad quad = - getIconQuad(anchor, *shapedIcon, line, layout, 24.0f, SymbolPlacementType::Point, shapedText); - - ASSERT_EQ(quad.tl.x, -60); - ASSERT_EQ(quad.tl.y, -10); - ASSERT_EQ(quad.tr.x, 20); - ASSERT_EQ(quad.tr.y, -10); - ASSERT_EQ(quad.bl.x, -60); - ASSERT_EQ(quad.bl.y, 30); - ASSERT_EQ(quad.br.x, 20); - ASSERT_EQ(quad.br.y, 30); + getIconQuad(shapedIcon, layout, 24.0f, shapedText); + + EXPECT_EQ(quad.tl.x, -60); + EXPECT_EQ(quad.tl.y, -10); + EXPECT_EQ(quad.tr.x, 20); + EXPECT_EQ(quad.tr.y, -10); + EXPECT_EQ(quad.bl.x, -60); + EXPECT_EQ(quad.bl.y, 30); + EXPECT_EQ(quad.br.x, 20); + EXPECT_EQ(quad.br.y, 30); } // both x textSize @@ -224,16 +208,16 @@ TEST(getIconQuads, style) { layout.get<TextSize>() = 12.0f; layout.get<IconTextFit>() = IconTextFitType::Both; SymbolQuad quad = - getIconQuad(anchor, *shapedIcon, line, layout, 12.0f, SymbolPlacementType::Point, shapedText); - - ASSERT_EQ(quad.tl.x, -30); - ASSERT_EQ(quad.tl.y, -5); - ASSERT_EQ(quad.tr.x, 10); - ASSERT_EQ(quad.tr.y, -5); - ASSERT_EQ(quad.bl.x, -30); - ASSERT_EQ(quad.bl.y, 15); - ASSERT_EQ(quad.br.x, 10); - ASSERT_EQ(quad.br.y, 15); + getIconQuad(shapedIcon, layout, 12.0f, shapedText); + + EXPECT_EQ(quad.tl.x, -30); + EXPECT_EQ(quad.tl.y, -5); + EXPECT_EQ(quad.tr.x, 10); + EXPECT_EQ(quad.tr.y, -5); + EXPECT_EQ(quad.bl.x, -30); + EXPECT_EQ(quad.bl.y, 15); + EXPECT_EQ(quad.br.x, 10); + EXPECT_EQ(quad.br.y, 15); } // both x textSize + padding @@ -246,16 +230,16 @@ TEST(getIconQuads, style) { layout.get<IconTextFitPadding>()[2] = 5.0f; layout.get<IconTextFitPadding>()[3] = 10.0f; SymbolQuad quad = - getIconQuad(anchor, *shapedIcon, line, layout, 12.0f, SymbolPlacementType::Point, shapedText); - - ASSERT_EQ(quad.tl.x, -40); - ASSERT_EQ(quad.tl.y, -10); - ASSERT_EQ(quad.tr.x, 20); - ASSERT_EQ(quad.tr.y, -10); - ASSERT_EQ(quad.bl.x, -40); - ASSERT_EQ(quad.bl.y, 20); - ASSERT_EQ(quad.br.x, 20); - ASSERT_EQ(quad.br.y, 20); + getIconQuad(shapedIcon, layout, 12.0f, shapedText); + + EXPECT_EQ(quad.tl.x, -40); + EXPECT_EQ(quad.tl.y, -10); + EXPECT_EQ(quad.tr.x, 20); + EXPECT_EQ(quad.tr.y, -10); + EXPECT_EQ(quad.bl.x, -40); + EXPECT_EQ(quad.bl.y, 20); + EXPECT_EQ(quad.br.x, 20); + EXPECT_EQ(quad.br.y, 20); } // both x textSize + padding t/r/b/l @@ -268,16 +252,16 @@ TEST(getIconQuads, style) { layout.get<IconTextFitPadding>()[2] = 10.0f; layout.get<IconTextFitPadding>()[3] = 15.0f; SymbolQuad quad = - getIconQuad(anchor, *shapedIcon, line, layout, 12.0f, SymbolPlacementType::Point, shapedText); - - ASSERT_EQ(quad.tl.x, -45); - ASSERT_EQ(quad.tl.y, -5); - ASSERT_EQ(quad.tr.x, 15); - ASSERT_EQ(quad.tr.y, -5); - ASSERT_EQ(quad.bl.x, -45); - ASSERT_EQ(quad.bl.y, 25); - ASSERT_EQ(quad.br.x, 15); - ASSERT_EQ(quad.br.y, 25); + getIconQuad(shapedIcon, layout, 12.0f, shapedText); + + EXPECT_EQ(quad.tl.x, -45); + EXPECT_EQ(quad.tl.y, -5); + EXPECT_EQ(quad.tr.x, 15); + EXPECT_EQ(quad.tr.y, -5); + EXPECT_EQ(quad.bl.x, -45); + EXPECT_EQ(quad.bl.y, 25); + EXPECT_EQ(quad.br.x, 15); + EXPECT_EQ(quad.br.y, 25); } } diff --git a/test/tile/annotation_tile.test.cpp b/test/tile/annotation_tile.test.cpp index ac3e064c50..8f3f903925 100644 --- a/test/tile/annotation_tile.test.cpp +++ b/test/tile/annotation_tile.test.cpp @@ -4,14 +4,17 @@ #include <mbgl/util/default_thread_pool.hpp> #include <mbgl/util/run_loop.hpp> #include <mbgl/map/transform.hpp> -#include <mbgl/map/query.hpp> -#include <mbgl/style/style.hpp> #include <mbgl/renderer/tile_parameters.hpp> -#include <mbgl/map/query.hpp> +#include <mbgl/renderer/query.hpp> #include <mbgl/text/collision_tile.hpp> #include <mbgl/geometry/feature_index.hpp> #include <mbgl/annotation/annotation_manager.hpp> #include <mbgl/annotation/annotation_tile.hpp> +#include <mbgl/renderer/image_manager.hpp> +#include <mbgl/text/glyph_manager.hpp> +#include <mbgl/renderer/backend_scope.hpp> +#include <mbgl/gl/headless_backend.hpp> +#include <mbgl/style/style.hpp> #include <memory> @@ -23,8 +26,12 @@ public: TransformState transformState; util::RunLoop loop; ThreadPool threadPool { 1 }; - AnnotationManager annotationManager { 1.0 }; - style::Style style { threadPool, fileSource, 1.0 }; + style::Style style { loop, fileSource, 1 }; + AnnotationManager annotationManager { style }; + HeadlessBackend backend; + BackendScope scope { backend }; + ImageManager imageManager; + GlyphManager glyphManager { fileSource }; TileParameters tileParameters { 1.0, @@ -34,7 +41,9 @@ public: fileSource, MapMode::Continuous, annotationManager, - style + imageManager, + glyphManager, + 0 }; }; @@ -44,14 +53,13 @@ TEST(AnnotationTile, Issue8289) { AnnotationTile tile(OverscaledTileID(0, 0, 0), test.tileParameters); auto data = std::make_unique<AnnotationTileData>(); - data->layers.emplace("test", AnnotationTileLayer("test")); - data->layers.at("test").features.push_back(AnnotationTileFeature(0, FeatureType::Point, GeometryCollection())); + data->addLayer("test")->addFeature(0, FeatureType::Point, GeometryCollection()); // Simulate layout and placement of a symbol layer. tile.onLayout(GeometryTile::LayoutResult { - {}, - std::make_unique<FeatureIndex>(), - std::move(data), + std::unordered_map<std::string, std::shared_ptr<Bucket>>(), + std::make_unique<FeatureIndex>(), + std::move(data), }, 0); auto collisionTile = std::make_unique<CollisionTile>(PlacementConfig()); @@ -62,15 +70,17 @@ TEST(AnnotationTile, Issue8289) { collisionTile->placeFeature(feature, false, false); tile.onPlacement(GeometryTile::PlacementResult { + std::unordered_map<std::string, std::shared_ptr<Bucket>>(), + std::move(collisionTile), + {}, {}, - std::move(collisionTile), }, 0); // Simulate a second layout with empty data. tile.onLayout(GeometryTile::LayoutResult { - {}, - std::make_unique<FeatureIndex>(), - std::make_unique<AnnotationTileData>(), + std::unordered_map<std::string, std::shared_ptr<Bucket>>(), + std::make_unique<FeatureIndex>(), + std::make_unique<AnnotationTileData>(), }, 0); std::unordered_map<std::string, std::vector<Feature>> result; @@ -78,7 +88,7 @@ TEST(AnnotationTile, Issue8289) { TransformState transformState; RenderedQueryOptions options; - tile.queryRenderedFeatures(result, queryGeometry, transformState, options); + tile.queryRenderedFeatures(result, queryGeometry, transformState, {}, options); EXPECT_TRUE(result.empty()); } diff --git a/test/tile/geojson_tile.test.cpp b/test/tile/geojson_tile.test.cpp index 18d11c2c14..953c3b8a5f 100644 --- a/test/tile/geojson_tile.test.cpp +++ b/test/tile/geojson_tile.test.cpp @@ -7,10 +7,12 @@ #include <mbgl/util/default_thread_pool.hpp> #include <mbgl/util/run_loop.hpp> #include <mbgl/map/transform.hpp> -#include <mbgl/style/style.hpp> #include <mbgl/renderer/tile_parameters.hpp> +#include <mbgl/style/style.hpp> #include <mbgl/style/layers/circle_layer.hpp> #include <mbgl/annotation/annotation_manager.hpp> +#include <mbgl/renderer/image_manager.hpp> +#include <mbgl/text/glyph_manager.hpp> #include <memory> @@ -23,8 +25,10 @@ public: TransformState transformState; util::RunLoop loop; ThreadPool threadPool { 1 }; - AnnotationManager annotationManager { 1.0 }; - style::Style style { threadPool, fileSource, 1.0 }; + style::Style style { loop, fileSource, 1 }; + AnnotationManager annotationManager { style }; + ImageManager imageManager; + GlyphManager glyphManager { fileSource }; Tileset tileset { { "https://example.com" }, { 0, 22 }, "none" }; TileParameters tileParameters { @@ -35,14 +39,16 @@ public: fileSource, MapMode::Continuous, annotationManager, - style + imageManager, + glyphManager, + 0 }; }; TEST(GeoJSONTile, Issue7648) { GeoJSONTileTest test; - test.style.addLayer(std::make_unique<CircleLayer>("circle", "source")); + CircleLayer layer("circle", "source"); mapbox::geometry::feature_collection<int16_t> features; features.push_back(mapbox::geometry::feature<int16_t> { @@ -55,9 +61,10 @@ TEST(GeoJSONTile, Issue7648) { observer.tileChanged = [&] (const Tile&) { // Once present, the bucket should never "disappear", which would cause // flickering. - ASSERT_NE(nullptr, tile.getBucket(*test.style.getRenderLayer("circle"))); + ASSERT_NE(nullptr, tile.getBucket(*layer.baseImpl)); }; + tile.setLayers({{ layer.baseImpl }}); tile.setObserver(&observer); tile.setPlacementConfig({}); @@ -76,7 +83,7 @@ TEST(GeoJSONTile, Issue7648) { TEST(GeoJSONTile, Issue9927) { GeoJSONTileTest test; - test.style.addLayer(std::make_unique<CircleLayer>("circle", "source")); + CircleLayer layer("circle", "source"); mapbox::geometry::feature_collection<int16_t> features; features.push_back(mapbox::geometry::feature<int16_t> { @@ -85,6 +92,7 @@ TEST(GeoJSONTile, Issue9927) { GeoJSONTile tile(OverscaledTileID(0, 0, 0), "source", test.tileParameters, features); + tile.setLayers({{ layer.baseImpl }}); tile.setPlacementConfig({}); while (!tile.isComplete()) { @@ -92,18 +100,18 @@ TEST(GeoJSONTile, Issue9927) { } ASSERT_TRUE(tile.isRenderable()); - ASSERT_NE(nullptr, tile.getBucket(*test.style.getRenderLayer("circle"))); + ASSERT_NE(nullptr, tile.getBucket(*layer.baseImpl)); // Make sure that once we've had a renderable tile and then receive erroneous data, we retain // the previously rendered data and keep the tile renderable. tile.setError(std::make_exception_ptr(std::runtime_error("Connection offline"))); ASSERT_TRUE(tile.isRenderable()); - ASSERT_NE(nullptr, tile.getBucket(*test.style.getRenderLayer("circle"))); + ASSERT_NE(nullptr, tile.getBucket(*layer.baseImpl)); // Then simulate a parsing failure and make sure that we keep it renderable in this situation // as well. We're using 3 as a correlationID since we've done two three calls that increment // this counter (as part of the GeoJSONTile constructor, setLayers, and setPlacementConfig). tile.onError(std::make_exception_ptr(std::runtime_error("Parse error")), 3); ASSERT_TRUE(tile.isRenderable()); - ASSERT_NE(nullptr, tile.getBucket(*test.style.getRenderLayer("circle"))); + ASSERT_NE(nullptr, tile.getBucket(*layer.baseImpl)); } diff --git a/test/tile/raster_tile.test.cpp b/test/tile/raster_tile.test.cpp index a606066e9b..8b2b3dee61 100644 --- a/test/tile/raster_tile.test.cpp +++ b/test/tile/raster_tile.test.cpp @@ -3,13 +3,15 @@ #include <mbgl/tile/raster_tile.hpp> #include <mbgl/tile/tile_loader_impl.hpp> +#include <mbgl/style/style.hpp> #include <mbgl/util/default_thread_pool.hpp> #include <mbgl/util/run_loop.hpp> #include <mbgl/map/transform.hpp> -#include <mbgl/style/style.hpp> #include <mbgl/annotation/annotation_manager.hpp> #include <mbgl/renderer/tile_parameters.hpp> -#include <mbgl/renderer/raster_bucket.hpp> +#include <mbgl/renderer/buckets/raster_bucket.hpp> +#include <mbgl/renderer/image_manager.hpp> +#include <mbgl/text/glyph_manager.hpp> using namespace mbgl; @@ -19,8 +21,10 @@ public: TransformState transformState; util::RunLoop loop; ThreadPool threadPool { 1 }; - AnnotationManager annotationManager { 1.0 }; - style::Style style { threadPool, fileSource, 1.0 }; + style::Style style { loop, fileSource, 1 }; + AnnotationManager annotationManager { style }; + ImageManager imageManager; + GlyphManager glyphManager { fileSource }; Tileset tileset { { "https://example.com" }, { 0, 22 }, "none" }; TileParameters tileParameters { @@ -31,7 +35,9 @@ public: fileSource, MapMode::Continuous, annotationManager, - style + imageManager, + glyphManager, + 0 }; }; @@ -56,7 +62,7 @@ TEST(RasterTile, onError) { TEST(RasterTile, onParsed) { RasterTileTest test; RasterTile tile(OverscaledTileID(0, 0, 0), test.tileParameters, test.tileset); - tile.onParsed(std::make_unique<RasterBucket>(UnassociatedImage{}), 0); + tile.onParsed(std::make_unique<RasterBucket>(PremultipliedImage{}), 0); EXPECT_TRUE(tile.isRenderable()); EXPECT_TRUE(tile.isLoaded()); EXPECT_TRUE(tile.isComplete()); diff --git a/test/tile/tile_id.test.cpp b/test/tile/tile_id.test.cpp index 1ef19fea0e..2f328b78d7 100644 --- a/test/tile/tile_id.test.cpp +++ b/test/tile/tile_id.test.cpp @@ -127,17 +127,17 @@ TEST(TileID, Canonical) { TEST(TileID, Overscaled) { EXPECT_TRUE(OverscaledTileID(4, 2, 3) == OverscaledTileID(4, 2, 3)); EXPECT_FALSE(OverscaledTileID(4, 2, 3) != OverscaledTileID(4, 2, 3)); - EXPECT_TRUE(OverscaledTileID(4, { 4, 2, 3 }) == OverscaledTileID(4, 2, 3)); - EXPECT_FALSE(OverscaledTileID(4, { 4, 2, 3 }) != OverscaledTileID(4, 2, 3)); - EXPECT_TRUE(OverscaledTileID(4, 2, 3) == OverscaledTileID(4, { 4, 2, 3 })); - EXPECT_FALSE(OverscaledTileID(4, 2, 3) != OverscaledTileID(4, { 4, 2, 3 })); - - EXPECT_TRUE(OverscaledTileID(4, 2, 3) != OverscaledTileID(5, { 4, 2, 3 })); - EXPECT_FALSE(OverscaledTileID(4, 2, 3) == OverscaledTileID(5, { 4, 2, 3 })); - EXPECT_TRUE(OverscaledTileID(4, 2, 3) != OverscaledTileID(6, { 4, 2, 3 })); - EXPECT_FALSE(OverscaledTileID(4, 2, 3) == OverscaledTileID(6, { 4, 2, 3 })); - EXPECT_TRUE(OverscaledTileID(4, 2, 3) != OverscaledTileID(7, { 4, 2, 3 })); - EXPECT_FALSE(OverscaledTileID(4, 2, 3) == OverscaledTileID(7, { 4, 2, 3 })); + EXPECT_TRUE(OverscaledTileID(4, 0, { 4, 2, 3 }) == OverscaledTileID(4, 2, 3)); + EXPECT_FALSE(OverscaledTileID(4, 0, { 4, 2, 3 }) != OverscaledTileID(4, 2, 3)); + EXPECT_TRUE(OverscaledTileID(4, 2, 3) == OverscaledTileID(4, 0, { 4, 2, 3 })); + EXPECT_FALSE(OverscaledTileID(4, 2, 3) != OverscaledTileID(4, 0, { 4, 2, 3 })); + + EXPECT_TRUE(OverscaledTileID(4, 2, 3) != OverscaledTileID(5, 0, { 4, 2, 3 })); + EXPECT_FALSE(OverscaledTileID(4, 2, 3) == OverscaledTileID(5, 0, { 4, 2, 3 })); + EXPECT_TRUE(OverscaledTileID(4, 2, 3) != OverscaledTileID(6, 0, { 4, 2, 3 })); + EXPECT_FALSE(OverscaledTileID(4, 2, 3) == OverscaledTileID(6, 0, { 4, 2, 3 })); + EXPECT_TRUE(OverscaledTileID(4, 2, 3) != OverscaledTileID(7, 0, { 4, 2, 3 })); + EXPECT_FALSE(OverscaledTileID(4, 2, 3) == OverscaledTileID(7, 0, { 4, 2, 3 })); EXPECT_TRUE(OverscaledTileID(4, 2, 3) != OverscaledTileID(4, 2, 4)); EXPECT_FALSE(OverscaledTileID(4, 2, 3) == OverscaledTileID(4, 2, 4)); @@ -146,70 +146,70 @@ TEST(TileID, Overscaled) { EXPECT_TRUE(OverscaledTileID(4, 2, 3) != OverscaledTileID(5, 2, 3)); EXPECT_FALSE(OverscaledTileID(4, 2, 3) == OverscaledTileID(5, 2, 3)); - EXPECT_TRUE(OverscaledTileID(7, { 4, 2, 3 }) == OverscaledTileID(7, { 4, 2, 3 })); - EXPECT_FALSE(OverscaledTileID(7, { 4, 2, 3 }) != OverscaledTileID(7, { 4, 2, 3 })); + EXPECT_TRUE(OverscaledTileID(7, 0, { 4, 2, 3 }) == OverscaledTileID(7, 0, { 4, 2, 3 })); + EXPECT_FALSE(OverscaledTileID(7, 0, { 4, 2, 3 }) != OverscaledTileID(7, 0, { 4, 2, 3 })); EXPECT_FALSE(OverscaledTileID(4, 2, 3) < OverscaledTileID(4, 2, 3)); - EXPECT_TRUE(OverscaledTileID(4, 2, 3) < OverscaledTileID(5, { 4, 2, 3 })); - EXPECT_FALSE(OverscaledTileID(5, { 4, 2, 3 }) < OverscaledTileID(4, 2, 3)); - EXPECT_TRUE(OverscaledTileID(4, 2, 3) < OverscaledTileID(6, { 4, 2, 3 })); - EXPECT_FALSE(OverscaledTileID(6, { 4, 2, 3 }) < OverscaledTileID(4, 2, 3)); - EXPECT_TRUE(OverscaledTileID(4, 2, 3) < OverscaledTileID(7, { 4, 2, 3 })); - EXPECT_FALSE(OverscaledTileID(7, { 4, 2, 3 }) < OverscaledTileID(4, 2, 3)); - - EXPECT_EQ(8u, OverscaledTileID(7, { 4, 2, 3 }).overscaleFactor()); - EXPECT_EQ(4u, OverscaledTileID(6, { 4, 2, 3 }).overscaleFactor()); - EXPECT_EQ(2u, OverscaledTileID(5, { 4, 2, 3 }).overscaleFactor()); - EXPECT_EQ(1u, OverscaledTileID(4, { 4, 2, 3 }).overscaleFactor()); - EXPECT_EQ(2147483648u, OverscaledTileID(31, { 0, 0, 0 }).overscaleFactor()); - - EXPECT_EQ(OverscaledTileID(0, { 0, 0, 0 }), OverscaledTileID(4, 2, 3).scaledTo(0)); - EXPECT_EQ(OverscaledTileID(1, { 1, 0, 0 }), OverscaledTileID(4, 2, 3).scaledTo(1)); - EXPECT_EQ(OverscaledTileID(2, { 2, 0, 0 }), OverscaledTileID(4, 2, 3).scaledTo(2)); - EXPECT_EQ(OverscaledTileID(3, { 3, 1, 1 }), OverscaledTileID(4, 2, 3).scaledTo(3)); - EXPECT_EQ(OverscaledTileID(4, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).scaledTo(4)); - EXPECT_EQ(OverscaledTileID(5, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).scaledTo(5)); - EXPECT_EQ(OverscaledTileID(6, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).scaledTo(6)); - EXPECT_EQ(OverscaledTileID(7, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).scaledTo(7)); - EXPECT_EQ(OverscaledTileID(8, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).scaledTo(8)); - EXPECT_EQ(OverscaledTileID(32, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).scaledTo(32)); - - EXPECT_EQ(UnwrappedTileID(0, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).unwrapTo(0)); - EXPECT_EQ(UnwrappedTileID(-1, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).unwrapTo(-1)); - EXPECT_EQ(UnwrappedTileID(1, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).unwrapTo(1)); - EXPECT_EQ(UnwrappedTileID(0, { 4, 2, 3 }), OverscaledTileID(5, { 4, 2, 3 }).unwrapTo(0)); - EXPECT_EQ(UnwrappedTileID(-1, { 4, 2, 3 }), OverscaledTileID(5, { 4, 2, 3 }).unwrapTo(-1)); - EXPECT_EQ(UnwrappedTileID(1, { 4, 2, 3 }), OverscaledTileID(5, { 4, 2, 3 }).unwrapTo(1)); + EXPECT_TRUE(OverscaledTileID(4, 2, 3) < OverscaledTileID(5, 0, { 4, 2, 3 })); + EXPECT_FALSE(OverscaledTileID(5, 0, { 4, 2, 3 }) < OverscaledTileID(4, 2, 3)); + EXPECT_TRUE(OverscaledTileID(4, 2, 3) < OverscaledTileID(6, 0, { 4, 2, 3 })); + EXPECT_FALSE(OverscaledTileID(6, 0, { 4, 2, 3 }) < OverscaledTileID(4, 2, 3)); + EXPECT_TRUE(OverscaledTileID(4, 2, 3) < OverscaledTileID(7,0, { 4, 2, 3 })); + EXPECT_FALSE(OverscaledTileID(7, 0, { 4, 2, 3 }) < OverscaledTileID(4, 2, 3)); + + EXPECT_EQ(8u, OverscaledTileID(7, 0, { 4, 2, 3 }).overscaleFactor()); + EXPECT_EQ(4u, OverscaledTileID(6, 0, { 4, 2, 3 }).overscaleFactor()); + EXPECT_EQ(2u, OverscaledTileID(5, 0, { 4, 2, 3 }).overscaleFactor()); + EXPECT_EQ(1u, OverscaledTileID(4, 0, { 4, 2, 3 }).overscaleFactor()); + EXPECT_EQ(2147483648u, OverscaledTileID(31, 0, { 0, 0, 0 }).overscaleFactor()); + + EXPECT_EQ(OverscaledTileID(0, 0, { 0, 0, 0 }), OverscaledTileID(4, 2, 3).scaledTo(0)); + EXPECT_EQ(OverscaledTileID(1, 0, { 1, 0, 0 }), OverscaledTileID(4, 2, 3).scaledTo(1)); + EXPECT_EQ(OverscaledTileID(2, 0, { 2, 0, 0 }), OverscaledTileID(4, 2, 3).scaledTo(2)); + EXPECT_EQ(OverscaledTileID(3, 0, { 3, 1, 1 }), OverscaledTileID(4, 2, 3).scaledTo(3)); + EXPECT_EQ(OverscaledTileID(4, 0, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).scaledTo(4)); + EXPECT_EQ(OverscaledTileID(5, 0, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).scaledTo(5)); + EXPECT_EQ(OverscaledTileID(6, 0, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).scaledTo(6)); + EXPECT_EQ(OverscaledTileID(7, 0, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).scaledTo(7)); + EXPECT_EQ(OverscaledTileID(8, 0, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).scaledTo(8)); + EXPECT_EQ(OverscaledTileID(32, 0, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).scaledTo(32)); + + EXPECT_EQ(UnwrappedTileID(0, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).toUnwrapped()); + EXPECT_EQ(UnwrappedTileID(-1, { 4, 2, 3 }), OverscaledTileID(4, -1, { 4, 2, 3 }).toUnwrapped()); + EXPECT_EQ(UnwrappedTileID(1, { 4, 2, 3 }), OverscaledTileID(4, 1, { 4, 2, 3 }).toUnwrapped()); + EXPECT_EQ(UnwrappedTileID(0, { 4, 2, 3 }), OverscaledTileID(5, 0, { 4, 2, 3 }).toUnwrapped()); + EXPECT_EQ(UnwrappedTileID(-1, { 4, 2, 3 }), OverscaledTileID(5, -1, { 4, 2, 3 }).toUnwrapped()); + EXPECT_EQ(UnwrappedTileID(1, { 4, 2, 3 }), OverscaledTileID(5, 1, { 4, 2, 3 }).toUnwrapped()); EXPECT_FALSE(OverscaledTileID(2, 0, 0).isChildOf(OverscaledTileID(3, 1, 1))); EXPECT_FALSE(OverscaledTileID(3, 1, 1).isChildOf(OverscaledTileID(3, 1, 1))); EXPECT_TRUE(OverscaledTileID(4, 2, 3).isChildOf(OverscaledTileID(3, 1, 1))); - EXPECT_TRUE(OverscaledTileID(5, { 4, 2, 3 }).isChildOf(OverscaledTileID(3, 1, 1))); - EXPECT_TRUE(OverscaledTileID(6, { 4, 2, 3 }).isChildOf(OverscaledTileID(3, 1, 1))); - EXPECT_TRUE(OverscaledTileID(7, { 4, 2, 3 }).isChildOf(OverscaledTileID(3, 1, 1))); - - EXPECT_FALSE(OverscaledTileID(2, 0, 0).isChildOf(OverscaledTileID(5, { 4, 2, 3 }))); - EXPECT_FALSE(OverscaledTileID(3, 1, 1).isChildOf(OverscaledTileID(5, { 4, 2, 3 }))); - EXPECT_FALSE(OverscaledTileID(4, 2, 3).isChildOf(OverscaledTileID(5, { 4, 2, 3 }))); - EXPECT_FALSE(OverscaledTileID(5, { 4, 2, 3 }).isChildOf(OverscaledTileID(5, { 4, 2, 3 }))); - EXPECT_TRUE(OverscaledTileID(6, { 4, 2, 3 }).isChildOf(OverscaledTileID(5, { 4, 2, 3 }))); - EXPECT_TRUE(OverscaledTileID(7, { 4, 2, 3 }).isChildOf(OverscaledTileID(5, { 4, 2, 3 }))); - - EXPECT_FALSE(OverscaledTileID(2, 0, 0).isChildOf(OverscaledTileID(6, { 4, 2, 3 }))); - EXPECT_FALSE(OverscaledTileID(3, 1, 1).isChildOf(OverscaledTileID(6, { 4, 2, 3 }))); - EXPECT_FALSE(OverscaledTileID(4, 2, 3).isChildOf(OverscaledTileID(6, { 4, 2, 3 }))); - EXPECT_FALSE(OverscaledTileID(5, { 4, 2, 3 }).isChildOf(OverscaledTileID(6, { 4, 2, 3 }))); - EXPECT_FALSE(OverscaledTileID(6, { 4, 2, 3 }).isChildOf(OverscaledTileID(6, { 4, 2, 3 }))); - EXPECT_TRUE(OverscaledTileID(7, { 4, 2, 3 }).isChildOf(OverscaledTileID(6, { 4, 2, 3 }))); - - EXPECT_FALSE(OverscaledTileID(2, 0, 0).isChildOf(OverscaledTileID(5, { 4, 0, 0 }))); - EXPECT_FALSE(OverscaledTileID(3, 1, 1).isChildOf(OverscaledTileID(5, { 4, 0, 0 }))); - EXPECT_FALSE(OverscaledTileID(4, 2, 3).isChildOf(OverscaledTileID(5, { 4, 0, 0 }))); - EXPECT_FALSE(OverscaledTileID(5, { 4, 2, 3 }).isChildOf(OverscaledTileID(5, { 4, 0, 0 }))); - EXPECT_FALSE(OverscaledTileID(6, { 4, 2, 3 }).isChildOf(OverscaledTileID(5, { 4, 0, 0 }))); - EXPECT_FALSE(OverscaledTileID(7, { 4, 2, 3 }).isChildOf(OverscaledTileID(5, { 4, 0, 0 }))); - - EXPECT_FALSE(OverscaledTileID(4, { 4, 2, 3 }).isChildOf(OverscaledTileID(5, { 3, 1, 1 }))); + EXPECT_TRUE(OverscaledTileID(5, 0, { 4, 2, 3 }).isChildOf(OverscaledTileID(3, 1, 1))); + EXPECT_TRUE(OverscaledTileID(6, 0, { 4, 2, 3 }).isChildOf(OverscaledTileID(3, 1, 1))); + EXPECT_TRUE(OverscaledTileID(7, 0, { 4, 2, 3 }).isChildOf(OverscaledTileID(3, 1, 1))); + + EXPECT_FALSE(OverscaledTileID(2, 0, 0).isChildOf(OverscaledTileID(5, 0, { 4, 2, 3 }))); + EXPECT_FALSE(OverscaledTileID(3, 1, 1).isChildOf(OverscaledTileID(5, 0, { 4, 2, 3 }))); + EXPECT_FALSE(OverscaledTileID(4, 2, 3).isChildOf(OverscaledTileID(5, 0, { 4, 2, 3 }))); + EXPECT_FALSE(OverscaledTileID(5, 0, { 4, 2, 3 }).isChildOf(OverscaledTileID(5, 0, { 4, 2, 3 }))); + EXPECT_TRUE(OverscaledTileID(6, 0, { 4, 2, 3 }).isChildOf(OverscaledTileID(5, 0, { 4, 2, 3 }))); + EXPECT_TRUE(OverscaledTileID(7, 0, { 4, 2, 3 }).isChildOf(OverscaledTileID(5, 0, { 4, 2, 3 }))); + + EXPECT_FALSE(OverscaledTileID(2, 0, 0).isChildOf(OverscaledTileID(6, 0, { 4, 2, 3 }))); + EXPECT_FALSE(OverscaledTileID(3, 1, 1).isChildOf(OverscaledTileID(6, 0, { 4, 2, 3 }))); + EXPECT_FALSE(OverscaledTileID(4, 2, 3).isChildOf(OverscaledTileID(6, 0, { 4, 2, 3 }))); + EXPECT_FALSE(OverscaledTileID(5, 0, { 4, 2, 3 }).isChildOf(OverscaledTileID(6, 0, { 4, 2, 3 }))); + EXPECT_FALSE(OverscaledTileID(6, 0, { 4, 2, 3 }).isChildOf(OverscaledTileID(6, 0, { 4, 2, 3 }))); + EXPECT_TRUE(OverscaledTileID(7, 0, { 4, 2, 3 }).isChildOf(OverscaledTileID(6, 0, { 4, 2, 3 }))); + + EXPECT_FALSE(OverscaledTileID(2, 0, 0).isChildOf(OverscaledTileID(5, 0, { 4, 0, 0 }))); + EXPECT_FALSE(OverscaledTileID(3, 1, 1).isChildOf(OverscaledTileID(5, 0, { 4, 0, 0 }))); + EXPECT_FALSE(OverscaledTileID(4, 2, 3).isChildOf(OverscaledTileID(5, 0, { 4, 0, 0 }))); + EXPECT_FALSE(OverscaledTileID(5, 0, { 4, 2, 3 }).isChildOf(OverscaledTileID(5, 0, { 4, 0, 0 }))); + EXPECT_FALSE(OverscaledTileID(6, 0, { 4, 2, 3 }).isChildOf(OverscaledTileID(5, 0, { 4, 0, 0 }))); + EXPECT_FALSE(OverscaledTileID(7, 0, { 4, 2, 3 }).isChildOf(OverscaledTileID(5, 0, { 4, 0, 0 }))); + + EXPECT_FALSE(OverscaledTileID(4, 0, { 4, 2, 3 }).isChildOf(OverscaledTileID(5, 0, { 3, 1, 1 }))); } TEST(TileID, Unwrapped) { @@ -273,10 +273,10 @@ TEST(TileID, Unwrapped) { EXPECT_FALSE(UnwrappedTileID(0, 1, 0) < UnwrappedTileID(1, 0, 0)); EXPECT_FALSE(UnwrappedTileID(5, 3, 6) < UnwrappedTileID(5, 3, 6)); - EXPECT_EQ(OverscaledTileID(4, { 4, 2, 3 }), UnwrappedTileID(4, 2, 3).overscaleTo(4)); - EXPECT_EQ(OverscaledTileID(5, { 4, 2, 3 }), UnwrappedTileID(4, 2, 3).overscaleTo(5)); - EXPECT_EQ(OverscaledTileID(6, { 4, 2, 3 }), UnwrappedTileID(4, 2, 3).overscaleTo(6)); - EXPECT_EQ(OverscaledTileID(32, { 4, 2, 3 }), UnwrappedTileID(4, 2, 3).overscaleTo(32)); + EXPECT_EQ(OverscaledTileID(4, 0, { 4, 2, 3 }), UnwrappedTileID(4, 2, 3).overscaleTo(4)); + EXPECT_EQ(OverscaledTileID(5, 0, { 4, 2, 3 }), UnwrappedTileID(4, 2, 3).overscaleTo(5)); + EXPECT_EQ(OverscaledTileID(6, 0, { 4, 2, 3 }), UnwrappedTileID(4, 2, 3).overscaleTo(6)); + EXPECT_EQ(OverscaledTileID(32, 0, { 4, 2, 3 }), UnwrappedTileID(4, 2, 3).overscaleTo(32)); EXPECT_EQ(UnwrappedTileID(-1, { 1, 0, 0 }), UnwrappedTileID(1, -2, 0)); EXPECT_EQ(UnwrappedTileID(-1, { 1, 0, 1 }), UnwrappedTileID(1, -2, 1)); diff --git a/test/tile/vector_tile.test.cpp b/test/tile/vector_tile.test.cpp index 336076ecc2..7e8b659b7a 100644 --- a/test/tile/vector_tile.test.cpp +++ b/test/tile/vector_tile.test.cpp @@ -6,14 +6,16 @@ #include <mbgl/util/default_thread_pool.hpp> #include <mbgl/util/run_loop.hpp> #include <mbgl/map/transform.hpp> -#include <mbgl/map/query.hpp> #include <mbgl/style/style.hpp> #include <mbgl/style/layers/symbol_layer.hpp> #include <mbgl/renderer/tile_parameters.hpp> -#include <mbgl/renderer/symbol_bucket.hpp> +#include <mbgl/renderer/buckets/symbol_bucket.hpp> +#include <mbgl/renderer/query.hpp> #include <mbgl/text/collision_tile.hpp> #include <mbgl/geometry/feature_index.hpp> #include <mbgl/annotation/annotation_manager.hpp> +#include <mbgl/renderer/image_manager.hpp> +#include <mbgl/text/glyph_manager.hpp> #include <memory> @@ -25,8 +27,10 @@ public: TransformState transformState; util::RunLoop loop; ThreadPool threadPool { 1 }; - AnnotationManager annotationManager { 1.0 }; - style::Style style { threadPool, fileSource, 1.0 }; + style::Style style { loop, fileSource, 1 }; + AnnotationManager annotationManager { style }; + ImageManager imageManager; + GlyphManager glyphManager { fileSource }; Tileset tileset { { "https://example.com" }, { 0, 22 }, "none" }; TileParameters tileParameters { @@ -37,7 +41,9 @@ public: fileSource, MapMode::Continuous, annotationManager, - style + imageManager, + glyphManager, + 0 }; }; @@ -69,7 +75,7 @@ TEST(VectorTile, Issue7615) { style::SymbolLayoutProperties::PossiblyEvaluated(), std::map< std::string, - std::pair<style::IconPaintProperties::Evaluated, style::TextPaintProperties::Evaluated>>(), + std::pair<style::IconPaintProperties::PossiblyEvaluated, style::TextPaintProperties::PossiblyEvaluated>>(), 16.0f, 1.0f, 0.0f, false, false); // Simulate placement of a symbol layer. @@ -79,16 +85,18 @@ TEST(VectorTile, Issue7615) { symbolBucket }}, nullptr, + {}, + {}, }, 0); // Subsequent onLayout should not cause the existing symbol bucket to be discarded. tile.onLayout(GeometryTile::LayoutResult { - {}, + std::unordered_map<std::string, std::shared_ptr<Bucket>>(), nullptr, nullptr, }, 0); - EXPECT_EQ(symbolBucket.get(), tile.getBucket(*symbolLayer.baseImpl->createRenderLayer())); + EXPECT_EQ(symbolBucket.get(), tile.getBucket(*symbolLayer.baseImpl)); } TEST(VectorTile, Issue8542) { diff --git a/test/util/async_task.test.cpp b/test/util/async_task.test.cpp index 78dc79dd19..f3025e8952 100644 --- a/test/util/async_task.test.cpp +++ b/test/util/async_task.test.cpp @@ -1,9 +1,12 @@ #include <mbgl/util/async_task.hpp> #include <mbgl/util/run_loop.hpp> -#include <mbgl/util/thread.hpp> +#include <mbgl/util/default_thread_pool.hpp> +#include <mbgl/actor/actor_ref.hpp> #include <mbgl/test/util.hpp> +#include <atomic> +#include <future> #include <vector> using namespace mbgl::util; @@ -29,6 +32,10 @@ public: cb(); } + void sync(std::promise<void> barrier) { + barrier.set_value(); + } + private: AsyncTask *async; }; @@ -94,23 +101,24 @@ TEST(AsyncTask, DestroyAfterSignaling) { TEST(AsyncTask, RequestCoalescingMultithreaded) { RunLoop loop; - unsigned count = 0; + unsigned count = 0, numThreads = 25; AsyncTask async([&count] { ++count; }); - std::vector<std::unique_ptr<Thread<TestWorker>>> threads; - ThreadContext context = {"Test"}; + mbgl::ThreadPool threads(numThreads); + auto mailbox = std::make_shared<mbgl::Mailbox>(threads); - unsigned numThreads = 25; - for (unsigned i = 0; i < numThreads; ++i) { - std::unique_ptr<Thread<TestWorker>> thread = - std::make_unique<Thread<TestWorker>>(context, &async); + TestWorker worker(&async); + mbgl::ActorRef<TestWorker> workerRef(worker, mailbox); - thread->invoke(&TestWorker::run); - threads.push_back(std::move(thread)); + for (unsigned i = 0; i < numThreads; ++i) { + workerRef.invoke(&TestWorker::run); } - // Join all the threads - threads.clear(); + std::promise<void> barrier; + std::future<void> barrierFuture = barrier.get_future(); + + workerRef.invoke(&TestWorker::sync, std::move(barrier)); + barrierFuture.wait(); loop.runOnce(); @@ -120,29 +128,20 @@ TEST(AsyncTask, RequestCoalescingMultithreaded) { TEST(AsyncTask, ThreadSafety) { RunLoop loop; - unsigned count = 0; - AsyncTask async([&count] { ++count; }); + unsigned count = 0, numThreads = 25; + std::atomic_uint completed(numThreads); - unsigned numThreads = 25; + AsyncTask async([&count] { ++count; }); - auto callback = [&] { - if (!--numThreads) { - loop.stop(); - } - }; + mbgl::ThreadPool threads(numThreads); + auto mailbox = std::make_shared<mbgl::Mailbox>(threads); - std::vector<std::unique_ptr<Thread<TestWorker>>> threads; - std::vector<std::unique_ptr<mbgl::AsyncRequest>> requests; - ThreadContext context = {"Test"}; + TestWorker worker(&async); + mbgl::ActorRef<TestWorker> workerRef(worker, mailbox); for (unsigned i = 0; i < numThreads; ++i) { - std::unique_ptr<Thread<TestWorker>> thread = - std::make_unique<Thread<TestWorker>>(context, &async); - - requests.push_back( - thread->invokeWithCallback(&TestWorker::runWithCallback, callback)); - - threads.push_back(std::move(thread)); + // The callback runs on the worker, thus the atomic type. + workerRef.invoke(&TestWorker::runWithCallback, [&] { if (!--completed) loop.stop(); }); } loop.run(); diff --git a/test/util/dtoa.test.cpp b/test/util/dtoa.test.cpp new file mode 100644 index 0000000000..8d2fba1877 --- /dev/null +++ b/test/util/dtoa.test.cpp @@ -0,0 +1,24 @@ +#include <mbgl/test/util.hpp> + +#include <mbgl/util/dtoa.hpp> + +#include <cfloat> +#include <cmath> + +using namespace mbgl; + +TEST(Dtoa, Precision) { + EXPECT_EQ(M_E, std::stod(util::dtoa(M_E))); + EXPECT_EQ(M_LOG2E, std::stod(util::dtoa(M_LOG2E))); + EXPECT_EQ(M_LOG10E, std::stod(util::dtoa(M_LOG10E))); + EXPECT_EQ(M_LN2, std::stod(util::dtoa(M_LN2))); + EXPECT_EQ(M_LN10, std::stod(util::dtoa(M_LN10))); + EXPECT_EQ(M_PI, std::stod(util::dtoa(M_PI))); + EXPECT_EQ(M_PI_2, std::stod(util::dtoa(M_PI_2))); + EXPECT_EQ(M_PI_4, std::stod(util::dtoa(M_PI_4))); + EXPECT_EQ(M_1_PI, std::stod(util::dtoa(M_1_PI))); + EXPECT_EQ(M_2_PI, std::stod(util::dtoa(M_2_PI))); + EXPECT_EQ(M_2_SQRTPI, std::stod(util::dtoa(M_2_SQRTPI))); + EXPECT_EQ(M_SQRT2, std::stod(util::dtoa(M_SQRT2))); + EXPECT_EQ(M_SQRT1_2, std::stod(util::dtoa(M_SQRT1_2))); +} diff --git a/test/util/image.test.cpp b/test/util/image.test.cpp index 4cacf89253..f4a6473040 100644 --- a/test/util/image.test.cpp +++ b/test/util/image.test.cpp @@ -86,33 +86,49 @@ TEST(Image, WebPTile) { } #endif // !defined(__ANDROID__) && !defined(__APPLE__) && !defined(QT_IMAGE_DECODERS) +TEST(Image, Resize) { + AlphaImage image({0, 0}); + + image.resize({1, 1}); + EXPECT_EQ(image.size, Size({1, 1})); + + image.fill(100); + image.resize({2, 1}); + EXPECT_EQ(image.size, Size({2, 1})); + EXPECT_EQ(image.data[0], 100); + EXPECT_EQ(image.data[1], 0); + + image.resize({0, 0}); + EXPECT_EQ(image.size, Size({0, 0})); +} + TEST(Image, Copy) { PremultipliedImage src5({5, 5}); PremultipliedImage dst5({5, 5}); PremultipliedImage src10({10, 10}); PremultipliedImage dst10({10, 10}); - EXPECT_THROW(PremultipliedImage::copy(src5, dst10, {0, 0}, {0, 0}, {6, 0}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src5, dst10, {0, 0}, {0, 0}, {0, 6}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src5, dst10, {1, 1}, {0, 0}, {5, 0}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src5, dst10, {1, 1}, {0, 0}, {0, 5}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src5, dst10, {0, 0}, {0, 0}, {6, 1}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src5, dst10, {0, 0}, {0, 0}, {1, 6}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src5, dst10, {1, 1}, {0, 0}, {5, 1}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src5, dst10, {1, 1}, {0, 0}, {1, 5}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst5, {0, 0}, {0, 0}, {6, 0}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst5, {0, 0}, {0, 0}, {0, 6}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst5, {0, 0}, {1, 1}, {5, 0}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst5, {0, 0}, {1, 1}, {0, 5}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst5, {0, 0}, {0, 0}, {6, 1}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst5, {0, 0}, {0, 0}, {1, 6}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst5, {0, 0}, {1, 1}, {5, 1}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst5, {0, 0}, {1, 1}, {1, 5}), std::out_of_range); const uint32_t max = std::numeric_limits<uint32_t>::max(); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {max, 0}, {0, 0}, {1, 0}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, max}, {0, 0}, {0, 1}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 0}, {max, 0}, {1, 0}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 0}, {0, max}, {0, 1}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {max, 0}, {0, 0}, {1, 1}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, max}, {0, 0}, {1, 1}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 0}, {max, 0}, {1, 1}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 0}, {0, max}, {1, 1}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {1, 0}, {0, 0}, {max, 0}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 1}, {0, 0}, {0, max}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 0}, {1, 0}, {max, 0}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 0}, {0, 1}, {0, max}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {1, 0}, {0, 0}, {max, 1}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 1}, {0, 0}, {1, max}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 0}, {1, 0}, {max, 1}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 0}, {0, 1}, {1, max}), std::out_of_range); } TEST(Image, Move) { @@ -142,4 +158,8 @@ TEST(Image, Premultiply) { EXPECT_EQ(127, image.data[1]); EXPECT_EQ(127, image.data[2]); EXPECT_EQ(128, image.data[3]); + EXPECT_EQ(1u, image.size.width); + EXPECT_EQ(1u, image.size.height); + EXPECT_EQ(0u, rgba.size.width); + EXPECT_EQ(0u, rgba.size.height); } diff --git a/test/util/memory.test.cpp b/test/util/memory.test.cpp index 065d024bef..54763cd9db 100644 --- a/test/util/memory.test.cpp +++ b/test/util/memory.test.cpp @@ -3,12 +3,11 @@ #include <mbgl/test/util.hpp> #include <mbgl/map/map.hpp> -#include <mbgl/map/backend_scope.hpp> -#include <mbgl/gl/headless_backend.hpp> -#include <mbgl/gl/offscreen_view.hpp> +#include <mbgl/gl/headless_frontend.hpp> #include <mbgl/util/default_thread_pool.hpp> #include <mbgl/util/io.hpp> #include <mbgl/util/run_loop.hpp> +#include <mbgl/style/style.hpp> #include <algorithm> #include <iostream> @@ -17,7 +16,7 @@ #include <unordered_map> #include <utility> -#include <stdlib.h> +#include <cstdlib> #include <unistd.h> using namespace mbgl; @@ -35,9 +34,6 @@ public: } util::RunLoop runLoop; - HeadlessBackend backend { test::sharedDisplay() }; - BackendScope scope { backend }; - OffscreenView view{ backend.getContext(), { 512, 512 } }; StubFileSource fileSource; ThreadPool threadPool { 4 }; @@ -72,21 +68,27 @@ private: TEST(Memory, Vector) { MemoryTest test; + float ratio { 2 }; - Map map(test.backend, { 256, 256 }, 2, test.fileSource, test.threadPool, MapMode::Still); + HeadlessFrontend frontend { { 256, 256 }, ratio, test.fileSource, test.threadPool }; + Map map(frontend, MapObserver::nullObserver(), frontend.getSize(), ratio, test.fileSource, + test.threadPool, MapMode::Still); map.setZoom(16); // more map features - map.setStyleURL("mapbox://streets"); + map.getStyle().loadURL("mapbox://streets"); - test::render(map, test.view); + frontend.render(map); } TEST(Memory, Raster) { MemoryTest test; + float ratio { 2 }; - Map map(test.backend, { 256, 256 }, 2, test.fileSource, test.threadPool, MapMode::Still); - map.setStyleURL("mapbox://satellite"); + HeadlessFrontend frontend { { 256, 256 }, ratio, test.fileSource, test.threadPool }; + Map map(frontend, MapObserver::nullObserver(), frontend.getSize(), ratio, test.fileSource, + test.threadPool, MapMode::Still); + map.getStyle().loadURL("mapbox://satellite"); - test::render(map, test.view); + frontend.render(map); } /** @@ -113,53 +115,54 @@ TEST(Memory, Footprint) { if (!shouldRunFootprint()) { return; } - + MemoryTest test; - auto renderMap = [&](Map& map, const char* style){ - map.setZoom(16); - map.setStyleURL(style); - test::render(map, test.view); + class FrontendAndMap { + public: + FrontendAndMap(MemoryTest& test_, const char* style) + : frontend(Size{ 256, 256 }, 2, test_.fileSource, test_.threadPool) + , map(frontend, MapObserver::nullObserver(), frontend.getSize(), 2, test_.fileSource, test_.threadPool, MapMode::Still) { + map.setZoom(16); + map.getStyle().loadURL(style); + frontend.render(map); + } + + HeadlessFrontend frontend; + Map map; }; // Warm up buffers and cache. for (unsigned i = 0; i < 10; ++i) { - Map map(test.backend, { 256, 256 }, 2, test.fileSource, test.threadPool, MapMode::Still); - renderMap(map, "mapbox://streets"); - renderMap(map, "mapbox://satellite"); - }; + FrontendAndMap(test, "mapbox://streets"); + FrontendAndMap(test, "mapbox://satellite"); + } // Process close callbacks, mostly needed by // libuv runloop. test.runLoop.runOnce(); - std::vector<std::unique_ptr<Map>> maps; + std::vector<std::unique_ptr<FrontendAndMap>> maps; unsigned runs = 15; long vectorInitialRSS = mbgl::test::getCurrentRSS(); for (unsigned i = 0; i < runs; ++i) { - auto vector = std::make_unique<Map>(test.backend, Size{ 256, 256 }, 2, test.fileSource, - test.threadPool, MapMode::Still); - renderMap(*vector, "mapbox://streets"); - maps.push_back(std::move(vector)); - }; + maps.emplace_back(std::make_unique<FrontendAndMap>(test, "mapbox://streets")); + } double vectorFootprint = (mbgl::test::getCurrentRSS() - vectorInitialRSS) / double(runs); long rasterInitialRSS = mbgl::test::getCurrentRSS(); for (unsigned i = 0; i < runs; ++i) { - auto raster = std::make_unique<Map>(test.backend, Size{ 256, 256 }, 2, test.fileSource, - test.threadPool, MapMode::Still); - renderMap(*raster, "mapbox://satellite"); - maps.push_back(std::move(raster)); - }; + maps.emplace_back(std::make_unique<FrontendAndMap>(test, "mapbox://satellite")); + } double rasterFootprint = (mbgl::test::getCurrentRSS() - rasterInitialRSS) / double(runs); RecordProperty("vectorFootprint", vectorFootprint); RecordProperty("rasterFootprint", rasterFootprint); - ASSERT_LT(vectorFootprint, 65.2 * 1024 * 1024) << "\ + ASSERT_LT(vectorFootprint, 40 * 1024 * 1024) << "\ mbgl::Map footprint over 65.2MB for vector styles."; ASSERT_LT(rasterFootprint, 25 * 1024 * 1024) << "\ diff --git a/test/util/merge_lines.test.cpp b/test/util/merge_lines.test.cpp index 8a3a400887..d3a2ebae03 100644 --- a/test/util/merge_lines.test.cpp +++ b/test/util/merge_lines.test.cpp @@ -1,50 +1,31 @@ #include <mbgl/test/util.hpp> +#include <mbgl/test/stub_geometry_tile_feature.hpp> #include <mbgl/layout/merge_lines.hpp> #include <mbgl/layout/symbol_feature.hpp> +#include <utility> const std::u16string aaa = u"a"; const std::u16string bbb = u"b"; using namespace mbgl; -class GeometryTileFeatureStub : public GeometryTileFeature { -public: - GeometryTileFeatureStub(optional<FeatureIdentifier> id_, FeatureType type_, GeometryCollection geometry_, - std::unordered_map<std::string, Value> properties_) : - id(id_), - type(type_), - geometry(geometry_), - properties(properties_) - {} - - FeatureType getType() const override { return type; } - optional<Value> getValue(const std::string& key) const override { - auto it = properties.find(key); - if (it != properties.end()) { - return it->second; - } - return {}; - }; - std::unordered_map<std::string,Value> getProperties() const override { return properties; }; - optional<FeatureIdentifier> getID() const override { return id; }; - GeometryCollection getGeometries() const override { return geometry; }; - - optional<FeatureIdentifier> id; - FeatureType type; - GeometryCollection geometry; - std::unordered_map<std::string,Value> properties; -}; +namespace { + +PropertyMap properties; +LineString<int16_t> emptyLine; + +} class SymbolFeatureStub : public SymbolFeature { public: SymbolFeatureStub(optional<FeatureIdentifier> id_, FeatureType type_, GeometryCollection geometry_, - std::unordered_map<std::string, Value> properties_, optional<std::u16string> text_, - optional<std::string> icon_, std::size_t index_) : - SymbolFeature(std::make_unique<GeometryTileFeatureStub>(id_, type_, geometry_, properties_)) + PropertyMap properties_, optional<std::u16string> text_, + optional<std::string> icon_, std::size_t index_) : + SymbolFeature(std::make_unique<StubGeometryTileFeature>(std::move(id_), type_, std::move(geometry_), std::move(properties_))) { - text = text_; - icon = icon_; + text = std::move(text_); + icon = std::move(icon_); index = index_; } }; @@ -52,20 +33,20 @@ public: TEST(MergeLines, SameText) { // merges lines with the same text std::vector<mbgl::SymbolFeature> input1; - input1.push_back(SymbolFeatureStub({}, FeatureType::LineString, {{{0, 0}, {1, 0}, {2, 0}}}, {}, aaa, {}, 0)); - input1.push_back(SymbolFeatureStub({}, FeatureType::LineString, {{{4, 0}, {5, 0}, {6, 0}}}, {}, bbb, {}, 0)); - input1.push_back(SymbolFeatureStub({}, FeatureType::LineString, {{{8, 0}, {9, 0}}}, {}, aaa, {}, 0)); - input1.push_back(SymbolFeatureStub({}, FeatureType::LineString, {{{2, 0}, {3, 0}, {4, 0}}}, {}, aaa, {}, 0)); - input1.push_back(SymbolFeatureStub({}, FeatureType::LineString, {{{6, 0}, {7, 0}, {8, 0}}}, {}, aaa, {}, 0)); - input1.push_back(SymbolFeatureStub({}, FeatureType::LineString, {{{5, 0}, {6, 0}}}, {}, aaa, {}, 0)); - - const std::vector<GeometryTileFeatureStub> expected1 = { - { {}, FeatureType::LineString, {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}}}, {} }, - { {}, FeatureType::LineString, {{{4, 0}, {5, 0}, {6, 0}}}, {} }, - { {}, FeatureType::LineString, {{{5, 0}, {6, 0}, {7, 0}, {8, 0}, {9, 0}}}, {} }, - { {}, FeatureType::LineString, {{}}, {} }, - { {}, FeatureType::LineString, {{}}, {} }, - { {}, FeatureType::LineString, {{}}, {} } + input1.push_back(SymbolFeatureStub({}, FeatureType::LineString, {{{0, 0}, {1, 0}, {2, 0}}}, properties, aaa, {}, 0)); + input1.push_back(SymbolFeatureStub({}, FeatureType::LineString, {{{4, 0}, {5, 0}, {6, 0}}}, properties, bbb, {}, 0)); + input1.push_back(SymbolFeatureStub({}, FeatureType::LineString, {{{8, 0}, {9, 0}}}, properties, aaa, {}, 0)); + input1.push_back(SymbolFeatureStub({}, FeatureType::LineString, {{{2, 0}, {3, 0}, {4, 0}}}, properties, aaa, {}, 0)); + input1.push_back(SymbolFeatureStub({}, FeatureType::LineString, {{{6, 0}, {7, 0}, {8, 0}}}, properties, aaa, {}, 0)); + input1.push_back(SymbolFeatureStub({}, FeatureType::LineString, {{{5, 0}, {6, 0}}}, properties, aaa, {}, 0)); + + const std::vector<StubGeometryTileFeature> expected1 = { + { {}, FeatureType::LineString, {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}}}, properties }, + { {}, FeatureType::LineString, {{{4, 0}, {5, 0}, {6, 0}}}, properties }, + { {}, FeatureType::LineString, {{{5, 0}, {6, 0}, {7, 0}, {8, 0}, {9, 0}}}, properties }, + { {}, FeatureType::LineString, { emptyLine }, properties }, + { {}, FeatureType::LineString, { emptyLine }, properties }, + { {}, FeatureType::LineString, { emptyLine }, properties } }; mbgl::util::mergeLines(input1); @@ -78,14 +59,14 @@ TEST(MergeLines, SameText) { TEST(MergeLines, BothEnds) { // mergeLines handles merge from both ends std::vector<mbgl::SymbolFeature> input2; - input2.push_back(SymbolFeatureStub { {}, FeatureType::LineString, {{{0, 0}, {1, 0}, {2, 0}}}, {}, aaa, {}, 0 }); - input2.push_back(SymbolFeatureStub { {}, FeatureType::LineString, {{{4, 0}, {5, 0}, {6, 0}}}, {}, aaa, {}, 0 }); - input2.push_back(SymbolFeatureStub { {}, FeatureType::LineString, {{{2, 0}, {3, 0}, {4, 0}}}, {}, aaa, {}, 0 }); - - const std::vector<GeometryTileFeatureStub> expected2 = { - { {}, FeatureType::LineString, {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}}}, {} }, - { {}, FeatureType::LineString, {{}}, {} }, - { {}, FeatureType::LineString, {{}}, {} } + input2.push_back(SymbolFeatureStub { {}, FeatureType::LineString, {{{0, 0}, {1, 0}, {2, 0}}}, properties, aaa, {}, 0 }); + input2.push_back(SymbolFeatureStub { {}, FeatureType::LineString, {{{4, 0}, {5, 0}, {6, 0}}}, properties, aaa, {}, 0 }); + input2.push_back(SymbolFeatureStub { {}, FeatureType::LineString, {{{2, 0}, {3, 0}, {4, 0}}}, properties, aaa, {}, 0 }); + + const std::vector<StubGeometryTileFeature> expected2 = { + { {}, FeatureType::LineString, {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}}}, properties }, + { {}, FeatureType::LineString, { emptyLine }, properties }, + { {}, FeatureType::LineString, { emptyLine }, properties } }; mbgl::util::mergeLines(input2); @@ -98,14 +79,14 @@ TEST(MergeLines, BothEnds) { TEST(MergeLines, CircularLines) { // mergeLines handles circular lines std::vector<mbgl::SymbolFeature> input3; - input3.push_back(SymbolFeatureStub { {}, FeatureType::LineString, {{{0, 0}, {1, 0}, {2, 0}}}, {}, aaa, {}, 0 }); - input3.push_back(SymbolFeatureStub { {}, FeatureType::LineString, {{{2, 0}, {3, 0}, {4, 0}}}, {}, aaa, {}, 0 }); - input3.push_back(SymbolFeatureStub { {}, FeatureType::LineString, {{{4, 0}, {0, 0}}}, {}, aaa, {}, 0 }); - - const std::vector<GeometryTileFeatureStub> expected3 = { - { {}, FeatureType::LineString, {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {0, 0}}}, {} }, - { {}, FeatureType::LineString, {{}}, {} }, - { {}, FeatureType::LineString, {{}}, {} } + input3.push_back(SymbolFeatureStub { {}, FeatureType::LineString, {{{0, 0}, {1, 0}, {2, 0}}}, properties, aaa, {}, 0 }); + input3.push_back(SymbolFeatureStub { {}, FeatureType::LineString, {{{2, 0}, {3, 0}, {4, 0}}}, properties, aaa, {}, 0 }); + input3.push_back(SymbolFeatureStub { {}, FeatureType::LineString, {{{4, 0}, {0, 0}}}, properties, aaa, {}, 0 }); + + const std::vector<StubGeometryTileFeature> expected3 = { + { {}, FeatureType::LineString, {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {0, 0}}}, properties }, + { {}, FeatureType::LineString, { emptyLine }, properties }, + { {}, FeatureType::LineString, { emptyLine }, properties } }; mbgl::util::mergeLines(input3); @@ -117,9 +98,9 @@ TEST(MergeLines, CircularLines) { TEST(MergeLines, EmptyOuterGeometry) { std::vector<mbgl::SymbolFeature> input; - input.push_back(SymbolFeatureStub { {}, FeatureType::LineString, {}, {}, aaa, {}, 0 }); + input.push_back(SymbolFeatureStub { {}, FeatureType::LineString, {}, properties, aaa, {}, 0 }); - const std::vector<GeometryTileFeatureStub> expected = { { {}, FeatureType::LineString, {}, {} } }; + const std::vector<StubGeometryTileFeature> expected = { { {}, FeatureType::LineString, {}, properties } }; mbgl::util::mergeLines(input); @@ -128,9 +109,9 @@ TEST(MergeLines, EmptyOuterGeometry) { TEST(MergeLines, EmptyInnerGeometry) { std::vector<mbgl::SymbolFeature> input; - input.push_back(SymbolFeatureStub { {}, FeatureType::LineString, {{}}, {}, aaa, {}, 0 }); + input.push_back(SymbolFeatureStub { {}, FeatureType::LineString, {}, properties, aaa, {}, 0 }); - const std::vector<GeometryTileFeatureStub> expected = { { {}, FeatureType::LineString, {{}}, {} } }; + const std::vector<StubGeometryTileFeature> expected = { { {}, FeatureType::LineString, {}, properties } }; mbgl::util::mergeLines(input); diff --git a/test/util/offscreen_texture.test.cpp b/test/util/offscreen_texture.test.cpp index feaabf2630..09c940c4c3 100644 --- a/test/util/offscreen_texture.test.cpp +++ b/test/util/offscreen_texture.test.cpp @@ -3,23 +3,26 @@ #include <mbgl/gl/gl.hpp> #include <mbgl/gl/context.hpp> #include <mbgl/gl/headless_backend.hpp> -#include <mbgl/gl/offscreen_view.hpp> -#include <mbgl/map/backend_scope.hpp> +#include <mbgl/renderer/backend_scope.hpp> #include <mbgl/util/offscreen_texture.hpp> using namespace mbgl; TEST(OffscreenTexture, EmptyRed) { - HeadlessBackend backend { test::sharedDisplay() }; + HeadlessBackend backend({ 512, 256 }); BackendScope scope { backend }; - OffscreenView view(backend.getContext(), { 512, 256 }); - view.bind(); + + // Scissor test shouldn't leak after HeadlessBackend::bind(). + MBGL_CHECK_ERROR(glScissor(64, 64, 128, 128)); + backend.getContext().scissorTest.setCurrentValue(true); + + backend.bind(); MBGL_CHECK_ERROR(glClearColor(1.0f, 0.0f, 0.0f, 1.0f)); MBGL_CHECK_ERROR(glClear(GL_COLOR_BUFFER_BIT)); - auto image = view.readStillImage(); + auto image = backend.readStillImage(); test::checkImage("test/fixtures/offscreen_texture/empty-red", image, 0, 0); } @@ -35,7 +38,7 @@ struct Shader { MBGL_CHECK_ERROR(glCompileShader(fragmentShader)); MBGL_CHECK_ERROR(glAttachShader(program, fragmentShader)); MBGL_CHECK_ERROR(glLinkProgram(program)); - a_pos = glGetAttribLocation(program, "a_pos"); + a_pos = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_pos")); } ~Shader() { @@ -69,7 +72,7 @@ struct Buffer { TEST(OffscreenTexture, RenderToTexture) { - HeadlessBackend backend { test::sharedDisplay() }; + HeadlessBackend backend({ 512, 256 }); BackendScope scope { backend }; auto& context = backend.getContext(); @@ -114,13 +117,12 @@ void main() { } )MBGL_SHADER"); - GLuint u_texture = glGetUniformLocation(compositeShader.program, "u_texture"); + GLuint u_texture = MBGL_CHECK_ERROR(glGetUniformLocation(compositeShader.program, "u_texture")); Buffer triangleBuffer({ 0, 0.5, 0.5, -0.5, -0.5, -0.5 }); Buffer viewportBuffer({ -1, -1, 1, -1, -1, 1, 1, 1 }); - OffscreenView view(context, { 512, 256 }); - view.bind(); + backend.bind(); // First, draw red to the bound FBO. context.clear(Color::red(), {}, {}); @@ -128,6 +130,11 @@ void main() { // Then, create a texture, bind it, and render yellow to that texture. This should not // affect the originally bound FBO. OffscreenTexture texture(context, { 128, 128 }); + + // Scissor test shouldn't leak after OffscreenTexture::bind(). + MBGL_CHECK_ERROR(glScissor(32, 32, 64, 64)); + context.scissorTest.setCurrentValue(true); + texture.bind(); context.clear(Color(), {}, {}); @@ -143,9 +150,9 @@ void main() { test::checkImage("test/fixtures/offscreen_texture/render-to-texture", image, 0, 0); // Now reset the FBO back to normal and retrieve the original (restored) framebuffer. - view.bind(); + backend.bind(); - image = view.readStillImage(); + image = backend.readStillImage(); test::checkImage("test/fixtures/offscreen_texture/render-to-fbo", image, 0, 0); // Now, composite the Framebuffer texture we've rendered to onto the main FBO. @@ -158,6 +165,6 @@ void main() { glVertexAttribPointer(compositeShader.a_pos, 2, GL_FLOAT, GL_FALSE, 0, nullptr)); MBGL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); - image = view.readStillImage(); + image = backend.readStillImage(); test::checkImage("test/fixtures/offscreen_texture/render-to-fbo-composited", image, 0, 0.1); } diff --git a/test/util/thread.test.cpp b/test/util/thread.test.cpp index 972bddf383..76fb5ce3f0 100644 --- a/test/util/thread.test.cpp +++ b/test/util/thread.test.cpp @@ -1,63 +1,58 @@ -#include <mbgl/util/thread.hpp> -#include <mbgl/util/run_loop.hpp> - +#include <mbgl/actor/actor_ref.hpp> #include <mbgl/test/util.hpp> +#include <mbgl/util/default_thread_pool.hpp> +#include <mbgl/util/run_loop.hpp> +#include <mbgl/util/thread.hpp> +#include <mbgl/util/timer.hpp> #include <atomic> +#include <memory> +using namespace mbgl; using namespace mbgl::util; class TestObject { public: - TestObject(std::thread::id otherTid) + TestObject(ActorRef<TestObject>, std::thread::id otherTid) : tid(std::this_thread::get_id()) { EXPECT_NE(tid, otherTid); } - void fn1(int val) { + ~TestObject() { EXPECT_EQ(tid, std::this_thread::get_id()); - EXPECT_EQ(val, 1); } - void fn2(std::function<void (int)> cb) { + void fn1(int val) const { EXPECT_EQ(tid, std::this_thread::get_id()); - cb(1); - } - - void transferIn(std::unique_ptr<int> val) { - EXPECT_EQ(tid, std::this_thread::get_id()); - EXPECT_EQ(*val, 1); + EXPECT_EQ(val, 1); } - void transferOut(std::function<void (std::unique_ptr<int>)> cb) { + void fn2(std::function<void (int)> cb) const { EXPECT_EQ(tid, std::this_thread::get_id()); - cb(std::make_unique<int>(1)); + cb(1); } - void transferInOut(std::unique_ptr<int> val, std::function<void (std::unique_ptr<int>)> cb) { + void transferIn(std::unique_ptr<int> val) const { EXPECT_EQ(tid, std::this_thread::get_id()); EXPECT_EQ(*val, 1); - cb(std::move(val)); } - void transferInShared(std::shared_ptr<int> val) { + void transferInShared(std::shared_ptr<int> val) const { EXPECT_EQ(tid, std::this_thread::get_id()); EXPECT_EQ(*val, 1); } - void transferOutShared(std::function<void (std::shared_ptr<int>)> cb) { + void transferString(const std::string& string) const { EXPECT_EQ(tid, std::this_thread::get_id()); - cb(std::make_shared<int>(1)); + EXPECT_EQ(string, "test"); } - void transferString(const std::string& string, std::function<void (std::string)> cb) { - EXPECT_EQ(tid, std::this_thread::get_id()); - EXPECT_EQ(string, "test"); - cb(string); + void checkContext(std::promise<bool> result) const { + result.set_value(tid == std::this_thread::get_id()); } - void checkContext(std::function<void (bool)> cb) const { - cb(tid == std::this_thread::get_id()); + void sync(std::promise<void> result) const { + result.set_value(); } const std::thread::id tid; @@ -65,95 +60,61 @@ public: TEST(Thread, invoke) { const std::thread::id tid = std::this_thread::get_id(); + Thread<TestObject> thread("Test", tid); - RunLoop loop; - std::vector<std::unique_ptr<mbgl::AsyncRequest>> requests; + thread.actor().invoke(&TestObject::fn1, 1); + thread.actor().invoke(&TestObject::fn2, [] (int result) { EXPECT_EQ(result, 1); } ); + thread.actor().invoke(&TestObject::transferIn, std::make_unique<int>(1)); + thread.actor().invoke(&TestObject::transferInShared, std::make_shared<int>(1)); - loop.invoke([&] { - EXPECT_EQ(tid, std::this_thread::get_id()); - Thread<TestObject> thread({"Test"}, tid); - - thread.invoke(&TestObject::fn1, 1); - requests.push_back(thread.invokeWithCallback(&TestObject::fn2, [&] (int result) { - EXPECT_EQ(tid, std::this_thread::get_id()); - EXPECT_EQ(result, 1); - })); - - thread.invoke(&TestObject::transferIn, std::make_unique<int>(1)); - requests.push_back(thread.invokeWithCallback(&TestObject::transferOut, [&] (std::unique_ptr<int> result) { - EXPECT_EQ(tid, std::this_thread::get_id()); - EXPECT_EQ(*result, 1); - })); - - requests.push_back(thread.invokeWithCallback(&TestObject::transferInOut, std::make_unique<int>(1), [&] (std::unique_ptr<int> result) { - EXPECT_EQ(tid, std::this_thread::get_id()); - EXPECT_EQ(*result, 1); - })); - - thread.invoke(&TestObject::transferInShared, std::make_shared<int>(1)); - requests.push_back(thread.invokeWithCallback(&TestObject::transferOutShared, [&] (std::shared_ptr<int> result) { - EXPECT_EQ(tid, std::this_thread::get_id()); - EXPECT_EQ(*result, 1); - })); - - // Cancelled request - thread.invokeWithCallback(&TestObject::fn2, [&] (int) { - ADD_FAILURE(); - }); + std::string test("test"); + thread.actor().invoke(&TestObject::transferString, test); - std::string test("test"); - requests.push_back(thread.invokeWithCallback(&TestObject::transferString, test, [&] (std::string result){ - EXPECT_EQ(tid, std::this_thread::get_id()); - EXPECT_EQ(result, "test"); - loop.stop(); - })); - test.clear(); - }); - - loop.run(); + // Make sure the message queue was consumed before ending the test. + std::promise<void> result; + auto resultFuture = result.get_future(); + thread.actor().invoke(&TestObject::sync, std::move(result)); + resultFuture.get(); } -TEST(Thread, context) { +TEST(Thread, Context) { const std::thread::id tid = std::this_thread::get_id(); + Thread<TestObject> thread("Test", tid); - RunLoop loop; - std::vector<std::unique_ptr<mbgl::AsyncRequest>> requests; - - loop.invoke([&] { - Thread<TestObject> thread({"Test"}, tid); - - requests.push_back(thread.invokeWithCallback(&TestObject::checkContext, [&] (bool inTestThreadContext) { - EXPECT_EQ(inTestThreadContext, true); - loop.stop(); - })); - }); + std::promise<bool> result; + auto resultFuture = result.get_future(); - loop.run(); + thread.actor().invoke(&TestObject::checkContext, std::move(result)); + EXPECT_EQ(resultFuture.get(), true); } class TestWorker { public: - TestWorker() = default; + TestWorker(ActorRef<TestWorker>) {} - void send(std::function<void ()> fn, std::function<void ()> cb) { - fn(); + void send(std::function<void ()> cb) { cb(); } + + void sendDelayed(std::function<void ()> cb) { + timer.start(Milliseconds(300), mbgl::Duration::zero(), [cb] { + cb(); + }); + } + +private: + Timer timer; }; TEST(Thread, ExecutesAfter) { RunLoop loop; - Thread<TestWorker> thread({"Test"}); + Thread<TestWorker> thread("Test"); bool didWork = false; bool didAfter = false; - auto request = thread.invokeWithCallback(&TestWorker::send, [&] { - didWork = true; - }, [&] { - didAfter = true; - loop.stop(); - }); + thread.actor().invoke(&TestWorker::send, [&] { didWork = true; }); + thread.actor().invoke(&TestWorker::send, [&] { didAfter = true; loop.stop(); }); loop.run(); @@ -161,72 +122,92 @@ TEST(Thread, ExecutesAfter) { EXPECT_TRUE(didAfter); } -TEST(Thread, WorkRequestDeletionWaitsForWorkToComplete) { +TEST(Thread, CanSelfWakeUp) { RunLoop loop; + Thread<TestWorker> thread("Test"); - Thread<TestWorker> thread({"Test"}); + thread.actor().invoke(&TestWorker::sendDelayed, [&] { + loop.stop(); + }); - std::promise<void> started; - bool didWork = false; + loop.run(); +} - auto request = thread.invokeWithCallback(&TestWorker::send, [&] { - started.set_value(); - usleep(10000); - didWork = true; - }, [&] {}); +TEST(Thread, Concurrency) { + auto loop = std::make_shared<RunLoop>(); + + unsigned numMessages = 100000; + std::atomic_uint completed(numMessages); + + ThreadPool threadPool(10); + Actor<TestWorker> poolWorker(threadPool); + auto poolWorkerRef = poolWorker.self(); + + Thread<TestWorker> threadedObject("Test"); + auto threadedObjectRef = threadedObject.actor(); + + // 10 threads sending 100k messages to the Thread. The + // idea here is to test if the scheduler is handling concurrency + // correctly, otherwise this test should crash. + for (unsigned i = 0; i < numMessages; ++i) { + poolWorkerRef.invoke(&TestWorker::send, [threadedObjectRef, loop, &completed] () mutable { + threadedObjectRef.invoke(&TestWorker::send, [loop, &completed] () { + if (!--completed) { + loop->stop(); + } + }); + }); + }; - started.get_future().get(); - request.reset(); - EXPECT_TRUE(didWork); + loop->run(); } -TEST(Thread, WorkRequestDeletionCancelsAfter) { - RunLoop loop; - Thread<TestWorker> thread({"Test"}); +TEST(Thread, ThreadPoolMessaging) { + auto loop = std::make_shared<RunLoop>(); - std::promise<void> started; - bool didAfter = false; + ThreadPool threadPool(1); + Actor<TestWorker> poolWorker(threadPool); + auto poolWorkerRef = poolWorker.self(); + + Thread<TestWorker> threadedObject("Test"); + auto threadedObjectRef = threadedObject.actor(); - auto request = thread.invokeWithCallback(&TestWorker::send, [&] { - started.set_value(); - }, [&] { - didAfter = true; + // This is sending a message to the Thread from the main + // thread. Then the Thread will send another message to + // a worker on the ThreadPool. + threadedObjectRef.invoke(&TestWorker::send, [poolWorkerRef, loop] () mutable { + poolWorkerRef.invoke(&TestWorker::send, [loop] () { loop->stop(); }); }); - started.get_future().get(); - request.reset(); - loop.runOnce(); - EXPECT_FALSE(didAfter); -} + loop->run(); -TEST(Thread, WorkRequestDeletionCancelsImmediately) { - RunLoop loop; - Thread<TestWorker> thread({"Test"}); + // Same as before, but in the opposite direction. + poolWorkerRef.invoke(&TestWorker::send, [threadedObjectRef, loop] () mutable { + threadedObjectRef.invoke(&TestWorker::send, [loop] () { loop->stop(); }); + }); - std::promise<void> started; + loop->run(); +} + +TEST(Thread, ReferenceCanOutliveThread) { + auto thread = std::make_unique<Thread<TestWorker>>("Test"); + auto worker = thread->actor(); - auto request1 = thread.invokeWithCallback(&TestWorker::send, [&] { - usleep(10000); - started.set_value(); - }, [&] {}); + thread.reset(); - auto request2 = thread.invokeWithCallback(&TestWorker::send, [&] { - ADD_FAILURE() << "Second work item should not be invoked"; - }, [&] {}); - request2.reset(); + for (unsigned i = 0; i < 1000; ++i) { + worker.invoke(&TestWorker::send, [&] { ADD_FAILURE() << "Should never happen"; }); + } - started.get_future().get(); - request1.reset(); + usleep(10000); } TEST(Thread, DeletePausedThread) { - RunLoop loop; - std::atomic_bool flag(false); - auto thread = std::make_unique<Thread<TestWorker>>(ThreadContext{"Test"}); + auto thread = std::make_unique<Thread<TestWorker>>("Test"); thread->pause(); - thread->invoke(&TestWorker::send, [&] { flag = true; }, [] {}); + thread->actor().invoke(&TestWorker::send, [&] { flag = true; }); // Should not hang. thread.reset(); @@ -240,18 +221,18 @@ TEST(Thread, Pause) { std::atomic_bool flag(false); - Thread<TestWorker> thread1({"Test1"}); + Thread<TestWorker> thread1("Test1"); thread1.pause(); - Thread<TestWorker> thread2({"Test2"}); + Thread<TestWorker> thread2("Test2"); for (unsigned i = 0; i < 100; ++i) { - thread1.invoke(&TestWorker::send, [&] { flag = true; }, [] {}); - thread2.invoke(&TestWorker::send, [&] { ASSERT_FALSE(flag); }, [] {}); + thread1.actor().invoke(&TestWorker::send, [&] { flag = true; }); + thread2.actor().invoke(&TestWorker::send, [&] { ASSERT_FALSE(flag); }); } // Queue a message at the end of thread2 queue. - thread2.invoke(&TestWorker::send, [&] { loop.stop(); }, [] {}); + thread2.actor().invoke(&TestWorker::send, [&] { loop.stop(); }); loop.run(); } @@ -260,16 +241,16 @@ TEST(Thread, Resume) { std::atomic_bool flag(false); - Thread<TestWorker> thread({"Test"}); + Thread<TestWorker> thread("Test"); thread.pause(); for (unsigned i = 0; i < 100; ++i) { - thread.invoke(&TestWorker::send, [&] { flag = true; }, [] {}); + thread.actor().invoke(&TestWorker::send, [&] { flag = true; }); } // Thread messages are ondered, when we resume, this is going // to me the last thing to run on the message queue. - thread.invoke(&TestWorker::send, [&] { loop.stop(); }, [] {}); + thread.actor().invoke(&TestWorker::send, [&] { loop.stop(); }); // This test will be flaky if the thread doesn't get paused. ASSERT_FALSE(flag); @@ -283,7 +264,7 @@ TEST(Thread, Resume) { TEST(Thread, PauseResume) { RunLoop loop; - Thread<TestWorker> thread({"Test"}); + Thread<TestWorker> thread("Test"); // Test if multiple pause/resume work. for (unsigned i = 0; i < 100; ++i) { @@ -291,6 +272,6 @@ TEST(Thread, PauseResume) { thread.resume(); } - thread.invoke(&TestWorker::send, [&] { loop.stop(); }, [] {}); + thread.actor().invoke(&TestWorker::send, [&] { loop.stop(); }); loop.run(); } diff --git a/test/util/thread_local.test.cpp b/test/util/thread_local.test.cpp index 4ee7042580..7142697f48 100644 --- a/test/util/thread_local.test.cpp +++ b/test/util/thread_local.test.cpp @@ -4,13 +4,15 @@ #include <mbgl/test/util.hpp> +#include <future> + using namespace mbgl::util; namespace { class TestThread { public: - TestThread(int *number_) { + TestThread(mbgl::ActorRef<TestThread>, int *number_) { number.set(number_); } @@ -18,8 +20,8 @@ public: number.set(nullptr); } - int getNumber() { - return *number.get(); + void getNumber(std::promise<int> result){ + result.set_value(*number.get()); } private: @@ -37,15 +39,28 @@ TEST(ThreadLocalStorage, Basic) { int number2 = 2; int number3 = 3; - ThreadContext context = {"Test"}; + Thread<TestThread> thread1("Test", &number1); + Thread<TestThread> thread2("Test", &number2); + Thread<TestThread> thread3("Test", &number3); + + auto thread1Ref = thread1.actor(); + auto thread2Ref = thread2.actor(); + auto thread3Ref = thread3.actor(); + + std::promise<int> result1; + auto result1Future = result1.get_future(); + thread1Ref.invoke(&TestThread::getNumber, std::move(result1)); + EXPECT_EQ(number1, result1Future.get()); - Thread<TestThread> thread1(context, &number1); - Thread<TestThread> thread2(context, &number2); - Thread<TestThread> thread3(context, &number3); + std::promise<int> result2; + auto result2Future = result2.get_future(); + thread2Ref.invoke(&TestThread::getNumber, std::move(result2)); + EXPECT_EQ(number2, result2Future.get()); - EXPECT_EQ(number1, thread1.invokeSync(&TestThread::getNumber)); - EXPECT_EQ(number2, thread2.invokeSync(&TestThread::getNumber)); - EXPECT_EQ(number3, thread3.invokeSync(&TestThread::getNumber)); + std::promise<int> result3; + auto result3Future = result3.get_future(); + thread3Ref.invoke(&TestThread::getNumber, std::move(result3)); + EXPECT_EQ(number3, result3Future.get()); } TEST(ThreadLocalStorage, NotSetReturnsNull) { @@ -56,40 +71,38 @@ TEST(ThreadLocalStorage, NotSetReturnsNull) { namespace { -struct DtorCounter { - ~DtorCounter() { ++(*value); } - unsigned *value; -}; - -class TestThreadReclaim { +class TestThreadDataOwnership { public: - TestThreadReclaim(DtorCounter* counter_) { - counter.set(counter_); + TestThreadDataOwnership(mbgl::ActorRef<TestThreadDataOwnership>, int* data_) { + data.set(data_); + } + + ~TestThreadDataOwnership() { + data.set(nullptr); } private: - static ThreadLocal<DtorCounter> counter; + static ThreadLocal<int> data; }; -ThreadLocal<DtorCounter> TestThreadReclaim::counter; +ThreadLocal<int> TestThreadDataOwnership::data; } // namespace -TEST(ThreadLocalStorage, AutoReclaim) { +TEST(ThreadLocalStorage, ShouldNotTakeOwnership) { RunLoop loop; - unsigned counter = 0; - - auto dtorCounter1 = new DtorCounter{ &counter }; - auto dtorCounter2 = new DtorCounter{ &counter }; - - ThreadContext context = {"Test"}; + auto data1 = std::make_unique<int>(10); + auto data2 = std::make_unique<int>(20); - auto thread1 = std::make_unique<Thread<TestThreadReclaim>>(context, dtorCounter1); - auto thread2 = std::make_unique<Thread<TestThreadReclaim>>(context, dtorCounter2); + auto thread1 = std::make_unique<Thread<TestThreadDataOwnership>>("Test", data1.get()); + auto thread2 = std::make_unique<Thread<TestThreadDataOwnership>>("Test", data2.get()); thread1.reset(); thread2.reset(); - EXPECT_EQ(counter, 2u); + // Will crash if ThreadLocal destroys + // the pointer it is managing. + ASSERT_EQ(*data1, 10); + ASSERT_EQ(*data2, 20); } diff --git a/test/util/tile_cover.test.cpp b/test/util/tile_cover.test.cpp index c746e6dab5..933c18b5ea 100644 --- a/test/util/tile_cover.test.cpp +++ b/test/util/tile_cover.test.cpp @@ -84,3 +84,12 @@ TEST(TileCover, SanFranciscoZ0Wrapped) { EXPECT_EQ((std::vector<UnwrappedTileID>{ { 0, 1, 0 } }), util::tileCover(sanFranciscoWrapped, 0)); } + +TEST(TileCount, SanFranciscoZ10) { + EXPECT_EQ(4u, util::tileCount(sanFrancisco, 10, util::tileSize)); +} + +TEST(TileCount, SanFranciscoZ22) { + EXPECT_EQ(7254450u, util::tileCount(sanFrancisco, 22, util::tileSize)); +} + diff --git a/test/util/work_queue.test.cpp b/test/util/work_queue.test.cpp deleted file mode 100644 index 60c72f7358..0000000000 --- a/test/util/work_queue.test.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include <mbgl/test/util.hpp> - -#include <mbgl/util/run_loop.hpp> -#include <mbgl/util/thread.hpp> -#include <mbgl/util/work_queue.hpp> - -#include <thread> - -using namespace mbgl::util; - -class TestThread { -public: - TestThread(WorkQueue* queue_) : queue(queue_) {} - - void send(std::function<void()>&& fn) { - queue->push(std::move(fn)); - } - -private: - WorkQueue* queue; -}; - -TEST(WorkQueue, push) { - RunLoop loop; - - WorkQueue queue; - Thread<TestThread> thread({"Test"}, &queue); - - uint8_t count = 0; - - auto endTest = [&]() { - if (++count == 4) { - loop.stop(); - } - }; - - thread.invoke(&TestThread::send, endTest); - thread.invoke(&TestThread::send, endTest); - thread.invoke(&TestThread::send, endTest); - thread.invoke(&TestThread::send, endTest); - - loop.run(); -} - -TEST(WorkQueue, cancel) { - RunLoop loop; - - WorkQueue queue; - - auto work = [&]() { - FAIL() << "Should never be called"; - }; - - queue.push(work); - queue.push(work); - queue.push(work); - queue.push(work); - queue.push(work); -} |