summaryrefslogtreecommitdiff
path: root/test/util
diff options
context:
space:
mode:
authorJohn Firebaugh <john.firebaugh@gmail.com>2016-02-02 11:09:16 -0800
committerJohn Firebaugh <john.firebaugh@gmail.com>2016-02-02 11:31:13 -0800
commitb97dcbc12592fc93d2c8137f3d56a523e994a136 (patch)
tree12e5f91d686f2853c5322a525663193a546ab130 /test/util
parentce8c6e26d58f91bba46576080caf48e4765c176f (diff)
downloadqtlocation-mapboxgl-b97dcbc12592fc93d2c8137f3d56a523e994a136.tar.gz
[tests] Reorganize tests to match src structure
Diffstat (limited to 'test/util')
-rw-r--r--test/util/assert.cpp9
-rw-r--r--test/util/async_task.cpp126
-rw-r--r--test/util/clip_ids.cpp251
-rw-r--r--test/util/geo.cpp61
-rw-r--r--test/util/image.cpp101
-rw-r--r--test/util/mapbox.cpp145
-rw-r--r--test/util/merge_lines.cpp75
-rw-r--r--test/util/run_loop.cpp75
-rw-r--r--test/util/text_conversions.cpp35
-rw-r--r--test/util/thread.cpp226
-rw-r--r--test/util/thread_local.cpp95
-rw-r--r--test/util/timer.cpp173
-rw-r--r--test/util/token.cpp50
-rw-r--r--test/util/work_queue.cpp63
14 files changed, 1485 insertions, 0 deletions
diff --git a/test/util/assert.cpp b/test/util/assert.cpp
new file mode 100644
index 0000000000..f3eba009f0
--- /dev/null
+++ b/test/util/assert.cpp
@@ -0,0 +1,9 @@
+#include "../fixtures/util.hpp"
+
+#include <mbgl/util/assert.hpp>
+
+using namespace mbgl;
+
+TEST(Assert, Always) {
+ EXPECT_DEATH(assert_always(true == false), "failed assertion `true == false'");
+}
diff --git a/test/util/async_task.cpp b/test/util/async_task.cpp
new file mode 100644
index 0000000000..8073670fe1
--- /dev/null
+++ b/test/util/async_task.cpp
@@ -0,0 +1,126 @@
+#include <mbgl/util/async_task.hpp>
+#include <mbgl/util/run_loop.hpp>
+#include <mbgl/util/thread.hpp>
+
+#include "../fixtures/util.hpp"
+
+#include <vector>
+
+using namespace mbgl::util;
+
+namespace {
+
+class TestWorker {
+public:
+ TestWorker(AsyncTask *async_)
+ : async(async_) {}
+
+ void run() {
+ for (unsigned i = 0; i < 100000; ++i) {
+ async->send();
+ }
+ }
+
+ void runWithCallback(std::function<void()> cb) {
+ for (unsigned i = 0; i < 100000; ++i) {
+ async->send();
+ }
+
+ cb();
+ }
+
+private:
+ AsyncTask *async;
+};
+
+} // namespace
+
+TEST(AsyncTask, RequestCoalescing) {
+ RunLoop loop;
+
+ unsigned count = 0;
+ AsyncTask async([&count] { ++count; });
+
+ async.send();
+ async.send();
+ async.send();
+ async.send();
+ async.send();
+
+ loop.runOnce();
+
+ EXPECT_EQ(count, 1);
+}
+
+TEST(AsyncTask, DestroyShouldNotRunQueue) {
+ RunLoop loop;
+
+ unsigned count = 0;
+ auto async = std::make_unique<AsyncTask>([&count] { ++count; });
+
+ async->send();
+ async.reset();
+
+ EXPECT_EQ(count, 0);
+}
+
+TEST(AsyncTask, RequestCoalescingMultithreaded) {
+ RunLoop loop;
+
+ unsigned count = 0;
+ AsyncTask async([&count] { ++count; });
+
+ std::vector<std::unique_ptr<Thread<TestWorker>>> threads;
+ ThreadContext context = {"Test", ThreadType::Map, ThreadPriority::Regular};
+
+ unsigned numThreads = 25;
+ for (unsigned i = 0; i < numThreads; ++i) {
+ std::unique_ptr<Thread<TestWorker>> thread =
+ std::make_unique<Thread<TestWorker>>(context, &async);
+
+ thread->invoke(&TestWorker::run);
+ threads.push_back(std::move(thread));
+ }
+
+ // Join all the threads
+ threads.clear();
+
+ loop.runOnce();
+
+ EXPECT_EQ(count, 1);
+}
+
+TEST(AsyncTask, ThreadSafety) {
+ RunLoop loop;
+
+ unsigned count = 0;
+ AsyncTask async([&count] { ++count; });
+
+ unsigned numThreads = 25;
+
+ auto callback = [&] {
+ if (!--numThreads) {
+ loop.stop();
+ }
+ };
+
+ std::vector<std::unique_ptr<Thread<TestWorker>>> threads;
+ std::vector<std::unique_ptr<mbgl::WorkRequest>> requests;
+ ThreadContext context = {"Test", ThreadType::Map, ThreadPriority::Regular};
+
+ 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));
+ }
+
+ loop.run();
+
+ // We expect here more than 1 but 1 would also be
+ // a valid result, although very unlikely (I hope).
+ EXPECT_GT(count, 1);
+}
diff --git a/test/util/clip_ids.cpp b/test/util/clip_ids.cpp
new file mode 100644
index 0000000000..4ecd1797e6
--- /dev/null
+++ b/test/util/clip_ids.cpp
@@ -0,0 +1,251 @@
+#include <iostream>
+#include "../fixtures/util.hpp"
+
+#include <algorithm>
+
+#include <mbgl/util/clip_id.hpp>
+#include <mbgl/map/tile.hpp>
+
+using namespace mbgl;
+
+template <typename T> void generate(const T &sources) {
+ ClipIDGenerator generator;
+
+ for (size_t j = 0; j < sources.size(); j++) {
+ std::forward_list<Tile *> tile_ptrs;
+ std::transform(sources[j].begin(), sources[j].end(), std::front_inserter(tile_ptrs), [](const std::shared_ptr<Tile> &tile) { return tile.get(); });
+ generator.update(tile_ptrs);
+ }
+}
+
+template <typename T> void print(const T &sources) {
+ for (size_t j = 0; j < sources.size(); j++) {
+ for (size_t i = 0; i < sources[j].size(); i++) {
+ std::cout << " ASSERT_EQ(ClipID(\"" << sources[j][i]->clip.mask << "\", \"" << sources[j][i]->clip.reference << "\"), sources[" << j << "][" << i << "]->clip);\n";
+ }
+ }
+}
+
+TEST(ClipIDs, ParentAndFourChildren) {
+ const std::vector<std::vector<std::shared_ptr<Tile>>> sources = {
+ {
+ std::make_shared<Tile>(TileID { 1, 0, 0, 1 }),
+ std::make_shared<Tile>(TileID { 1, 0, 1, 1 }),
+ std::make_shared<Tile>(TileID { 1, 1, 0, 1 }),
+ std::make_shared<Tile>(TileID { 1, 1, 1, 1 }),
+ std::make_shared<Tile>(TileID { 0, 0, 0, 0 }),
+ },
+ };
+
+ generate(sources);
+ // print(sources);
+
+ ASSERT_EQ(ClipID("00000111", "00000010"), sources[0][0]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000011"), sources[0][1]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000100"), sources[0][2]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000101"), sources[0][3]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000001"), sources[0][4]->clip);
+}
+
+TEST(ClipIDs, ParentAndFourChildrenNegative) {
+ const std::vector<std::vector<std::shared_ptr<Tile>>> sources = {
+ {
+ std::make_shared<Tile>(TileID { 1, -2, 0, 1 }),
+ std::make_shared<Tile>(TileID { 1, -2, 1, 1 }),
+ std::make_shared<Tile>(TileID { 1, -1, 0, 1 }),
+ std::make_shared<Tile>(TileID { 1, -1, 1, 1 }),
+ std::make_shared<Tile>(TileID { 0, -1, 0, 0 }),
+ },
+ };
+
+ generate(sources);
+ // print(sources);
+
+ ASSERT_EQ(ClipID("00000111", "00000010"), sources[0][0]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000011"), sources[0][1]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000100"), sources[0][2]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000101"), sources[0][3]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000001"), sources[0][4]->clip);
+}
+
+TEST(ClipIDs, NegativeParentAndMissingLevel) {
+ const std::vector<std::vector<std::shared_ptr<Tile>>> sources = {
+ {
+ std::make_shared<Tile>(TileID { 1, -1, 0, 1 }),
+ std::make_shared<Tile>(TileID { 2, -1, 0, 2 }),
+ std::make_shared<Tile>(TileID { 2, -2, 1, 2 }),
+ std::make_shared<Tile>(TileID { 2, -1, 1, 2 }),
+ std::make_shared<Tile>(TileID { 2, -2, 0, 2 }),
+ },
+ };
+
+ generate(sources);
+ // print(sources);
+
+ ASSERT_EQ(ClipID("00000111", "00000001"), sources[0][0]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000100"), sources[0][1]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000011"), sources[0][2]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000101"), sources[0][3]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000010"), sources[0][4]->clip);
+}
+
+
+TEST(ClipIDs, SevenOnSameLevel) {
+ const std::vector<std::vector<std::shared_ptr<Tile>>> sources = {
+ {
+ std::make_shared<Tile>(TileID { 2, 0, 0, 2 }),
+ std::make_shared<Tile>(TileID { 2, 0, 1, 2 }),
+ std::make_shared<Tile>(TileID { 2, 0, 2, 2 }),
+ std::make_shared<Tile>(TileID { 2, 1, 0, 2 }),
+ std::make_shared<Tile>(TileID { 2, 1, 1, 2 }),
+ std::make_shared<Tile>(TileID { 2, 1, 2, 2 }),
+ std::make_shared<Tile>(TileID { 2, 2, 0, 2 }),
+ },
+ };
+
+ generate(sources);
+ // print(sources);
+
+ ASSERT_EQ(ClipID("00000111", "00000001"), sources[0][0]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000010"), sources[0][1]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000011"), sources[0][2]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000100"), sources[0][3]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000101"), sources[0][4]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000110"), sources[0][5]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000111"), sources[0][6]->clip);
+}
+
+TEST(ClipIDs, MultipleLevels) {
+ const std::vector<std::vector<std::shared_ptr<Tile>>> sources = {
+ {
+ std::make_shared<Tile>(TileID { 2, 0, 0, 2 }),
+ std::make_shared<Tile>(TileID { 3, 0, 0, 3 }),
+ std::make_shared<Tile>(TileID { 3, 0, 1, 3 }),
+ std::make_shared<Tile>(TileID { 4, 0, 2, 4 }),
+ std::make_shared<Tile>(TileID { 4, 1, 2, 4 }),
+ std::make_shared<Tile>(TileID { 4, 0, 3, 4 }),
+ std::make_shared<Tile>(TileID { 4, 1, 3, 4 }),
+ std::make_shared<Tile>(TileID { 3, 1, 0, 3 }),
+ std::make_shared<Tile>(TileID { 3, 1, 1, 3 }),
+ std::make_shared<Tile>(TileID { 2, 1, 0, 2 }),
+ std::make_shared<Tile>(TileID { 3, 2, 0, 3 }),
+ std::make_shared<Tile>(TileID { 3, 2, 1, 3 }),
+ },
+ };
+
+ generate(sources);
+ // print(sources);
+
+ ASSERT_EQ(ClipID("00001111", "00000001"), sources[0][0]->clip);
+ ASSERT_EQ(ClipID("00001111", "00000011"), sources[0][1]->clip);
+ ASSERT_EQ(ClipID("00001111", "00000100"), sources[0][2]->clip);
+ ASSERT_EQ(ClipID("00001111", "00001001"), sources[0][3]->clip);
+ ASSERT_EQ(ClipID("00001111", "00001011"), sources[0][4]->clip);
+ ASSERT_EQ(ClipID("00001111", "00001010"), sources[0][5]->clip);
+ ASSERT_EQ(ClipID("00001111", "00001100"), sources[0][6]->clip);
+ ASSERT_EQ(ClipID("00001111", "00000101"), sources[0][7]->clip);
+ ASSERT_EQ(ClipID("00001111", "00000110"), sources[0][8]->clip);
+ ASSERT_EQ(ClipID("00001111", "00000010"), sources[0][9]->clip);
+ ASSERT_EQ(ClipID("00001111", "00000111"), sources[0][10]->clip);
+ ASSERT_EQ(ClipID("00001111", "00001000"), sources[0][11]->clip);
+}
+
+
+TEST(ClipIDs, Bug206) {
+ const std::vector<std::vector<std::shared_ptr<Tile>>> sources = {
+ {
+ std::make_shared<Tile>(TileID { 10, 162, 395, 10 }),
+ std::make_shared<Tile>(TileID { 10, 162, 396, 10 }),
+ std::make_shared<Tile>(TileID { 10, 163, 395, 10 }),
+ std::make_shared<Tile>(TileID { 11, 326, 791, 10 }),
+ std::make_shared<Tile>(TileID { 12, 654, 1582, 10 }),
+ std::make_shared<Tile>(TileID { 12, 654, 1583, 10 }),
+ std::make_shared<Tile>(TileID { 12, 655, 1582, 10 }),
+ std::make_shared<Tile>(TileID { 12, 655, 1583, 10 }),
+ std::make_shared<Tile>(TileID { 10, 163, 396, 10 }),
+ std::make_shared<Tile>(TileID { 10, 164, 395, 10 }),
+ std::make_shared<Tile>(TileID { 10, 164, 396, 10 }),
+ },
+ };
+
+ generate(sources);
+ // print(sources);
+
+ ASSERT_EQ(ClipID("00001111", "00000001"), sources[0][0]->clip);
+ ASSERT_EQ(ClipID("00001111", "00000010"), sources[0][1]->clip);
+ ASSERT_EQ(ClipID("00001111", "00000011"), sources[0][2]->clip);
+ ASSERT_EQ(ClipID("00001111", "00000111"), sources[0][3]->clip);
+ ASSERT_EQ(ClipID("00001111", "00001000"), sources[0][4]->clip);
+ ASSERT_EQ(ClipID("00001111", "00001001"), sources[0][5]->clip);
+ ASSERT_EQ(ClipID("00001111", "00001010"), sources[0][6]->clip);
+ ASSERT_EQ(ClipID("00001111", "00001011"), sources[0][7]->clip);
+ ASSERT_EQ(ClipID("00001111", "00000100"), sources[0][8]->clip);
+ ASSERT_EQ(ClipID("00001111", "00000101"), sources[0][9]->clip);
+ ASSERT_EQ(ClipID("00001111", "00000110"), sources[0][10]->clip);
+}
+
+
+TEST(ClipIDs, MultipleSources) {
+ const std::vector<std::vector<std::shared_ptr<Tile>>> sources = {
+ {
+ std::make_shared<Tile>(TileID { 0, 0, 0, 0 }),
+ std::make_shared<Tile>(TileID { 1, 1, 1, 1 }),
+ std::make_shared<Tile>(TileID { 2, 2, 1, 2 }),
+ std::make_shared<Tile>(TileID { 2, 2, 2, 2 }),
+ },
+ {
+ std::make_shared<Tile>(TileID { 0, 0, 0, 0 }),
+ std::make_shared<Tile>(TileID { 1, 1, 1, 1 }),
+ std::make_shared<Tile>(TileID { 2, 1, 1, 2 }),
+ std::make_shared<Tile>(TileID { 2, 2, 2, 2 }),
+ },
+ {
+ std::make_shared<Tile>(TileID { 1, 0, 0, 1 }),
+ std::make_shared<Tile>(TileID { 1, 0, 1, 1 }),
+ std::make_shared<Tile>(TileID { 1, 1, 0, 1 }),
+ std::make_shared<Tile>(TileID { 1, 1, 1, 1 }),
+ std::make_shared<Tile>(TileID { 2, 1, 1, 2 }),
+ },
+ };
+
+ generate(sources);
+ // print(sources);
+
+ ASSERT_EQ(ClipID("00000111", "00000001"), sources[0][0]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000010"), sources[0][1]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000011"), sources[0][2]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000100"), sources[0][3]->clip);
+ ASSERT_EQ(ClipID("00011000", "00001000"), sources[1][0]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000010"), sources[1][1]->clip);
+ ASSERT_EQ(ClipID("00011000", "00010000"), sources[1][2]->clip);
+ ASSERT_EQ(ClipID("00000111", "00000100"), sources[1][3]->clip);
+ ASSERT_EQ(ClipID("11100000", "00100000"), sources[2][0]->clip);
+ ASSERT_EQ(ClipID("11100000", "01000000"), sources[2][1]->clip);
+ ASSERT_EQ(ClipID("11100000", "01100000"), sources[2][2]->clip);
+ ASSERT_EQ(ClipID("11100000", "10000000"), sources[2][3]->clip);
+ ASSERT_EQ(ClipID("00011000", "00010000"), sources[2][4]->clip);
+}
+
+
+TEST(ClipIDs, DuplicateIDs) {
+ const std::vector<std::vector<std::shared_ptr<Tile>>> sources = {
+ {
+ std::make_shared<Tile>(TileID { 2, 0, 0, 2 }),
+ std::make_shared<Tile>(TileID { 2, 0, 1, 2 }),
+ },
+ {
+ std::make_shared<Tile>(TileID { 2, 0, 0, 2 }),
+ std::make_shared<Tile>(TileID { 2, 0, 1, 2 }),
+ std::make_shared<Tile>(TileID { 2, 0, 1, 2 }),
+ }
+ };
+
+ generate(sources);
+ // print(sources);
+
+ ASSERT_EQ(ClipID("00000011", "00000001"), sources[0][0]->clip);
+ ASSERT_EQ(ClipID("00000011", "00000010"), sources[0][1]->clip);
+ ASSERT_EQ(ClipID("00000011", "00000001"), sources[1][0]->clip);
+ ASSERT_EQ(ClipID("00000011", "00000010"), sources[1][1]->clip);
+ ASSERT_EQ(ClipID("00000011", "00000010"), sources[1][2]->clip);
+}
diff --git a/test/util/geo.cpp b/test/util/geo.cpp
new file mode 100644
index 0000000000..88f8bc496b
--- /dev/null
+++ b/test/util/geo.cpp
@@ -0,0 +1,61 @@
+#include "../fixtures/util.hpp"
+
+#include <mbgl/util/geo.hpp>
+#include <mbgl/map/tile_id.hpp>
+
+using namespace mbgl;
+
+TEST(Geo, LatLngFromTileID) {
+ for (int i = 0; i < 20; i++) {
+ const LatLng ll{ TileID(i, 0, 0, 0) };
+ ASSERT_DOUBLE_EQ(-180, ll.longitude);
+ ASSERT_DOUBLE_EQ(85.051128779806604, ll.latitude);
+ }
+
+ {
+ const LatLng ll{ TileID(0, 1, 0, 0) };
+ ASSERT_DOUBLE_EQ(180, ll.longitude);
+ ASSERT_DOUBLE_EQ(85.051128779806604, ll.latitude);
+ }
+
+ {
+ const LatLng ll{ TileID(0, -1, 0, 0) };
+ ASSERT_DOUBLE_EQ(-540, ll.longitude);
+ ASSERT_DOUBLE_EQ(85.051128779806604, ll.latitude);
+ }
+}
+
+
+TEST(Geo, LatLngBoundsFromTileID) {
+ {
+ const LatLngBounds bounds{ TileID(0, 0, 0, 0) };
+ ASSERT_DOUBLE_EQ(-180, bounds.sw.longitude);
+ ASSERT_DOUBLE_EQ(-85.051128779806604, bounds.sw.latitude);
+ ASSERT_DOUBLE_EQ(180, bounds.ne.longitude);
+ ASSERT_DOUBLE_EQ(85.051128779806604, bounds.ne.latitude);
+ }
+
+ {
+ const LatLngBounds bounds{ TileID(1, 0, 1, 0) };
+ ASSERT_DOUBLE_EQ(-180, bounds.sw.longitude);
+ ASSERT_DOUBLE_EQ(-85.051128779806604, bounds.sw.latitude);
+ ASSERT_DOUBLE_EQ(0, bounds.ne.longitude);
+ ASSERT_DOUBLE_EQ(0, bounds.ne.latitude);
+ }
+
+ {
+ const LatLngBounds bounds{ TileID(1, 1, 1, 0) };
+ ASSERT_DOUBLE_EQ(0, bounds.sw.longitude);
+ ASSERT_DOUBLE_EQ(-85.051128779806604, bounds.sw.latitude);
+ ASSERT_DOUBLE_EQ(180, bounds.ne.longitude);
+ ASSERT_DOUBLE_EQ(0, bounds.ne.latitude);
+ }
+
+ {
+ const LatLngBounds bounds{ TileID(1, 0, 0, 0) };
+ ASSERT_DOUBLE_EQ(-180, bounds.sw.longitude);
+ ASSERT_DOUBLE_EQ(0, bounds.sw.latitude);
+ ASSERT_DOUBLE_EQ(0, bounds.ne.longitude);
+ ASSERT_DOUBLE_EQ(85.051128779806604, bounds.ne.latitude);
+ }
+}
diff --git a/test/util/image.cpp b/test/util/image.cpp
new file mode 100644
index 0000000000..9886eede45
--- /dev/null
+++ b/test/util/image.cpp
@@ -0,0 +1,101 @@
+#include "../fixtures/util.hpp"
+
+#include <mbgl/util/premultiply.hpp>
+#include <mbgl/util/image.hpp>
+#include <mbgl/util/io.hpp>
+
+using namespace mbgl;
+
+TEST(Image, PNGRoundTrip) {
+ PremultipliedImage rgba { 1, 1 };
+ rgba.data[0] = 128;
+ rgba.data[1] = 0;
+ rgba.data[2] = 0;
+ rgba.data[3] = 255;
+
+ PremultipliedImage image = decodeImage(encodePNG(rgba));
+ EXPECT_EQ(128, image.data[0]);
+ EXPECT_EQ(0, image.data[1]);
+ EXPECT_EQ(0, image.data[2]);
+ EXPECT_EQ(255, image.data[3]);
+}
+
+TEST(Image, PNGRoundTripAlpha) {
+ PremultipliedImage rgba { 1, 1 };
+ rgba.data[0] = 128;
+ rgba.data[1] = 0;
+ rgba.data[2] = 0;
+ rgba.data[3] = 128;
+
+ PremultipliedImage image = decodeImage(encodePNG(rgba));
+ EXPECT_EQ(128, image.data[0]);
+ EXPECT_EQ(0, image.data[1]);
+ EXPECT_EQ(0, image.data[2]);
+ EXPECT_EQ(128, image.data[3]);
+}
+
+TEST(Image, PNGReadNoProfile) {
+ PremultipliedImage image = decodeImage(util::read_file("test/fixtures/image/no_profile.png"));
+ EXPECT_EQ(128, image.data[0]);
+ EXPECT_EQ(0, image.data[1]);
+ EXPECT_EQ(0, image.data[2]);
+ EXPECT_EQ(255, image.data[3]);
+}
+
+TEST(Image, PNGReadNoProfileAlpha) {
+ PremultipliedImage image = decodeImage(util::read_file("test/fixtures/image/no_profile_alpha.png"));
+ EXPECT_EQ(64, image.data[0]);
+ EXPECT_EQ(0, image.data[1]);
+ EXPECT_EQ(0, image.data[2]);
+ EXPECT_EQ(128, image.data[3]);
+}
+
+TEST(Image, PNGReadProfile) {
+ PremultipliedImage image = decodeImage(util::read_file("test/fixtures/image/profile.png"));
+ EXPECT_EQ(128, image.data[0]);
+ EXPECT_EQ(0, image.data[1]);
+ EXPECT_EQ(0, image.data[2]);
+ EXPECT_EQ(255, image.data[3]);
+}
+
+TEST(Image, PNGReadProfileAlpha) {
+ PremultipliedImage image = decodeImage(util::read_file("test/fixtures/image/profile_alpha.png"));
+ EXPECT_EQ(64, image.data[0]);
+ EXPECT_EQ(0, image.data[1]);
+ EXPECT_EQ(0, image.data[2]);
+ EXPECT_EQ(128, image.data[3]);
+}
+
+TEST(Image, PNGTile) {
+ PremultipliedImage image = decodeImage(util::read_file("test/fixtures/image/tile.png"));
+ EXPECT_EQ(256, image.width);
+ EXPECT_EQ(256, image.height);
+}
+
+TEST(Image, JPEGTile) {
+ PremultipliedImage image = decodeImage(util::read_file("test/fixtures/image/tile.jpeg"));
+ EXPECT_EQ(256, image.width);
+ EXPECT_EQ(256, image.height);
+}
+
+#if !defined(__ANDROID__) && !defined(__APPLE__)
+TEST(Image, WebPTile) {
+ PremultipliedImage image = decodeImage(util::read_file("test/fixtures/image/tile.webp"));
+ EXPECT_EQ(256, image.width);
+ EXPECT_EQ(256, image.height);
+}
+#endif // !defined(__ANDROID__) && !defined(__APPLE__)
+
+TEST(Image, Premultiply) {
+ UnassociatedImage rgba { 1, 1 };
+ rgba.data[0] = 255;
+ rgba.data[1] = 254;
+ rgba.data[2] = 253;
+ rgba.data[3] = 128;
+
+ PremultipliedImage image = util::premultiply(std::move(rgba));
+ EXPECT_EQ(128, image.data[0]);
+ EXPECT_EQ(127, image.data[1]);
+ EXPECT_EQ(127, image.data[2]);
+ EXPECT_EQ(128, image.data[3]);
+}
diff --git a/test/util/mapbox.cpp b/test/util/mapbox.cpp
new file mode 100644
index 0000000000..d6f9948e66
--- /dev/null
+++ b/test/util/mapbox.cpp
@@ -0,0 +1,145 @@
+#include "../fixtures/util.hpp"
+
+#include <mbgl/platform/log.hpp>
+#include <mbgl/util/mapbox.hpp>
+#include <regex>
+#include <iostream>
+
+using namespace mbgl;
+
+TEST(Mapbox, SourceURL) {
+ EXPECT_EQ(mbgl::util::mapbox::normalizeSourceURL("mapbox://user.map", "key"), "https://api.mapbox.com/v4/user.map.json?access_token=key&secure");
+ EXPECT_EQ(mbgl::util::mapbox::normalizeSourceURL("mapbox://user.map", "token"), "https://api.mapbox.com/v4/user.map.json?access_token=token&secure");
+ EXPECT_THROW(mbgl::util::mapbox::normalizeSourceURL("mapbox://user.map", ""), std::runtime_error);
+}
+
+TEST(Mapbox, GlyphsURL) {
+ EXPECT_EQ(mbgl::util::mapbox::normalizeGlyphsURL("mapbox://fonts/boxmap/Comic%20Sans/0-255.pbf", "key"), "https://api.mapbox.com/fonts/v1/boxmap/Comic%20Sans/0-255.pbf?access_token=key");
+ EXPECT_EQ(mbgl::util::mapbox::normalizeGlyphsURL("mapbox://fonts/boxmap/{fontstack}/{range}.pbf", "key"), "https://api.mapbox.com/fonts/v1/boxmap/{fontstack}/{range}.pbf?access_token=key");
+ EXPECT_EQ(mbgl::util::mapbox::normalizeGlyphsURL("http://path", "key"), "http://path");
+ EXPECT_EQ(mbgl::util::mapbox::normalizeGlyphsURL("mapbox://path", "key"), "mapbox://path");
+}
+
+TEST(Mapbox, StyleURL) {
+ EXPECT_EQ(mbgl::util::mapbox::normalizeStyleURL("mapbox://foo", "key"), "mapbox://foo");
+ EXPECT_EQ(mbgl::util::mapbox::normalizeStyleURL("mapbox://styles/user/style", "key"), "https://api.mapbox.com/styles/v1/user/style?access_token=key");
+ EXPECT_EQ(mbgl::util::mapbox::normalizeStyleURL("mapbox://styles/user/style/draft", "key"), "https://api.mapbox.com/styles/v1/user/style/draft?access_token=key");
+ EXPECT_EQ(mbgl::util::mapbox::normalizeStyleURL("http://path", "key"), "http://path");
+}
+
+TEST(Mapbox, SpriteURL) {
+ EXPECT_EQ(mbgl::util::mapbox::normalizeSpriteURL("map/box/sprites@2x.json", "key"), "map/box/sprites@2x.json");
+ EXPECT_EQ(mbgl::util::mapbox::normalizeSpriteURL("mapbox://foo", "key"), "mapbox://foo");
+ EXPECT_EQ(mbgl::util::mapbox::normalizeSpriteURL("mapbox://sprites/mapbox/streets-v8.json", "key"), "https://api.mapbox.com/styles/v1/mapbox/streets-v8/sprite.json?access_token=key");
+ EXPECT_EQ(mbgl::util::mapbox::normalizeSpriteURL("mapbox://sprites/mapbox/streets-v8@2x.png", "key"), "https://api.mapbox.com/styles/v1/mapbox/streets-v8/sprite@2x.png?access_token=key");
+ EXPECT_EQ(mbgl::util::mapbox::normalizeSpriteURL("mapbox://sprites/mapbox/streets-v8/draft@2x.png", "key"), "https://api.mapbox.com/styles/v1/mapbox/streets-v8/draft/sprite@2x.png?access_token=key");
+}
+
+TEST(Mapbox, TileURL) {
+ try {
+#if defined(__ANDROID__) || defined(__APPLE__)
+ EXPECT_EQ("http://path.png/tile{ratio}.png", mbgl::util::mapbox::normalizeRasterTileURL("http://path.png/tile.png"));
+ EXPECT_EQ("http://path.png/tile{ratio}.png32", mbgl::util::mapbox::normalizeRasterTileURL("http://path.png/tile.png32"));
+ EXPECT_EQ("http://path.png/tile{ratio}.png70", mbgl::util::mapbox::normalizeRasterTileURL("http://path.png/tile.png70"));
+ EXPECT_EQ("http://path.png/tile{ratio}.png?access_token=foo", mbgl::util::mapbox::normalizeRasterTileURL("http://path.png/tile.png?access_token=foo"));
+#else
+ EXPECT_EQ("http://path.png/tile{ratio}.webp", mbgl::util::mapbox::normalizeRasterTileURL("http://path.png/tile.png"));
+ EXPECT_EQ("http://path.png/tile{ratio}.webp32", mbgl::util::mapbox::normalizeRasterTileURL("http://path.png/tile.png32"));
+ EXPECT_EQ("http://path.png/tile{ratio}.webp70", mbgl::util::mapbox::normalizeRasterTileURL("http://path.png/tile.png70"));
+ EXPECT_EQ("http://path.png/tile{ratio}.webp?access_token=foo", mbgl::util::mapbox::normalizeRasterTileURL("http://path.png/tile.png?access_token=foo"));
+#endif // defined(__ANDROID__) || defined(__APPLE__)
+ EXPECT_EQ("http://path.png/tile{ratio}.pbf", mbgl::util::mapbox::normalizeRasterTileURL("http://path.png/tile.pbf"));
+ EXPECT_EQ("http://path.png/tile{ratio}.pbf?access_token=foo", mbgl::util::mapbox::normalizeRasterTileURL("http://path.png/tile.pbf?access_token=foo"));
+ EXPECT_EQ("http://path.png/tile{ratio}.pbf?access_token=foo.png", mbgl::util::mapbox::normalizeRasterTileURL("http://path.png/tile.pbf?access_token=foo.png"));
+ EXPECT_EQ("http://path.png/tile{ratio}.pbf?access_token=foo.png/bar", mbgl::util::mapbox::normalizeRasterTileURL("http://path.png/tile.pbf?access_token=foo.png/bar"));
+ EXPECT_EQ("http://path.png/tile{ratio}.pbf?access_token=foo.png/bar.png", mbgl::util::mapbox::normalizeRasterTileURL("http://path.png/tile.pbf?access_token=foo.png/bar.png"));
+ } catch (const std::regex_error& e) {
+ const char *error = "unknown";
+ switch (e.code()) {
+ case std::regex_constants::error_collate:
+ error = "error_collate"; break;
+ case std::regex_constants::error_ctype:
+ error = "error_ctype"; break;
+ case std::regex_constants::error_escape:
+ error = "error_escape"; break;
+ case std::regex_constants::error_backref:
+ error = "error_backref"; break;
+ case std::regex_constants::error_paren:
+ error = "error_paren"; break;
+ case std::regex_constants::error_brace:
+ error = "error_brace"; break;
+ case std::regex_constants::error_badbrace:
+ error = "error_badbrace"; break;
+ case std::regex_constants::error_range:
+ error = "error_range"; break;
+ case std::regex_constants::error_space:
+ error = "error_space"; break;
+ case std::regex_constants::error_badrepeat:
+ error = "error_badrepeat"; break;
+ case std::regex_constants::error_complexity:
+ error = "error_complexity"; break;
+ case std::regex_constants::error_stack:
+ error = "error_stack"; break;
+ default:
+ break;
+ }
+ mbgl::Log::Error(mbgl::Event::General, "regex_error caught: %s - %s (%d)", e.what(), error, e.code());
+ throw e;
+ }
+}
+
+TEST(Mapbox, CanonicalURL) {
+ using mbgl::util::mapbox::canonicalURL;
+ EXPECT_EQ(
+ canonicalURL("https://a.tiles.mapbox.com/v4/"
+ "mapbox.mapbox-terrain-v2,mapbox.mapbox-streets-v6/15/17599/"
+ "10744.vector.pbf?access_token=pk.kAeslEm93Sjf3mXk."
+ "vbiF02XnvkPkzlFhGSn2iIm6De3Cxsk5tmips2tvkG8sF"),
+ "mapbox://v4/mapbox.mapbox-terrain-v2,mapbox.mapbox-streets-v6/15/17599/10744.vector.pbf");
+
+ EXPECT_EQ(
+ canonicalURL("http://a.tiles.mapbox.com/v4/"
+ "mapbox.mapbox-terrain-v2,mapbox.mapbox-streets-v6/15/17599/"
+ "10744.vector.pbf?access_token=pk.kAeslEm93Sjf3mXk."
+ "vbiF02XnvkPkzlFhGSn2iIm6De3Cxsk5tmips2tvkG8sF"),
+ "mapbox://v4/mapbox.mapbox-terrain-v2,mapbox.mapbox-streets-v6/15/17599/10744.vector.pbf");
+
+ EXPECT_EQ(
+ canonicalURL("https://b.tiles.mapbox.com/v4/"
+ "mapbox.mapbox-terrain-v2,mapbox.mapbox-streets-v6/15/17599/"
+ "10744.vector.pbf?access_token=pk.kAeslEm93Sjf3mXk."
+ "vbiF02XnvkPkzlFhGSn2iIm6De3Cxsk5tmips2tvkG8sF"),
+ "mapbox://v4/mapbox.mapbox-terrain-v2,mapbox.mapbox-streets-v6/15/17599/10744.vector.pbf");
+
+ EXPECT_EQ(
+ canonicalURL("http://c.tiles.mapbox.com/v4/"
+ "mapbox.mapbox-terrain-v2,mapbox.mapbox-streets-v6/15/17599/"
+ "10744.vector.pbf?access_token=pk.kAeslEm93Sjf3mXk."
+ "vbiF02XnvkPkzlFhGSn2iIm6De3Cxsk5tmips2tvkG8sF"),
+ "mapbox://v4/mapbox.mapbox-terrain-v2,mapbox.mapbox-streets-v6/15/17599/10744.vector.pbf");
+
+ EXPECT_EQ(
+ canonicalURL("https://api.mapbox.com/v4/"
+ "mapbox.mapbox-terrain-v2,mapbox.mapbox-streets-v6/15/17599/"
+ "10744.vector.pbf?access_token=pk.kAeslEm93Sjf3mXk."
+ "vbiF02XnvkPkzlFhGSn2iIm6De3Cxsk5tmips2tvkG8sF"),
+ "mapbox://v4/mapbox.mapbox-terrain-v2,mapbox.mapbox-streets-v6/15/17599/10744.vector.pbf");
+
+ EXPECT_EQ(
+ canonicalURL("http://api.mapbox.com/v4/"
+ "mapbox.mapbox-terrain-v2,mapbox.mapbox-streets-v6/15/17599/"
+ "10744.vector.pbf"),
+ "mapbox://v4/mapbox.mapbox-terrain-v2,mapbox.mapbox-streets-v6/15/17599/10744.vector.pbf");
+
+ EXPECT_EQ(canonicalURL("https://api.mapbox.com/fonts/v1/mapbox/"
+ "DIN%20Offc%20Pro%20Italic%2cArial%20Unicode%20MS%20Regular/"
+ "0-255.pbf?access_token=pk.kAeslEm93Sjf3mXk."
+ "vbiF02XnvkPkzlFhGSn2iIm6De3Cxsk5tmips2tvkG8sF"),
+ "mapbox://fonts/v1/mapbox/DIN%20Offc%20Pro%20Italic%2cArial%20Unicode%20MS%20Regular/"
+ "0-255.pbf");
+
+ EXPECT_EQ(canonicalURL("https://api.mapbox.com/styles/v1/mapbox/streets-v8/"
+ "sprite.json?access_token=pk.kAeslEm93Sjf3mXk."
+ "vbiF02XnvkPkzlFhGSn2iIm6De3Cxsk5tmips2tvkG8sF"),
+ "mapbox://styles/v1/mapbox/streets-v8/sprite.json");
+}
diff --git a/test/util/merge_lines.cpp b/test/util/merge_lines.cpp
new file mode 100644
index 0000000000..90625e9e0a
--- /dev/null
+++ b/test/util/merge_lines.cpp
@@ -0,0 +1,75 @@
+#include "../fixtures/util.hpp"
+
+#include <mbgl/util/merge_lines.hpp>
+
+const std::u32string aaa = U"a";
+const std::u32string bbb = U"b";
+
+TEST(MergeLines, SameText) {
+ // merges lines with the same text
+ std::vector<mbgl::SymbolFeature> input1 = {
+ { {{{0, 0}, {1, 0}, {2, 0}}}, aaa, "" },
+ { {{{4, 0}, {5, 0}, {6, 0}}}, bbb, "" },
+ { {{{8, 0}, {9, 0}}}, aaa, "" },
+ { {{{2, 0}, {3, 0}, {4, 0}}}, aaa, "" },
+ { {{{6, 0}, {7, 0}, {8, 0}}}, aaa, "" },
+ { {{{5, 0}, {6, 0}}}, aaa, "" }
+ };
+
+ const std::vector<mbgl::SymbolFeature> expected1 = {
+ { {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}}}, aaa, "" },
+ { {{{4, 0}, {5, 0}, {6, 0}}}, bbb, "" },
+ { {{{5, 0}, {6, 0}, {7, 0}, {8, 0}, {9, 0}}}, aaa, "" },
+ { {{}}, aaa, "" },
+ { {{}}, aaa, "" },
+ { {{}}, aaa, "" }
+ };
+
+ mbgl::util::mergeLines(input1);
+
+ for (int i = 0; i < 6; i++) {
+ EXPECT_EQ(input1[i].geometry, expected1[i].geometry);
+ }
+}
+
+TEST(MergeLines, BothEnds) {
+ // mergeLines handles merge from both ends
+ std::vector<mbgl::SymbolFeature> input2 = {
+ { {{{0, 0}, {1, 0}, {2, 0}}}, aaa, "" },
+ { {{{4, 0}, {5, 0}, {6, 0}}}, aaa, "" },
+ { {{{2, 0}, {3, 0}, {4, 0}}}, aaa, "" }
+ };
+
+ const std::vector<mbgl::SymbolFeature> expected2 = {
+ { {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}}}, aaa, "" },
+ { {{}}, aaa, "" },
+ { {{}}, aaa, "" }
+ };
+
+ mbgl::util::mergeLines(input2);
+
+ for (int i = 0; i < 3; i++) {
+ EXPECT_EQ(input2[i].geometry, expected2[i].geometry);
+ }
+}
+
+TEST(MergeLines, CircularLines) {
+ // mergeLines handles circular lines
+ std::vector<mbgl::SymbolFeature> input3 = {
+ { {{{0, 0}, {1, 0}, {2, 0}}}, aaa, "" },
+ { {{{2, 0}, {3, 0}, {4, 0}}}, aaa, "" },
+ { {{{4, 0}, {0, 0}}}, aaa, "" }
+ };
+
+ const std::vector<mbgl::SymbolFeature> expected3 = {
+ { {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {0, 0}}}, aaa, "" },
+ { {{}}, aaa, "" },
+ { {{}}, aaa, "" }
+ };
+
+ mbgl::util::mergeLines(input3);
+
+ for (int i = 0; i < 3; i++) {
+ EXPECT_EQ(input3[i].geometry, expected3[i].geometry);
+ }
+}
diff --git a/test/util/run_loop.cpp b/test/util/run_loop.cpp
new file mode 100644
index 0000000000..f00f7248b5
--- /dev/null
+++ b/test/util/run_loop.cpp
@@ -0,0 +1,75 @@
+#include <mbgl/util/run_loop.hpp>
+#include <mbgl/util/timer.hpp>
+
+#include "../fixtures/util.hpp"
+
+using namespace mbgl::util;
+
+TEST(RunLoop, Stop) {
+ RunLoop loop(RunLoop::Type::New);
+
+ Timer timer;
+ timer.start(mbgl::Duration::zero(), mbgl::Duration::zero(), [&] {
+ loop.stop();
+ });
+
+ loop.run();
+}
+
+TEST(RunLoop, MultipleStop) {
+ RunLoop loop(RunLoop::Type::New);
+
+ Timer timer;
+ timer.start(mbgl::Duration::zero(), mbgl::Duration::zero(), [&] {
+ loop.stop();
+ loop.stop();
+ loop.stop();
+ loop.stop();
+ });
+
+ loop.run();
+}
+
+TEST(RunLoop, UnrefShouldStop) {
+ RunLoop loop(RunLoop::Type::New);
+
+ Timer timer;
+ timer.start(mbgl::Duration::zero(), mbgl::Duration::zero(), [&] {
+ loop.unref();
+ });
+
+ loop.run();
+}
+
+TEST(RunLoop, RefUnref) {
+ RunLoop loop(RunLoop::Type::New);
+
+ Timer timer;
+ auto zero = mbgl::Duration::zero();
+
+ auto cb3 = [&] {
+ loop.stop();
+ };
+
+ auto cb2 = [&] {
+ loop.unref();
+ loop.unref();
+
+ loop.ref();
+
+ timer.start(zero, zero, cb3);
+ };
+
+ auto cb1 = [&] {
+ loop.ref();
+ loop.ref();
+
+ loop.unref();
+
+ timer.start(zero, zero, cb2);
+ };
+
+ timer.start(zero, zero, cb1);
+
+ loop.run();
+}
diff --git a/test/util/text_conversions.cpp b/test/util/text_conversions.cpp
new file mode 100644
index 0000000000..78d88ed12e
--- /dev/null
+++ b/test/util/text_conversions.cpp
@@ -0,0 +1,35 @@
+#include <iostream>
+#include "../fixtures/util.hpp"
+
+#include <mbgl/util/utf.hpp>
+#include <mbgl/platform/platform.hpp>
+
+using namespace mbgl;
+
+TEST(TextConversions, to_upper) {
+ EXPECT_EQ(std::string("STREET"), platform::uppercase("strEEt")); // EN
+ EXPECT_EQ(std::string("ROAD"), platform::uppercase("rOAd")); // EN
+
+ EXPECT_EQ(std::string("STRASSE"), platform::uppercase("straße")); // DE
+ EXPECT_EQ(std::string("MASSE"), platform::uppercase("maße")); // DE
+ EXPECT_EQ(std::string("WEISSKOPFSEEADLER"), platform::uppercase("weißkopfseeadler")); // DE
+
+ EXPECT_EQ(std::string("AZƏRBAYCAN"), platform::uppercase("Azərbaycan")); // AZ
+
+ EXPECT_EQ(std::string("ὈΔΥΣΣΕΎΣ"), platform::uppercase("Ὀδυσσεύς")); // GR
+}
+
+
+TEST(TextConversions, to_lower) {
+ EXPECT_EQ(std::string("street"), platform::lowercase("strEEt")); // EN
+ EXPECT_EQ(std::string("road"), platform::lowercase("rOAd")); // EN
+
+ EXPECT_EQ(std::string("straße"), platform::lowercase("Straße")); // DE
+ EXPECT_EQ(std::string("strasse"), platform::lowercase("STRASSE")); // DE
+ EXPECT_EQ(std::string("masse"), platform::lowercase("MASSE")); // DE
+ EXPECT_EQ(std::string("weisskopfseeadler"), platform::lowercase("weiSSkopfseeadler")); // DE
+
+ EXPECT_EQ(std::string("azərbaycan"), platform::lowercase("AZƏRBAYCAN")); // AZ
+ EXPECT_EQ(std::string("ὀδυσσεύς"), platform::lowercase("ὈΔΥΣΣΕΎΣ")); // GR
+
+}
diff --git a/test/util/thread.cpp b/test/util/thread.cpp
new file mode 100644
index 0000000000..f60d7d43dd
--- /dev/null
+++ b/test/util/thread.cpp
@@ -0,0 +1,226 @@
+#include <mbgl/util/thread.hpp>
+#include <mbgl/util/run_loop.hpp>
+
+#include "../fixtures/util.hpp"
+
+using namespace mbgl::util;
+
+class TestObject {
+public:
+ TestObject(std::thread::id otherTid)
+ : tid(std::this_thread::get_id()) {
+ EXPECT_NE(tid, otherTid);
+ }
+
+ void fn1(int val) {
+ EXPECT_EQ(tid, std::this_thread::get_id());
+ EXPECT_EQ(val, 1);
+ }
+
+ void fn2(std::function<void (int)> cb) {
+ 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);
+ }
+
+ void transferOut(std::function<void (std::unique_ptr<int>)> cb) {
+ EXPECT_EQ(tid, std::this_thread::get_id());
+ cb(std::make_unique<int>(1));
+ }
+
+ void transferInOut(std::unique_ptr<int> val, std::function<void (std::unique_ptr<int>)> cb) {
+ EXPECT_EQ(tid, std::this_thread::get_id());
+ EXPECT_EQ(*val, 1);
+ cb(std::move(val));
+ }
+
+ void transferInShared(std::shared_ptr<int> val) {
+ EXPECT_EQ(tid, std::this_thread::get_id());
+ EXPECT_EQ(*val, 1);
+ }
+
+ void transferOutShared(std::function<void (std::shared_ptr<int>)> cb) {
+ EXPECT_EQ(tid, std::this_thread::get_id());
+ cb(std::make_shared<int>(1));
+ }
+
+ 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::function<void (bool)> cb) const {
+ cb(ThreadContext::currentlyOn(ThreadType::Worker)
+ && ThreadContext::getName() == "Test"
+ && ThreadContext::getPriority() == ThreadPriority::Low);
+ }
+
+ const std::thread::id tid;
+};
+
+TEST(Thread, invoke) {
+ const std::thread::id tid = std::this_thread::get_id();
+
+ RunLoop loop;
+ std::vector<std::unique_ptr<mbgl::WorkRequest>> requests;
+
+ loop.invoke([&] {
+ EXPECT_EQ(tid, std::this_thread::get_id());
+ Thread<TestObject> thread({"Test", ThreadType::Map, ThreadPriority::Regular}, 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::unique_ptr<int> result) {
+ EXPECT_EQ(tid, std::this_thread::get_id());
+ EXPECT_EQ(*result, 1);
+ }, std::make_unique<int>(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");
+ requests.push_back(thread.invokeWithCallback(&TestObject::transferString, [&] (std::string result){
+ EXPECT_EQ(tid, std::this_thread::get_id());
+ EXPECT_EQ(result, "test");
+ loop.stop();
+ }, test));
+ test.clear();
+ });
+
+ loop.run();
+}
+
+TEST(Thread, context) {
+ bool isMainThreadContext = ThreadContext::currentlyOn(ThreadType::Main)
+ && ThreadContext::getName() == "Main"
+ && ThreadContext::getPriority() == ThreadPriority::Regular;
+
+ EXPECT_EQ(isMainThreadContext, true);
+
+ const std::thread::id tid = std::this_thread::get_id();
+
+ RunLoop loop;
+ std::vector<std::unique_ptr<mbgl::WorkRequest>> requests;
+
+ loop.invoke([&] {
+ Thread<TestObject> thread({"Test", ThreadType::Worker, ThreadPriority::Low}, tid);
+
+ requests.push_back(thread.invokeWithCallback(&TestObject::checkContext, [&] (bool inTestThreadContext) {
+ EXPECT_EQ(inTestThreadContext, true);
+ loop.stop();
+ }));
+ });
+
+ loop.run();
+}
+
+class TestWorker {
+public:
+ TestWorker() = default;
+
+ void send(std::function<void ()> fn, std::function<void ()> cb) {
+ fn();
+ cb();
+ }
+};
+
+TEST(Thread, ExecutesAfter) {
+ RunLoop loop;
+ Thread<TestWorker> thread({"Test", ThreadType::Map, ThreadPriority::Regular});
+
+ bool didWork = false;
+ bool didAfter = false;
+
+ auto request = thread.invokeWithCallback(&TestWorker::send, [&] {
+ didAfter = true;
+ loop.stop();
+ }, [&] {
+ didWork = true;
+ });
+
+ loop.run();
+
+ EXPECT_TRUE(didWork);
+ EXPECT_TRUE(didAfter);
+}
+
+TEST(Thread, WorkRequestDeletionWaitsForWorkToComplete) {
+ RunLoop loop;
+
+ Thread<TestWorker> thread({"Test", ThreadType::Map, ThreadPriority::Regular});
+
+ std::promise<void> started;
+ bool didWork = false;
+
+ auto request = thread.invokeWithCallback(&TestWorker::send, [&] {}, [&] {
+ started.set_value();
+ usleep(10000);
+ didWork = true;
+ });
+
+ started.get_future().get();
+ request.reset();
+ EXPECT_TRUE(didWork);
+}
+
+TEST(Thread, WorkRequestDeletionCancelsAfter) {
+ RunLoop loop;
+ Thread<TestWorker> thread({"Test", ThreadType::Map, ThreadPriority::Regular});
+
+ std::promise<void> started;
+ bool didAfter = false;
+
+ auto request = thread.invokeWithCallback(&TestWorker::send, [&] {
+ didAfter = true;
+ }, [&] {
+ started.set_value();
+ });
+
+ started.get_future().get();
+ request.reset();
+ loop.runOnce();
+ EXPECT_FALSE(didAfter);
+}
+
+TEST(Thread, WorkRequestDeletionCancelsImmediately) {
+ RunLoop loop;
+ Thread<TestWorker> thread({"Test", ThreadType::Map, ThreadPriority::Regular});
+
+ std::promise<void> started;
+
+ auto request1 = thread.invokeWithCallback(&TestWorker::send, [&] {}, [&] {
+ usleep(10000);
+ started.set_value();
+ });
+
+ auto request2 = thread.invokeWithCallback(&TestWorker::send, [&] {}, [&] {
+ ADD_FAILURE() << "Second work item should not be invoked";
+ });
+ request2.reset();
+
+ started.get_future().get();
+ request1.reset();
+}
diff --git a/test/util/thread_local.cpp b/test/util/thread_local.cpp
new file mode 100644
index 0000000000..aeaf187540
--- /dev/null
+++ b/test/util/thread_local.cpp
@@ -0,0 +1,95 @@
+#include <mbgl/util/run_loop.hpp>
+#include <mbgl/util/thread.hpp>
+#include <mbgl/util/thread_local.hpp>
+
+#include "../fixtures/util.hpp"
+
+using namespace mbgl::util;
+
+namespace {
+
+class TestThread {
+public:
+ TestThread(int *number_) {
+ number.set(number_);
+ }
+
+ ~TestThread() {
+ number.set(nullptr);
+ }
+
+ int getNumber() {
+ return *number.get();
+ }
+
+private:
+ static ThreadLocal<int> number;
+};
+
+ThreadLocal<int> TestThread::number;
+
+} // namespace
+
+TEST(ThreadLocalStorage, Basic) {
+ RunLoop loop;
+
+ int number1 = 1;
+ int number2 = 2;
+ int number3 = 3;
+
+ ThreadContext context = {"Test", ThreadType::Map, ThreadPriority::Regular};
+
+ Thread<TestThread> thread1(context, &number1);
+ Thread<TestThread> thread2(context, &number2);
+ Thread<TestThread> thread3(context, &number3);
+
+ EXPECT_EQ(number1, thread1.invokeSync<int>(&TestThread::getNumber));
+ EXPECT_EQ(number2, thread2.invokeSync<int>(&TestThread::getNumber));
+ EXPECT_EQ(number3, thread3.invokeSync<int>(&TestThread::getNumber));
+}
+
+TEST(ThreadLocalStorage, NotSetReturnsNull) {
+ static ThreadLocal<int> number;
+
+ EXPECT_EQ(nullptr, number.get());
+}
+
+namespace {
+
+struct DtorCounter {
+ ~DtorCounter() { ++(*value); }
+ unsigned *value;
+};
+
+class TestThreadReclaim {
+public:
+ TestThreadReclaim(DtorCounter* counter_) {
+ counter.set(counter_);
+ }
+
+private:
+ static ThreadLocal<DtorCounter> counter;
+};
+
+ThreadLocal<DtorCounter> TestThreadReclaim::counter;
+
+} // namespace
+
+TEST(ThreadLocalStorage, AutoReclaim) {
+ RunLoop loop;
+
+ unsigned counter = 0;
+
+ DtorCounter* dtorCounter1 = new DtorCounter{ &counter };
+ DtorCounter* dtorCounter2 = new DtorCounter{ &counter };
+
+ ThreadContext context = {"Test", ThreadType::Map, ThreadPriority::Regular};
+
+ auto thread1 = std::make_unique<Thread<TestThreadReclaim>>(context, dtorCounter1);
+ auto thread2 = std::make_unique<Thread<TestThreadReclaim>>(context, dtorCounter2);
+
+ thread1.reset();
+ thread2.reset();
+
+ EXPECT_EQ(counter, 2);
+}
diff --git a/test/util/timer.cpp b/test/util/timer.cpp
new file mode 100644
index 0000000000..1ac72d8068
--- /dev/null
+++ b/test/util/timer.cpp
@@ -0,0 +1,173 @@
+#include <mbgl/util/chrono.hpp>
+#include <mbgl/util/timer.hpp>
+#include <mbgl/util/run_loop.hpp>
+#include <mbgl/util/chrono.hpp>
+
+#include <memory>
+
+#include "../fixtures/util.hpp"
+
+using namespace mbgl::util;
+
+TEST(Timer, Basic) {
+ RunLoop loop;
+
+ Timer timer;
+
+ auto callback = [&loop] { loop.stop(); };
+
+ auto interval = mbgl::Milliseconds(300);
+ auto expectedTotalTime = interval;
+
+ auto first = mbgl::Clock::now();
+ timer.start(interval, mbgl::Duration::zero(), callback);
+
+ loop.run();
+
+ auto totalTime = std::chrono::duration_cast<mbgl::Milliseconds>(mbgl::Clock::now() - first);
+
+ // These are not high precision timers. Especially libuv uses
+ // cached time from the beginning of of the main loop iteration
+ // and it is very prone to fire earlier, which is, odd.
+ EXPECT_GE(totalTime, expectedTotalTime * 0.8);
+ EXPECT_LE(totalTime, expectedTotalTime * 1.2);
+}
+
+TEST(Timer, Repeat) {
+ RunLoop loop;
+
+ Timer timer;
+
+ unsigned count = 10;
+ auto callback = [&] {
+ if (!--count) {
+ loop.stop();
+ }
+ };
+
+ auto interval = mbgl::Milliseconds(50);
+ auto expectedTotalTime = interval * count;
+
+ auto first = mbgl::Clock::now();
+ timer.start(interval, interval, callback);
+
+ loop.run();
+
+ auto totalTime = std::chrono::duration_cast<mbgl::Milliseconds>(mbgl::Clock::now() - first);
+
+ EXPECT_GE(totalTime, expectedTotalTime * 0.8);
+ EXPECT_LE(totalTime, expectedTotalTime * 1.2);
+}
+
+TEST(Timer, Stop) {
+ RunLoop loop;
+
+ Timer timer1;
+ Timer timer2;
+
+ auto interval1 = mbgl::Milliseconds(50);
+ auto interval2 = mbgl::Milliseconds(250);
+ auto expectedTotalTime = interval2;
+
+ int count = 0;
+
+ auto callback1 = [&] {
+ ++count;
+ timer1.stop();
+ };
+
+ auto callback2 = [&] {
+ ++count;
+ loop.stop();
+ };
+
+ auto first = mbgl::Clock::now();
+ timer1.start(interval1, interval1, callback1);
+ timer2.start(interval2, mbgl::Duration::zero(), callback2);
+
+ loop.run();
+
+ auto totalTime = std::chrono::duration_cast<mbgl::Milliseconds>(mbgl::Clock::now() - first);
+
+ EXPECT_EQ(count, 2);
+
+ EXPECT_GE(totalTime, expectedTotalTime * 0.8);
+ EXPECT_LE(totalTime, expectedTotalTime * 1.2);
+}
+
+TEST(Timer, DestroyShouldStop) {
+ RunLoop loop;
+
+ auto timer1 = std::make_unique<Timer>();
+ Timer timer2;
+
+ auto interval1 = mbgl::Milliseconds(50);
+ auto interval2 = mbgl::Milliseconds(250);
+ auto expectedTotalTime = interval2;
+
+ int count = 0;
+
+ auto callback1 = [&] {
+ ++count;
+ timer1.reset();
+ };
+
+ auto callback2 = [&] {
+ ++count;
+ loop.stop();
+ };
+
+ auto first = mbgl::Clock::now();
+ timer1->start(interval1, interval1, callback1);
+ timer2.start(interval2, mbgl::Duration::zero(), callback2);
+
+ loop.run();
+
+ auto totalTime = std::chrono::duration_cast<mbgl::Milliseconds>(mbgl::Clock::now() - first);
+
+ EXPECT_EQ(count, 2);
+
+ EXPECT_GE(totalTime, expectedTotalTime * 0.8);
+ EXPECT_LE(totalTime, expectedTotalTime * 1.2);
+}
+
+TEST(Timer, StartOverrides) {
+ RunLoop loop;
+
+ Timer timer;
+
+ auto interval1 = mbgl::Milliseconds(50);
+ auto interval2 = mbgl::Milliseconds(250);
+ auto expectedTotalTime = interval1 + interval2;
+
+ int count = 0;
+
+ auto callback2 = [&] {
+ ++count;
+ loop.stop();
+ };
+
+ auto callback1 = [&] {
+ ++count;
+ timer.start(interval2, mbgl::Duration::zero(), callback2);
+ };
+
+ auto first = mbgl::Clock::now();
+ timer.start(interval1, mbgl::Duration::zero(), callback1);
+
+ loop.run();
+
+ auto totalTime = std::chrono::duration_cast<mbgl::Milliseconds>(mbgl::Clock::now() - first);
+
+ EXPECT_EQ(count, 2);
+
+ EXPECT_GE(totalTime, expectedTotalTime * 0.8);
+ EXPECT_LE(totalTime, expectedTotalTime * 1.2);
+}
+
+TEST(Timer, CanStopNonStartedTimer) {
+ RunLoop loop;
+
+ Timer timer;
+ timer.stop();
+}
diff --git a/test/util/token.cpp b/test/util/token.cpp
new file mode 100644
index 0000000000..add31afbad
--- /dev/null
+++ b/test/util/token.cpp
@@ -0,0 +1,50 @@
+#include <iostream>
+#include "../fixtures/util.hpp"
+
+#include <mbgl/util/token.hpp>
+
+using namespace mbgl;
+
+TEST(Token, replaceTokens) {
+ EXPECT_EQ("literal", mbgl::util::replaceTokens("literal", [](const std::string& token) -> std::string {
+ if (token == "name") return "14th St NW";
+ return "";
+ }));
+ EXPECT_EQ("14th St NW", mbgl::util::replaceTokens("{name}", [](const std::string& token) -> std::string {
+ if (token == "name") return "14th St NW";
+ return "";
+ }));
+ EXPECT_EQ("", mbgl::util::replaceTokens("{name}", [](const std::string& token) -> std::string {
+ if (token == "text") return "14th St NW";
+ return "";
+ }));
+ EXPECT_EQ("1400", mbgl::util::replaceTokens("{num}", [](const std::string& token) -> std::string {
+ if (token == "num") return "1400";
+ return "";
+ }));
+ EXPECT_EQ("500 m", mbgl::util::replaceTokens("{num} m", [](const std::string& token) -> std::string {
+ if (token == "num") return "500";
+ return "";
+ }));
+ EXPECT_EQ("3 Fine Fields", mbgl::util::replaceTokens("{a} {b} {c}", [](const std::string& token) -> std::string {
+ if (token == "a") return "3";
+ if (token == "b") return "Fine";
+ if (token == "c") return "Fields";
+ return "";
+ }));
+ EXPECT_EQ(" but still", mbgl::util::replaceTokens("{notset} but still", [](const std::string&) -> std::string {
+ return "";
+ }));
+ EXPECT_EQ("dashed", mbgl::util::replaceTokens("{dashed-property}", [](const std::string& token) -> std::string {
+ if (token == "dashed-property") return "dashed";
+ return "";
+ }));
+ EXPECT_EQ("150 m", mbgl::util::replaceTokens("{HØYDE} m", [](const std::string& token) -> std::string {
+ if (token == "HØYDE") return "150";
+ return "";
+ }));
+ EXPECT_EQ("reserved {for:future} use", mbgl::util::replaceTokens("reserved {for:future} use", [](const std::string& token) -> std::string {
+ if (token == "for:future") return "unknown";
+ return "";
+ }));
+}
diff --git a/test/util/work_queue.cpp b/test/util/work_queue.cpp
new file mode 100644
index 0000000000..a6cd6c3f88
--- /dev/null
+++ b/test/util/work_queue.cpp
@@ -0,0 +1,63 @@
+#include "../fixtures/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) {
+ EXPECT_TRUE(ThreadContext::currentlyOn(ThreadType::Map));
+
+ queue->push(std::move(fn));
+ }
+
+private:
+ WorkQueue* queue;
+};
+
+TEST(WorkQueue, push) {
+ RunLoop loop;
+
+ WorkQueue queue;
+ Thread<TestThread> thread({"Test", ThreadType::Map, ThreadPriority::Regular}, &queue);
+
+ uint8_t count = 0;
+
+ auto endTest = [&]() {
+ EXPECT_TRUE(ThreadContext::currentlyOn(ThreadType::Main));
+
+ 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);
+}