diff options
author | John Firebaugh <john.firebaugh@gmail.com> | 2016-01-26 12:19:04 -0800 |
---|---|---|
committer | John Firebaugh <john.firebaugh@gmail.com> | 2016-02-10 15:40:20 -0800 |
commit | cdedb66387065680efd318dacb61572c920b81b1 (patch) | |
tree | 3cc2676e15fac4c7b024cd17603c46e341f5a589 /test/storage | |
parent | 025375ad0b365a06e0742b92fecc9bc538b5a6e0 (diff) | |
download | qtlocation-mapboxgl-cdedb66387065680efd318dacb61572c920b81b1.tar.gz |
[core] Reimplement existing caching within an offline-capable database schema
Diffstat (limited to 'test/storage')
-rw-r--r-- | test/storage/asset_file_source.cpp | 1 | ||||
-rw-r--r-- | test/storage/cache_response.cpp | 215 | ||||
-rw-r--r-- | test/storage/cache_shared.cpp | 34 | ||||
-rw-r--r-- | test/storage/cache_size.cpp | 241 | ||||
-rw-r--r-- | test/storage/cache_stale.cpp | 131 | ||||
-rw-r--r-- | test/storage/database.cpp | 349 | ||||
-rw-r--r-- | test/storage/default_file_source.cpp (renamed from test/storage/cache_revalidate.cpp) | 52 | ||||
-rw-r--r-- | test/storage/offline_database.cpp | 374 |
8 files changed, 422 insertions, 975 deletions
diff --git a/test/storage/asset_file_source.cpp b/test/storage/asset_file_source.cpp index 6734cc693c..a9261ee8a2 100644 --- a/test/storage/asset_file_source.cpp +++ b/test/storage/asset_file_source.cpp @@ -1,7 +1,6 @@ #include "storage.hpp" #include <mbgl/storage/asset_file_source.hpp> -#include <mbgl/storage/sqlite_cache.hpp> #include <mbgl/platform/platform.hpp> #include <mbgl/util/chrono.hpp> #include <mbgl/util/run_loop.hpp> diff --git a/test/storage/cache_response.cpp b/test/storage/cache_response.cpp deleted file mode 100644 index adeb45727e..0000000000 --- a/test/storage/cache_response.cpp +++ /dev/null @@ -1,215 +0,0 @@ -#include "storage.hpp" - -#include <mbgl/storage/default_file_source.hpp> -#include <mbgl/storage/sqlite_cache.hpp> -#include <mbgl/util/chrono.hpp> -#include <mbgl/util/run_loop.hpp> - -TEST_F(Storage, CacheResponse) { - SCOPED_TEST(CacheResponse); - - using namespace mbgl; - - util::RunLoop loop; - DefaultFileSource fs(":memory:", "."); - - const Resource resource { Resource::Unknown, "http://127.0.0.1:3000/cache" }; - Response response; - - std::unique_ptr<FileRequest> req1; - std::unique_ptr<FileRequest> req2; - - req1 = fs.request(resource, [&](Response res) { - req1.reset(); - EXPECT_EQ(nullptr, res.error); - ASSERT_TRUE(res.data.get()); - EXPECT_EQ("Response 1", *res.data); - EXPECT_TRUE(bool(res.expires)); - EXPECT_FALSE(bool(res.modified)); - EXPECT_FALSE(bool(res.etag)); - response = res; - - // Now test that we get the same values as in the previous request. If we'd go to the server - // again, we'd get different values. - req2 = fs.request(resource, [&](Response res2) { - req2.reset(); - EXPECT_EQ(response.error, res2.error); - ASSERT_TRUE(res2.data.get()); - EXPECT_EQ(*response.data, *res2.data); - EXPECT_EQ(response.expires, res2.expires); - EXPECT_EQ(response.modified, res2.modified); - EXPECT_EQ(response.etag, res2.etag); - - loop.stop(); - CacheResponse.finish(); - }); - }); - - loop.run(); -} - -// Make sure we /do/ store 404 Not Found responses into the cache -TEST_F(Storage, CacheNotFound) { - SCOPED_TEST(CacheNotFound); - - using namespace mbgl; - - util::RunLoop loop; - DefaultFileSource fs(":memory:", "."); - - const Resource resource{ Resource::Unknown, "http://127.0.0.1:3000/not-found" }; - - // Insert existing data into the cache that will be marked as stale. - Response response; - response.data = std::make_shared<const std::string>("existing data"); - fs.getCache().put(resource, response); - - std::unique_ptr<FileRequest> req1; - std::unique_ptr<WorkRequest> req2; - - int counter = 0; - - // Then, request the actual URL and check that we're getting the rigged cache response first, - // then the connection error message. - req1 = fs.request(resource, [&](Response res) { - if (counter == 0) { - EXPECT_EQ(nullptr, res.error); - ASSERT_TRUE(res.data.get()); - EXPECT_EQ("existing data", *res.data); - EXPECT_FALSE(bool(res.expires)); - EXPECT_FALSE(bool(res.modified)); - EXPECT_FALSE(bool(res.etag)); - } else if (counter == 1) { - EXPECT_NE(nullptr, res.error); - EXPECT_EQ(Response::Error::Reason::NotFound, res.error->reason); - ASSERT_TRUE(res.data.get()); - EXPECT_EQ("Not Found!", *res.data); - req1.reset(); - - // Finally, check the cache to make sure we cached the 404 response. - req2 = fs.getCache().get(resource, [&](std::unique_ptr<Response> res2) { - EXPECT_NE(nullptr, res2->error); - EXPECT_EQ(Response::Error::Reason::NotFound, res2->error->reason); - ASSERT_TRUE(res2->data.get()); - EXPECT_EQ("Not Found!", *res2->data); - CacheNotFound.finish(); - loop.stop(); - }); - } else { - FAIL() << "Got too many responses"; - } - counter++; - }); - - loop.run(); -} - -// Make sure we don't store a connection error into the cache -TEST_F(Storage, DontCacheConnectionErrors) { - SCOPED_TEST(DontCacheConnectionErrors); - - using namespace mbgl; - - util::RunLoop loop; - DefaultFileSource fs(":memory:", "."); - - const Resource resource{ Resource::Unknown, "http://127.0.0.1:3001" }; - - // Insert existing data into the cache that will be marked as stale. - Response response; - response.data = std::make_shared<const std::string>("existing data"); - fs.getCache().put(resource, response); - - std::unique_ptr<FileRequest> req1; - std::unique_ptr<WorkRequest> req2; - - int counter = 0; - - // Then, request the actual URL and check that we're getting the rigged cache response first, - // then the connection error message. - req1 = fs.request(resource, [&](Response res) { - if (counter == 0) { - EXPECT_EQ(nullptr, res.error); - ASSERT_TRUE(res.data.get()); - EXPECT_EQ("existing data", *res.data); - EXPECT_FALSE(bool(res.expires)); - EXPECT_FALSE(bool(res.modified)); - EXPECT_FALSE(bool(res.etag)); - } else if (counter == 1) { - EXPECT_NE(nullptr, res.error); - EXPECT_EQ(Response::Error::Reason::Connection, res.error->reason); - req1.reset(); - - // Finally, check the cache to make sure we still have our original data in there rather - // than the failed connection attempt. - req2 = fs.getCache().get(resource, [&](std::unique_ptr<Response> res2) { - EXPECT_EQ(nullptr, res2->error); - ASSERT_TRUE(res2->data.get()); - EXPECT_EQ("existing data", *res2->data); - DontCacheConnectionErrors.finish(); - loop.stop(); - }); - } else { - FAIL() << "Got too many responses"; - } - counter++; - }); - - loop.run(); -} - -// Make sure we don't store a bad server response into the cache -TEST_F(Storage, DontCacheServerErrors) { - SCOPED_TEST(DontCacheServerErrors); - - using namespace mbgl; - - util::RunLoop loop; - DefaultFileSource fs(":memory:", "."); - - const Resource resource{ Resource::Unknown, "http://127.0.0.1:3000/permanent-error" }; - - // Insert existing data into the cache that will be marked as stale. - Response response; - response.data = std::make_shared<const std::string>("existing data"); - fs.getCache().put(resource, response); - - std::unique_ptr<FileRequest> req1; - std::unique_ptr<WorkRequest> req2; - - int counter = 0; - - // Then, request the actual URL and check that we're getting the rigged cache response first, - // then the server error message. - req1 = fs.request(resource, [&](Response res) { - if (counter == 0) { - EXPECT_EQ(nullptr, res.error); - ASSERT_TRUE(res.data.get()); - EXPECT_EQ("existing data", *res.data); - EXPECT_FALSE(bool(res.expires)); - EXPECT_FALSE(bool(res.modified)); - EXPECT_FALSE(bool(res.etag)); - } else if (counter == 1) { - EXPECT_NE(nullptr, res.error); - EXPECT_EQ(Response::Error::Reason::Server, res.error->reason); - ASSERT_TRUE(res.data.get()); - EXPECT_EQ("Server Error!", *res.data); - req1.reset(); - - // Finally, check the cache to make sure we still have our original data in there rather - // than the failed connection attempt. - req2 = fs.getCache().get(resource, [&](std::unique_ptr<Response> res2) { - EXPECT_EQ(nullptr, res2->error); - ASSERT_TRUE(res2->data.get()); - EXPECT_EQ("existing data", *res2->data); - DontCacheServerErrors.finish(); - loop.stop(); - }); - } else { - FAIL() << "Got too many responses"; - } - counter++; - }); - - loop.run(); -} diff --git a/test/storage/cache_shared.cpp b/test/storage/cache_shared.cpp deleted file mode 100644 index 89ef3bfb84..0000000000 --- a/test/storage/cache_shared.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "storage.hpp" - -#include <mbgl/storage/sqlite_cache.hpp> -#include <mbgl/storage/resource.hpp> -#include <mbgl/util/run_loop.hpp> - -TEST_F(Storage, CacheShared) { - SCOPED_TEST(CacheShared) - using namespace mbgl; - - util::RunLoop loop; - - // Check that we're getting two different caches when we request different paths. - auto memory = SQLiteCache::getShared(); - auto file = SQLiteCache::getShared("test/fixtures/database/cache.db"); - EXPECT_NE(memory.get(), file.get()); - EXPECT_EQ(memory.get(), SQLiteCache::getShared().get()); - - // Store a response into the memory file cache, then delete the last reference. - const Resource resource { Resource::Kind::Unknown, "http://example.com" }; - memory->put(resource, Response()); - memory.reset(); - - // Now check that the original memory file cache has been destructed and that it doesn't contain - // the information we put into it. - memory = SQLiteCache::getShared(); - auto req = memory->get(resource, [&](std::unique_ptr<Response> res) { - EXPECT_FALSE(res.get()); - CacheShared.finish(); - loop.stop(); - }); - - loop.run(); -} diff --git a/test/storage/cache_size.cpp b/test/storage/cache_size.cpp deleted file mode 100644 index b0d59d5934..0000000000 --- a/test/storage/cache_size.cpp +++ /dev/null @@ -1,241 +0,0 @@ -#include "storage.hpp" - -#include <mbgl/storage/resource.hpp> -#include <mbgl/storage/response.hpp> -#include <mbgl/storage/sqlite_cache.hpp> -#include <mbgl/util/run_loop.hpp> -#include <mbgl/util/string.hpp> -#include <mbgl/util/timer.hpp> -#include <mbgl/util/chrono.hpp> - -#include <memory> -#include <random> - -bool tileIsCached(mbgl::SQLiteCache* cache, unsigned id) { - using namespace mbgl; - - auto url = std::string("http://tile") + mbgl::util::toString(id); - bool replied = false; - - std::unique_ptr<Response> response; - auto callback = [&] (std::unique_ptr<Response> res) { - replied = true; - response = std::move(res); - }; - - Resource resource{ Resource::Kind::Tile, url }; - auto req = cache->get(resource, callback); - - while (!replied) { - util::RunLoop::Get()->runOnce(); - } - - return response != nullptr; -} - -void insertTile(mbgl::SQLiteCache* cache, unsigned id, uint64_t size) { - using namespace mbgl; - - auto url = std::string("http://tile") + mbgl::util::toString(id); - - Response response; - response.modified = SystemClock::now(); - response.expires = SystemClock::now() + Seconds(5000); - response.etag = url; - - auto data = std::make_shared<std::string>(size, 0); - - // Fill data with garbage so SQLite won't try to - // optimize allocation by reusing pages. - static std::mt19937 generator; - std::generate_n(data->begin(), size, generator); - - response.data = data; - - Resource resource{ Resource::Kind::Tile, url }; - cache->put(resource, response); -} - -void refreshTile(mbgl::SQLiteCache* cache, unsigned id) { - using namespace mbgl; - - auto url = std::string("http://tile") + mbgl::util::toString(id); - - Response response; - response.modified = SystemClock::now(); - response.expires = SystemClock::now() + Seconds(5000); - response.notModified = true; - - Resource resource{ Resource::Kind::Tile, url }; - cache->put(resource, response); -} - -uint64_t cacheSize(mbgl::SQLiteCache* cache, unsigned entryCount, uint64_t entrySize) { - uint64_t total = 0; - - for (unsigned i = 0; i < entryCount; ++i) { - if (tileIsCached(cache, i)) { - total += entrySize; - } - } - - return total; -} - -TEST_F(Storage, CacheEntrySizeLimit) { - using namespace mbgl; - - util::RunLoop loop; - SQLiteCache cache(":memory:"); - - const uint64_t entrySize = 5 * 1024 * 1024; // 5 MB - - insertTile(&cache, 0, entrySize); - EXPECT_TRUE(tileIsCached(&cache, 0)); - - insertTile(&cache, 1, entrySize + 1); - EXPECT_FALSE(tileIsCached(&cache, 1)); - - insertTile(&cache, 2, entrySize - 1); - EXPECT_TRUE(tileIsCached(&cache, 2)); - - // Setting a new size should not delete existing entries. - cache.setMaximumCacheEntrySize(entrySize / 2); - EXPECT_TRUE(tileIsCached(&cache, 2)); - - insertTile(&cache, 3, entrySize / 2 - 1); - EXPECT_TRUE(tileIsCached(&cache, 3)); - - insertTile(&cache, 4, entrySize); - EXPECT_FALSE(tileIsCached(&cache, 4)); - - cache.setMaximumCacheEntrySize(entrySize * 2); - insertTile(&cache, 5, entrySize); - EXPECT_TRUE(tileIsCached(&cache, 5)); -} - -TEST_F(Storage, CacheSizeSetNewLimit) { - using namespace mbgl; - - util::RunLoop loop; - SQLiteCache cache(":memory:"); - - const unsigned entryCount = 800; - const uint64_t entrySize = 10 * 1024; // 10 KB - - cache.setMaximumCacheEntrySize(entrySize + 1); - - // Cache size defaults to unlimited, all these - // inserts should work. - for (unsigned i = 0; i < entryCount; ++i) { - insertTile(&cache, i, entrySize); - } - - for (unsigned i = 0; i < entryCount; ++i) { - EXPECT_TRUE(tileIsCached(&cache, i)); - } - - uint64_t expectedCacheSize = entryCount * entrySize; - EXPECT_EQ(cacheSize(&cache, entryCount, entrySize), expectedCacheSize); - - // Setting a new size should remove records until the new - // size limit is satisfied. - cache.setMaximumCacheSize(expectedCacheSize / 2); - EXPECT_LT(cacheSize(&cache, entryCount, entrySize), expectedCacheSize / 2); - - // Cache size 1 should practically clean the cache and - // prevent adding any record, although it makes no sense - // to use such size limit IRL. - cache.setMaximumCacheSize(1); - EXPECT_EQ(cacheSize(&cache, entryCount, entrySize), 0); - - insertTile(&cache, 1000, entrySize); - EXPECT_FALSE(tileIsCached(&cache, 1000)); - - // Zero should be treated as unlimited. - cache.setMaximumCacheSize(0); - - for (unsigned i = 0; i < entryCount; ++i) { - insertTile(&cache, i, entrySize); - } - - EXPECT_EQ(cacheSize(&cache, entryCount, entrySize), expectedCacheSize); -} - -TEST_F(Storage, CacheSizePruneLeastAccessed) { - using namespace mbgl; - - util::RunLoop loop; - SQLiteCache cache(":memory:"); - - const unsigned entryCount = 400; - const uint64_t entrySize = 10 * 1024; // 10 KB - - cache.setMaximumCacheEntrySize(entrySize + 1); - cache.setMaximumCacheSize(entrySize * 350); - - for (unsigned i = 0; i < entryCount; ++i) { - insertTile(&cache, i, entrySize); - - if (i == entryCount / 2) { - // We need to sleep for 1s here because - // that is the time resolution for the - // `accessed` time. Then we 'ping' the - // entry, that should update the - // `accessed` time, so it won't get - // pruned when we need more space. - bool done = false; - - util::Timer timer; - timer.start(Milliseconds(1300), - Duration::zero(), - [&done] { done = true; }); - - while (!done) { - loop.runOnce(); - } - - EXPECT_TRUE(tileIsCached(&cache, 7)); - - // Refresh should also update the `accessed` - // time of a tile. - refreshTile(&cache, 9); - } - } - - EXPECT_FALSE(tileIsCached(&cache, 6)); - EXPECT_FALSE(tileIsCached(&cache, 8)); - EXPECT_FALSE(tileIsCached(&cache, 10)); - - EXPECT_TRUE(tileIsCached(&cache, 7)); - EXPECT_TRUE(tileIsCached(&cache, 9)); -} - -TEST_F(Storage, CacheSizeStress) { - using namespace mbgl; - - util::RunLoop loop; - SQLiteCache cache(":memory:"); - - const unsigned entryCount = 2000; - const uint64_t entrySize = 10 * 1024; // 10 KB - - cache.setMaximumCacheEntrySize(entrySize + 1); - cache.setMaximumCacheSize(entrySize * 300); - - for (unsigned i = 0; i < entryCount; ++i) { - insertTile(&cache, i, entrySize); - } - - // Should not be in the cache as they were - // first inserted. - EXPECT_FALSE(tileIsCached(&cache, 0)); - EXPECT_FALSE(tileIsCached(&cache, 99)); - EXPECT_FALSE(tileIsCached(&cache, 199)); - EXPECT_FALSE(tileIsCached(&cache, 299)); - EXPECT_FALSE(tileIsCached(&cache, 399)); - - EXPECT_TRUE(tileIsCached(&cache, entryCount - 1)); - - EXPECT_LT(cacheSize(&cache, entryCount, entrySize), entrySize * 300); -} diff --git a/test/storage/cache_stale.cpp b/test/storage/cache_stale.cpp deleted file mode 100644 index 9ad0d1c06e..0000000000 --- a/test/storage/cache_stale.cpp +++ /dev/null @@ -1,131 +0,0 @@ -#include "storage.hpp" - -#include <mbgl/platform/default/headless_display.hpp> -#include <mbgl/platform/default/headless_view.hpp> -#include <mbgl/storage/sqlite_cache.hpp> -#include <mbgl/storage/default_file_source.hpp> - -#include <mbgl/platform/log.hpp> -#include <mbgl/util/work_request.hpp> -#include <mbgl/util/io.hpp> - -using namespace mbgl; -using namespace std::literals::chrono_literals; -using namespace std::literals::string_literals; - -namespace { - -void checkRendering(Map& map, - const char* name, - std::chrono::milliseconds timeout, - double imageThreshold = 0.001, - double pixelThreshold = 0.1) { - test::checkImage("test/fixtures/stale/"s + name, test::render(map, timeout), imageThreshold, - pixelThreshold); -} - -Response expiredItem(const std::string& path) { - Response response; - response.data = std::make_shared<std::string>(util::read_file("test/fixtures/"s + path)); - response.expires = SystemClock::from_time_t(0); - return response; -} - -const std::string prefix = "http://127.0.0.1:3000"; - -} - -auto display = std::make_shared<mbgl::HeadlessDisplay>(); - -TEST_F(Storage, CacheStaleStyle) { - HeadlessView view(display, 1); - - auto cache = SQLiteCache::getShared(":memory:"); - - // Rig the cache with an expired stylesheet. - const std::string stylePath = "stale/style.json"; - const Resource styleResource{ Resource::Kind::Style, prefix + "/" + stylePath }; - cache->put(styleResource, expiredItem(stylePath)); - - DefaultFileSource fileSource(":memory:", "."); - - Map map(view, fileSource, MapMode::Still); - map.setStyleURL(styleResource.url); - - checkRendering(map, "stale_style", 1000ms); -} - -TEST_F(Storage, CacheStaleStyleAndTileJSON) { - HeadlessView view(display, 1); - - auto cache = SQLiteCache::getShared(":memory:"); - - // Rig the cache with an expired stylesheet. - const std::string stylePath = "stale/style_and_tilejson.json"; - const Resource styleResource{ Resource::Kind::Style, prefix + "/" + stylePath }; - cache->put(styleResource, expiredItem(stylePath)); - - // Rig the cache with an expired TileJSON. - const std::string tilejsonPath = "stale/streets.json"; - const Resource tilejsonResource{ Resource::Kind::Source, prefix + "/" + tilejsonPath }; - cache->put(tilejsonResource, expiredItem(tilejsonPath)); - - DefaultFileSource fileSource(":memory:", "."); - - Map map(view, fileSource, MapMode::Still); - map.setStyleURL(styleResource.url); - - checkRendering(map, "stale_style_and_tilejson", 1000ms); -} - -TEST_F(Storage, CacheStaleStyleAndSprite) { - HeadlessView view(display, 1); - - auto cache = SQLiteCache::getShared(":memory:"); - - // Rig the cache with an expired stylesheet. - const std::string stylePath = "stale/style_and_sprite.json"; - const Resource styleResource{ Resource::Kind::Style, prefix + "/" + stylePath }; - cache->put(styleResource, expiredItem(stylePath)); - - // Rig the cache with an expired sprite JSON. - const std::string spritejsonPath = "stale/sprite.json"; - const Resource spritejsonResource{ Resource::Kind::SpriteJSON, prefix + "/" + spritejsonPath }; - cache->put(spritejsonResource, expiredItem(spritejsonPath)); - - // Rig the cache with an expired sprite JSON. - const std::string spriteimagePath = "stale/sprite.png"; - const Resource spriteimageResource{ Resource::Kind::SpriteImage, prefix + "/" + spriteimagePath }; - cache->put(spriteimageResource, expiredItem(spriteimagePath)); - - DefaultFileSource fileSource(":memory:", "."); - - Map map(view, fileSource, MapMode::Still); - map.setStyleURL(styleResource.url); - - checkRendering(map, "stale_style_and_sprite", 1000ms); -} - -TEST_F(Storage, CacheStaleStyleAndGlyphs) { - HeadlessView view(display, 1); - - auto cache = SQLiteCache::getShared(":memory:"); - - // Rig the cache with an expired stylesheet. - const std::string stylePath = "stale/style_and_glyphs.json"; - const Resource styleResource{ Resource::Kind::Style, prefix + "/" + stylePath }; - cache->put(styleResource, expiredItem(stylePath)); - - // Rig the cache with an expired glyph PBF. - const std::string glyphPath = "stale/glyph.pbf"; - const Resource glyphResource{ Resource::Kind::Glyphs, prefix + "/stale/Open%20Sans%20Regular%2c%20Arial%20Unicode%20MS%20Regular/0-255.pbf" }; - cache->put(glyphResource, expiredItem(glyphPath)); - - DefaultFileSource fileSource(":memory:", "."); - - Map map(view, fileSource, MapMode::Still); - map.setStyleURL(styleResource.url); - - // TODO: this shouldn't take > 1 second - checkRendering(map, "stale_style_and_glyphs", 2000ms, 0.0015); -} diff --git a/test/storage/database.cpp b/test/storage/database.cpp deleted file mode 100644 index 19b46763b9..0000000000 --- a/test/storage/database.cpp +++ /dev/null @@ -1,349 +0,0 @@ -#include "storage.hpp" -#include "../fixtures/fixture_log_observer.hpp" - -#include "sqlite_cache_impl.hpp" -#include <mbgl/storage/resource.hpp> -#include <mbgl/storage/response.hpp> -#include <mbgl/util/io.hpp> - -#include <sqlite3.h> - -TEST_F(Storage, DatabaseDoesNotExist) { - using namespace mbgl; - - Log::setObserver(std::make_unique<FixtureLogObserver>()); - - SQLiteCache::Impl cache("test/fixtures/404/cache.db"); - - cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr<Response> res) { - EXPECT_EQ(nullptr, res.get()); - }); - - auto observer = Log::removeObserver(); - EXPECT_EQ(1ul, dynamic_cast<FixtureLogObserver*>(observer.get())->count({ EventSeverity::Error, Event::Database, 14, "unable to open database file" })); -} - -void createDir(const char* name) { - const int ret = mkdir(name, 0755); - if (ret == -1) { - ASSERT_EQ(EEXIST, errno); - } else { - ASSERT_EQ(0, ret); - } -} - -void deleteFile(const char* name) { - const int ret = unlink(name); - if (ret == -1) { - ASSERT_EQ(ENOENT, errno); - } else { - ASSERT_EQ(0, ret); - } -} - - -void writeFile(const char* name, const std::string& data) { - mbgl::util::write_file(name, data); -} - - -TEST_F(Storage, DatabaseCreate) { - using namespace mbgl; - - createDir("test/fixtures/database"); - deleteFile("test/fixtures/database/cache.db"); - - Log::setObserver(std::make_unique<FixtureLogObserver>()); - - SQLiteCache::Impl cache("test/fixtures/database/cache.db"); - - cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr<Response> res) { - EXPECT_EQ(nullptr, res.get()); - }); - - Log::removeObserver(); -} - -TEST_F(Storage, DatabaseVersion) { - using namespace mbgl; - - createDir("test/fixtures/database"); - deleteFile("test/fixtures/database/cache.db"); - std::string path("test/fixtures/database/cache.db"); - - Log::setObserver(std::make_unique<FixtureLogObserver>()); - - { - SQLiteCache::Impl cache(path); - cache.put({ Resource::Unknown, "mapbox://test" }, Response()); - } - - sqlite3* db; - sqlite3_open_v2(path.c_str(), &db, SQLITE_OPEN_READWRITE, nullptr); - sqlite3_exec(db, "PRAGMA user_version = 999999", nullptr, nullptr, nullptr); - sqlite3_close_v2(db); - - // Changing the version will force the database to get recreated - // thus removing every pre-existing cache entry. - { - SQLiteCache::Impl cache(path); - - cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr<Response> res) { - EXPECT_EQ(nullptr, res.get()); - }); - } - - Log::removeObserver(); -} - -class FileLock { -public: - FileLock(const std::string& path) { - const int err = sqlite3_open_v2(path.c_str(), &db, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, nullptr); - if (err != SQLITE_OK) { - throw std::runtime_error("Could not open db"); - } - lock(); - } - - void lock() { - assert(!locked); - const int err = sqlite3_exec(db, "begin exclusive transaction", nullptr, nullptr, nullptr); - if (err != SQLITE_OK) { - throw std::runtime_error("Could not lock db"); - } - locked = true; - } - - void unlock() { - assert(locked); - const int err = sqlite3_exec(db, "commit", nullptr, nullptr, nullptr); - if (err != SQLITE_OK) { - throw std::runtime_error("Could not unlock db"); - } - locked = false; - } - - ~FileLock() { - if (locked) { - unlock(); - } - } - -private: - sqlite3* db; - bool locked = false; -}; - -TEST_F(Storage, DatabaseLockedRead) { - using namespace mbgl; - - // Create a locked file. - createDir("test/fixtures/database"); - deleteFile("test/fixtures/database/locked.db"); - FileLock guard("test/fixtures/database/locked.db"); - - SQLiteCache::Impl cache("test/fixtures/database/locked.db"); - - { - // First request should fail. - Log::setObserver(std::make_unique<FixtureLogObserver>()); - - cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr<Response> res) { - EXPECT_EQ(nullptr, res.get()); - }); - - // Make sure that we got a few "database locked" errors - auto observer = Log::removeObserver(); - auto flo = dynamic_cast<FixtureLogObserver*>(observer.get()); - EXPECT_EQ(4ul, flo->count({ EventSeverity::Error, Event::Database, 5, "database is locked" })); - } - - // Then, unlock the file and try again. - guard.unlock(); - - { - // First, try getting a file (the cache value should not exist). - Log::setObserver(std::make_unique<FixtureLogObserver>()); - - cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr<Response> res) { - EXPECT_EQ(nullptr, res.get()); - }); - - // Make sure that we got a no errors - Log::removeObserver(); - } -} - - - -TEST_F(Storage, DatabaseLockedWrite) { - using namespace mbgl; - - // Create a locked file. - createDir("test/fixtures/database"); - deleteFile("test/fixtures/database/locked.db"); - FileLock guard("test/fixtures/database/locked.db"); - - SQLiteCache::Impl cache("test/fixtures/database/locked.db"); - - { - // Adds a file (which should fail). - Log::setObserver(std::make_unique<FixtureLogObserver>()); - - cache.put({ Resource::Unknown, "mapbox://test" }, Response()); - cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr<Response> res) { - EXPECT_EQ(nullptr, res.get()); - }); - - auto observer = Log::removeObserver(); - auto flo = dynamic_cast<FixtureLogObserver*>(observer.get()); - EXPECT_EQ(8ul, flo->count({ EventSeverity::Error, Event::Database, 5, "database is locked" })); - } - - // Then, unlock the file and try again. - guard.unlock(); - - { - // Then, set a file and obtain it again. - Log::setObserver(std::make_unique<FixtureLogObserver>()); - - Response response; - response.data = std::make_shared<std::string>("Demo"); - cache.put({ Resource::Unknown, "mapbox://test" }, response); - cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr<Response> res) { - ASSERT_NE(nullptr, res.get()); - ASSERT_TRUE(res->data.get()); - EXPECT_EQ("Demo", *res->data); - }); - - // Make sure that we got a no errors - Log::removeObserver(); - } -} - - - - -TEST_F(Storage, DatabaseLockedRefresh) { - using namespace mbgl; - - // Create a locked file. - createDir("test/fixtures/database"); - deleteFile("test/fixtures/database/locked.db"); - - SQLiteCache::Impl cache("test/fixtures/database/locked.db"); - - // Then, lock the file and try again. - FileLock guard("test/fixtures/database/locked.db"); - - { - // Adds a file. - Log::setObserver(std::make_unique<FixtureLogObserver>()); - - Response response; - response.data = std::make_shared<std::string>("Demo"); - cache.put({ Resource::Unknown, "mapbox://test" }, response); - cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr<Response> res) { - EXPECT_EQ(nullptr, res.get()); - }); - - auto observer = Log::removeObserver(); - auto flo = dynamic_cast<FixtureLogObserver*>(observer.get()); - EXPECT_EQ(8ul, flo->count({ EventSeverity::Error, Event::Database, 5, "database is locked" })); - } - - { - // Then, try to refresh it. - Log::setObserver(std::make_unique<FixtureLogObserver>()); - - cache.refresh({ Resource::Unknown, "mapbox://test" }, {}); - cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr<Response> res) { - EXPECT_EQ(nullptr, res.get()); - }); - - // Make sure that we got the right errors. - auto observer = Log::removeObserver(); - auto flo = dynamic_cast<FixtureLogObserver*>(observer.get()); - EXPECT_EQ(8ul, flo->count({ EventSeverity::Error, Event::Database, 5, "database is locked" })); - } -} - - - -TEST_F(Storage, DatabaseDeleted) { - using namespace mbgl; - - // Create a locked file. - createDir("test/fixtures/database"); - deleteFile("test/fixtures/database/locked.db"); - - SQLiteCache::Impl cache("test/fixtures/database/locked.db"); - - { - // Adds a file. - Log::setObserver(std::make_unique<FixtureLogObserver>()); - - Response response; - response.data = std::make_shared<std::string>("Demo"); - cache.put({ Resource::Unknown, "mapbox://test" }, response); - cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr<Response> res) { - ASSERT_NE(nullptr, res.get()); - ASSERT_TRUE(res->data.get()); - EXPECT_EQ("Demo", *res->data); - }); - - Log::removeObserver(); - } - - deleteFile("test/fixtures/database/locked.db"); - - { - // Adds a file. - Log::setObserver(std::make_unique<FixtureLogObserver>()); - - Response response; - response.data = std::make_shared<std::string>("Demo"); - cache.put({ Resource::Unknown, "mapbox://test" }, response); - cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr<Response> res) { - ASSERT_NE(nullptr, res.get()); - ASSERT_TRUE(res->data.get()); - EXPECT_EQ("Demo", *res->data); - }); - - auto observer = Log::removeObserver(); - auto flo = dynamic_cast<FixtureLogObserver*>(observer.get()); - EXPECT_EQ(1ul, flo->count({ EventSeverity::Error, Event::Database, 8, "attempt to write a readonly database" })); - } -} - - - -TEST_F(Storage, DatabaseInvalid) { - using namespace mbgl; - - // Create a locked file. - createDir("test/fixtures/database"); - deleteFile("test/fixtures/database/invalid.db"); - writeFile("test/fixtures/database/invalid.db", "this is an invalid file"); - - SQLiteCache::Impl cache("test/fixtures/database/invalid.db"); - - { - // Adds a file. - Log::setObserver(std::make_unique<FixtureLogObserver>()); - - Response response; - response.data = std::make_shared<std::string>("Demo"); - cache.put({ Resource::Unknown, "mapbox://test" }, response); - cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr<Response> res) { - ASSERT_NE(nullptr, res.get()); - ASSERT_TRUE(res->data.get()); - EXPECT_EQ("Demo", *res->data); - }); - - auto observer = Log::removeObserver(); - auto flo = dynamic_cast<FixtureLogObserver*>(observer.get()); - EXPECT_EQ(1ul, flo->count({ EventSeverity::Warning, Event::Database, -1, "Trashing invalid database" })); - } -} diff --git a/test/storage/cache_revalidate.cpp b/test/storage/default_file_source.cpp index e769e934d5..26fb164d3f 100644 --- a/test/storage/cache_revalidate.cpp +++ b/test/storage/default_file_source.cpp @@ -1,10 +1,54 @@ #include "storage.hpp" #include <mbgl/storage/default_file_source.hpp> -#include <mbgl/util/chrono.hpp> #include <mbgl/util/run_loop.hpp> -TEST_F(Storage, CacheRevalidateSame) { +class DefaultFileSourceTest : public Storage {}; + +TEST_F(DefaultFileSourceTest, CacheResponse) { + SCOPED_TEST(CacheResponse); + + using namespace mbgl; + + util::RunLoop loop; + DefaultFileSource fs(":memory:", "."); + + const Resource resource { Resource::Unknown, "http://127.0.0.1:3000/cache" }; + Response response; + + std::unique_ptr<FileRequest> req1; + std::unique_ptr<FileRequest> req2; + + req1 = fs.request(resource, [&](Response res) { + req1.reset(); + EXPECT_EQ(nullptr, res.error); + ASSERT_TRUE(res.data.get()); + EXPECT_EQ("Response 1", *res.data); + EXPECT_TRUE(bool(res.expires)); + EXPECT_FALSE(bool(res.modified)); + EXPECT_FALSE(bool(res.etag)); + response = res; + + // Now test that we get the same values as in the previous request. If we'd go to the server + // again, we'd get different values. + req2 = fs.request(resource, [&](Response res2) { + req2.reset(); + EXPECT_EQ(response.error, res2.error); + ASSERT_TRUE(res2.data.get()); + EXPECT_EQ(*response.data, *res2.data); + EXPECT_EQ(response.expires, res2.expires); + EXPECT_EQ(response.modified, res2.modified); + EXPECT_EQ(response.etag, res2.etag); + + loop.stop(); + CacheResponse.finish(); + }); + }); + + loop.run(); +} + +TEST_F(DefaultFileSourceTest, CacheRevalidateSame) { SCOPED_TEST(CacheRevalidateSame) using namespace mbgl; @@ -53,7 +97,7 @@ TEST_F(Storage, CacheRevalidateSame) { loop.run(); } -TEST_F(Storage, CacheRevalidateModified) { +TEST_F(DefaultFileSourceTest, CacheRevalidateModified) { SCOPED_TEST(CacheRevalidateModified) using namespace mbgl; @@ -102,7 +146,7 @@ TEST_F(Storage, CacheRevalidateModified) { loop.run(); } -TEST_F(Storage, CacheRevalidateEtag) { +TEST_F(DefaultFileSourceTest, CacheRevalidateEtag) { SCOPED_TEST(CacheRevalidateEtag) using namespace mbgl; diff --git a/test/storage/offline_database.cpp b/test/storage/offline_database.cpp new file mode 100644 index 0000000000..e2e32ee36b --- /dev/null +++ b/test/storage/offline_database.cpp @@ -0,0 +1,374 @@ +#include "../fixtures/fixture_log_observer.hpp" + +#include <mbgl/storage/offline_database.hpp> +#include <mbgl/storage/resource.hpp> +#include <mbgl/storage/response.hpp> +#include <mbgl/util/io.hpp> + +#include <gtest/gtest.h> +#include <sqlite3.h> + +namespace { + +void createDir(const char* name) { + const int ret = mkdir(name, 0755); + if (ret == -1) { + ASSERT_EQ(EEXIST, errno); + } else { + ASSERT_EQ(0, ret); + } +} + +void deleteFile(const char* name) { + const int ret = unlink(name); + if (ret == -1) { + ASSERT_EQ(ENOENT, errno); + } else { + ASSERT_EQ(0, ret); + } +} + +void writeFile(const char* name, const std::string& data) { + mbgl::util::write_file(name, data); +} + +class FileLock { +public: + FileLock(const std::string& path) { + const int err = sqlite3_open_v2(path.c_str(), &db, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, nullptr); + if (err != SQLITE_OK) { + throw std::runtime_error("Could not open db"); + } + lock(); + } + + void lock() { + assert(!locked); + const int err = sqlite3_exec(db, "begin exclusive transaction", nullptr, nullptr, nullptr); + if (err != SQLITE_OK) { + throw std::runtime_error("Could not lock db"); + } + locked = true; + } + + void unlock() { + assert(locked); + const int err = sqlite3_exec(db, "commit", nullptr, nullptr, nullptr); + if (err != SQLITE_OK) { + throw std::runtime_error("Could not unlock db"); + } + locked = false; + } + + ~FileLock() { + if (locked) { + unlock(); + } + } + +private: + sqlite3* db; + bool locked = false; +}; + +} + +//TEST(OfflineDatabase, NonexistentDirectory) { +// using namespace mbgl; +// +// Log::setObserver(std::make_unique<FixtureLogObserver>()); +// +// OfflineDatabase db("test/fixtures/404/offline.db"); +// +// db.get({ Resource::Unknown, "mapbox://test" }, [] (optional<Response> res) { +// EXPECT_FALSE(bool(res)); +// }); +// +// auto observer = Log::removeObserver(); +// EXPECT_EQ(1ul, dynamic_cast<FixtureLogObserver*>(observer.get())->count({ EventSeverity::Error, Event::Database, 14, "unable to open database file" })); +//} + +TEST(OfflineDatabase, Create) { + using namespace mbgl; + + createDir("test/fixtures/database"); + deleteFile("test/fixtures/database/offline.db"); + + Log::setObserver(std::make_unique<FixtureLogObserver>()); + + OfflineDatabase db("test/fixtures/database/offline.db"); + EXPECT_FALSE(bool(db.get({ Resource::Unknown, "mapbox://test" }))); + + Log::removeObserver(); +} + +TEST(OfflineDatabase, SchemaVersion) { + using namespace mbgl; + + createDir("test/fixtures/database"); + deleteFile("test/fixtures/database/offline.db"); + std::string path("test/fixtures/database/offline.db"); + + { + sqlite3* db; + sqlite3_open_v2(path.c_str(), &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr); + sqlite3_exec(db, "PRAGMA user_version = 1", nullptr, nullptr, nullptr); + sqlite3_close_v2(db); + } + + Log::setObserver(std::make_unique<FixtureLogObserver>()); + OfflineDatabase db(path); + + auto observer = Log::removeObserver(); + auto flo = dynamic_cast<FixtureLogObserver*>(observer.get()); + EXPECT_EQ(1ul, flo->count({ EventSeverity::Warning, Event::Database, -1, "Removing existing incompatible offline database" })); +} + +TEST(OfflineDatabase, Invalid) { + using namespace mbgl; + + createDir("test/fixtures/database"); + deleteFile("test/fixtures/database/invalid.db"); + writeFile("test/fixtures/database/invalid.db", "this is an invalid file"); + + Log::setObserver(std::make_unique<FixtureLogObserver>()); + + OfflineDatabase db("test/fixtures/database/invalid.db"); + + auto observer = Log::removeObserver(); + auto flo = dynamic_cast<FixtureLogObserver*>(observer.get()); + EXPECT_EQ(1ul, flo->count({ EventSeverity::Warning, Event::Database, -1, "Removing existing incompatible offline database" })); +} + +//TEST(OfflineDatabase, DatabaseLockedRead) { +// using namespace mbgl; +// +// // Create a locked file. +// createDir("test/fixtures/database"); +// deleteFile("test/fixtures/database/locked.db"); +// FileLock guard("test/fixtures/database/locked.db"); +// +// OfflineDatabase db("test/fixtures/database/locked.db"); +// +// { +// // First request should fail. +// Log::setObserver(std::make_unique<FixtureLogObserver>()); +// +// db.get({ Resource::Unknown, "mapbox://test" }, [] (optional<Response> res) { +// EXPECT_FALSE(bool(res)); +// }); +// +// // Make sure that we got a few "database locked" errors +// auto observer = Log::removeObserver(); +// auto flo = dynamic_cast<FixtureLogObserver*>(observer.get()); +// EXPECT_EQ(4ul, flo->count({ EventSeverity::Error, Event::Database, 5, "database is locked" })); +// } +// +// // Then, unlock the file and try again. +// guard.unlock(); +// +// { +// // First, try getting a file (the cache value should not exist). +// Log::setObserver(std::make_unique<FixtureLogObserver>()); +// +// db.get({ Resource::Unknown, "mapbox://test" }, [] (optional<Response> res) { +// EXPECT_FALSE(bool(res)); +// }); +// +// // Make sure that we got a no errors +// Log::removeObserver(); +// } +//} +// +//TEST(OfflineDatabase, DatabaseLockedWrite) { +// using namespace mbgl; +// +// // Create a locked file. +// createDir("test/fixtures/database"); +// deleteFile("test/fixtures/database/locked.db"); +// FileLock guard("test/fixtures/database/locked.db"); +// +// OfflineDatabase db("test/fixtures/database/locked.db"); +// +// { +// // Adds a file (which should fail). +// Log::setObserver(std::make_unique<FixtureLogObserver>()); +// +// db.put({ Resource::Unknown, "mapbox://test" }, Response()); +// db.get({ Resource::Unknown, "mapbox://test" }, [] (optional<Response> res) { +// EXPECT_FALSE(bool(res)); +// }); +// +// auto observer = Log::removeObserver(); +// auto flo = dynamic_cast<FixtureLogObserver*>(observer.get()); +// EXPECT_EQ(8ul, flo->count({ EventSeverity::Error, Event::Database, 5, "database is locked" })); +// } +// +// // Then, unlock the file and try again. +// guard.unlock(); +// +// { +// // Then, set a file and obtain it again. +// Log::setObserver(std::make_unique<FixtureLogObserver>()); +// +// Response response; +// response.data = std::make_shared<std::string>("Demo"); +// db.put({ Resource::Unknown, "mapbox://test" }, response); +// db.get({ Resource::Unknown, "mapbox://test" }, [] (optional<Response> res) { +// ASSERT_TRUE(bool(res)); +// ASSERT_TRUE(res->data.get()); +// EXPECT_EQ("Demo", *res->data); +// }); +// +// // Make sure that we got a no errors +// Log::removeObserver(); +// } +//} +// +//TEST(OfflineDatabase, DatabaseDeleted) { +// using namespace mbgl; +// +// // Create a locked file. +// createDir("test/fixtures/database"); +// deleteFile("test/fixtures/database/locked.db"); +// +// OfflineDatabase db("test/fixtures/database/locked.db"); +// +// { +// // Adds a file. +// Log::setObserver(std::make_unique<FixtureLogObserver>()); +// +// Response response; +// response.data = std::make_shared<std::string>("Demo"); +// db.put({ Resource::Unknown, "mapbox://test" }, response); +// db.get({ Resource::Unknown, "mapbox://test" }, [] (optional<Response> res) { +// ASSERT_TRUE(bool(res)); +// ASSERT_TRUE(res->data.get()); +// EXPECT_EQ("Demo", *res->data); +// }); +// +// Log::removeObserver(); +// } +// +// deleteFile("test/fixtures/database/locked.db"); +// +// { +// // Adds a file. +// Log::setObserver(std::make_unique<FixtureLogObserver>()); +// +// Response response; +// response.data = std::make_shared<std::string>("Demo"); +// db.put({ Resource::Unknown, "mapbox://test" }, response); +// db.get({ Resource::Unknown, "mapbox://test" }, [] (optional<Response> res) { +// ASSERT_TRUE(bool(res)); +// ASSERT_TRUE(res->data.get()); +// EXPECT_EQ("Demo", *res->data); +// }); +// +// auto observer = Log::removeObserver(); +// auto flo = dynamic_cast<FixtureLogObserver*>(observer.get()); +// EXPECT_EQ(1ul, flo->count({ EventSeverity::Error, Event::Database, 8, "attempt to write a readonly database" })); +// } +//} + +TEST(OfflineDatabase, PutDoesNotStoreConnectionErrors) { + using namespace mbgl; + + OfflineDatabase db(":memory:"); + + Resource resource { Resource::Unknown, "http://example.com/" }; + Response response; + response.error = std::make_unique<Response::Error>(Response::Error::Reason::Connection); + + db.put(resource, response); + EXPECT_FALSE(bool(db.get(resource))); +} + +TEST(OfflineDatabase, PutDoesNotStoreServerErrors) { + using namespace mbgl; + + OfflineDatabase db(":memory:"); + + Resource resource { Resource::Unknown, "http://example.com/" }; + Response response; + response.error = std::make_unique<Response::Error>(Response::Error::Reason::Server); + + db.put(resource, response); + EXPECT_FALSE(bool(db.get(resource))); +} + +TEST(OfflineDatabase, PutResource) { + using namespace mbgl; + + OfflineDatabase db(":memory:"); + + Resource resource { Resource::Style, "http://example.com/" }; + Response response; + response.data = std::make_shared<std::string>("data"); + + db.put(resource, response); + auto res = db.get(resource); + EXPECT_EQ(nullptr, res->error.get()); + EXPECT_EQ("data", *res->data); +} + +TEST(OfflineDatabase, PutTile) { + using namespace mbgl; + + OfflineDatabase db(":memory:"); + + Resource resource { Resource::Tile, "http://example.com/" }; + resource.tileData = Resource::TileData { + "http://example.com/", + 1, + 0, + 0, + 0 + }; + Response response; + response.data = std::make_shared<std::string>("data"); + + db.put(resource, response); + auto res = db.get(resource); + EXPECT_EQ(nullptr, res->error.get()); + EXPECT_EQ("data", *res->data); +} + +TEST(OfflineDatabase, PutResourceNotFound) { + using namespace mbgl; + + OfflineDatabase db(":memory:"); + + Resource resource { Resource::Style, "http://example.com/" }; + Response response; + response.error = std::make_unique<Response::Error>(Response::Error::Reason::NotFound); + + db.put(resource, response); + auto res = db.get(resource); + EXPECT_NE(nullptr, res->error); + EXPECT_EQ(Response::Error::Reason::NotFound, res->error->reason); + EXPECT_FALSE(res->data.get()); +} + +TEST(OfflineDatabase, PutTileNotFound) { + using namespace mbgl; + + OfflineDatabase db(":memory:"); + + Resource resource { Resource::Tile, "http://example.com/" }; + resource.tileData = Resource::TileData { + "http://example.com/", + 1, + 0, + 0, + 0 + }; + Response response; + response.error = std::make_unique<Response::Error>(Response::Error::Reason::NotFound); + + db.put(resource, response); + auto res = db.get(resource); + EXPECT_NE(nullptr, res->error); + EXPECT_EQ(Response::Error::Reason::NotFound, res->error->reason); + EXPECT_FALSE(res->data.get()); +} |