[tests] Refactor and make MockFileSource more general

Now it works more like StubStyleObserver: you can assign std::functions to specific slots based on resource type.

Rewrite resource loading tests in that style, making them less like integration tests of Style and more like unit tests of Source, GlyphStore, and SpriteStore.
This commit is contained in:
John Firebaugh
2016-02-01 14:07:26 -08:00
parent 0e66d4dcc5
commit 716997b99e
12 changed files with 599 additions and 557 deletions

View File

@@ -1,69 +0,0 @@
#include "mock_file_source.hpp"
#include <mbgl/util/io.hpp>
#include <mbgl/util/chrono.hpp>
namespace mbgl {
class MockFileRequest : public FileRequest {
public:
MockFileRequest(MockFileSource& fileSource_)
: fileSource(fileSource_) {
}
~MockFileRequest() {
fileSource.pending.erase(this);
}
MockFileSource& fileSource;
};
MockFileSource::MockFileSource(Type type_, const std::string& match_)
: type(type_), match(match_) {
timer.start(Milliseconds(10), Milliseconds(10), [this] {
// Explicit move to avoid iterator invalidation if ~MockFileRequest gets called within the loop.
auto pending_ = std::move(pending);
for (auto& pair : pending_) {
respond(pair.second.first, pair.second.second);
}
});
}
MockFileSource::~MockFileSource() {
timer.stop();
}
void MockFileSource::respond(Resource resource, Callback callback) const {
if (type == Type::Success || resource.url.find(match) == std::string::npos) {
Response res;
try {
res.data = std::make_shared<const std::string>(util::read_file(resource.url));
} catch (const std::exception& err) {
res.error = std::make_unique<Response::Error>(Response::Error::Reason::Other, err.what());
}
callback(res);
} else if (type == Type::RequestFail) {
Response res;
res.error = std::make_unique<Response::Error>(Response::Error::Reason::Other, "Failed by the test case");
callback(res);
} else if (type == Type::RequestWithCorruptedData) {
Response res;
auto data = std::make_shared<std::string>(util::read_file(resource.url));
data->insert(0, "CORRUPTED");
res.data = std::move(data);
callback(res);
}
}
std::unique_ptr<FileRequest> MockFileSource::request(const Resource& resource, Callback callback) {
auto req = std::make_unique<MockFileRequest>(*this);
pending.emplace(req.get(), std::make_pair(resource, callback));
if (requestEnqueuedCallback && resource.url.find(match) != std::string::npos) {
requestEnqueuedCallback();
}
return std::move(req);
}
} // namespace mbgl

View File

@@ -1,55 +0,0 @@
#ifndef TEST_RESOURCES_MOCK_FILE_SOURCE
#define TEST_RESOURCES_MOCK_FILE_SOURCE
#include <mbgl/storage/file_source.hpp>
#include <mbgl/util/timer.hpp>
#include <string>
#include <unordered_map>
namespace mbgl {
// The MockFileSource is a FileSource that can simulate different
// types of failures and it will work completely offline.
class MockFileSource : public FileSource {
public:
// Success:
// Will reply to every request correctly with valid data.
//
// RequestFail:
// Will reply with an error to requests that contains
// the "match" string on the URL.
//
// RequestWithCorruptedData:
// Will answer every request successfully but will return
// corrupt data on the requests that contains the "match"
// string on the URL.
enum Type {
Success,
RequestFail,
RequestWithCorruptedData
};
MockFileSource(Type, const std::string& match);
~MockFileSource() override;
// Function that gets called when a matching resource is enqueued.
std::function<void (void)> requestEnqueuedCallback;
// FileSource implementation.
std::unique_ptr<FileRequest> request(const Resource&, Callback) override;
private:
void respond(Resource, Callback) const;
friend class MockFileRequest;
Type type;
std::string match;
std::unordered_map<FileRequest*, std::pair<Resource, Callback>> pending;
util::Timer timer;
};
}
#endif

View File

@@ -3,16 +3,14 @@
"name": "Test",
"sources": {
"usedsource": {
"url": "test/fixtures/resources/source_vector.json",
"tiles": [],
"type": "vector"
},
"unusedsource": {
"url": "test/fixtures/resources/source_vector.json",
"tiles": [],
"type": "vector"
}
},
"sprite": "test/fixtures/resources/sprite",
"glyphs": "test/fixtures/resources/glyphs.pbf",
"layers": [{
"id": "usedlayer",
"type": "symbol",

66
test/fixtures/stub_file_source.cpp vendored Normal file
View File

@@ -0,0 +1,66 @@
#include "stub_file_source.hpp"
namespace mbgl {
using namespace std::chrono_literals;
class StubFileRequest : public FileRequest {
public:
StubFileRequest(StubFileSource& fileSource_)
: fileSource(fileSource_) {
}
~StubFileRequest() {
fileSource.pending.erase(this);
}
StubFileSource& fileSource;
};
StubFileSource::StubFileSource() {
timer.start(10ms, 10ms, [this] {
// Explicit move to avoid iterator invalidation if ~StubFileRequest gets called within the loop.
auto pending_ = std::move(pending);
for (auto& pair : pending_) {
pair.second.second(pair.second.first);
}
});
}
StubFileSource::~StubFileSource() = default;
std::unique_ptr<FileRequest> StubFileSource::request(const Resource& resource, Callback callback) {
auto req = std::make_unique<StubFileRequest>(*this);
pending.emplace(req.get(), std::make_pair(response(resource), callback));
return std::move(req);
}
Response StubFileSource::defaultResponse(const Resource& resource) {
switch (resource.kind) {
case Resource::Kind::Style:
if (!styleResponse) throw std::runtime_error("unexpected style request");
return styleResponse(resource);
case Resource::Kind::Source:
if (!sourceResponse) throw std::runtime_error("unexpected source request");
return sourceResponse(resource);
case Resource::Kind::Tile:
if (!tileResponse) throw std::runtime_error("unexpected tile request");
return tileResponse(resource);
case Resource::Kind::Glyphs:
if (!glyphsResponse) throw std::runtime_error("unexpected glyphs request");
return glyphsResponse(resource);
case Resource::Kind::SpriteJSON:
if (!spriteJSONResponse) throw std::runtime_error("unexpected sprite JSON request");
return spriteJSONResponse(resource);
case Resource::Kind::SpriteImage:
if (!spriteImageResponse) throw std::runtime_error("unexpected sprite image request");
return spriteImageResponse(resource);
case Resource::Kind::Unknown:
throw std::runtime_error("unknown resource type");
}
// The above switch is exhaustive, but placate GCC nonetheless:
return Response();
}
} // namespace mbgl

43
test/fixtures/stub_file_source.hpp vendored Normal file
View File

@@ -0,0 +1,43 @@
#ifndef TEST_RESOURCES_STUB_FILE_SOURCE
#define TEST_RESOURCES_STUB_FILE_SOURCE
#include <mbgl/storage/file_source.hpp>
#include <mbgl/util/timer.hpp>
#include <unordered_map>
namespace mbgl {
class StubFileSource : public FileSource {
public:
StubFileSource();
~StubFileSource() override;
std::unique_ptr<FileRequest> request(const Resource&, Callback) override;
// You can set the response callback on a global level by assigning this callback:
std::function<Response (const Resource&)> response = [this] (const Resource& resource) {
return defaultResponse(resource);
};
// Or set per-kind responses by setting these callbacks:
std::function<Response (const Resource&)> styleResponse;
std::function<Response (const Resource&)> sourceResponse;
std::function<Response (const Resource&)> tileResponse;
std::function<Response (const Resource&)> glyphsResponse;
std::function<Response (const Resource&)> spriteJSONResponse;
std::function<Response (const Resource&)> spriteImageResponse;
private:
friend class StubFileRequest;
// The default behavior is to throw if no per-kind callback has been set.
Response defaultResponse(const Resource&);
std::unordered_map<FileRequest*, std::pair<Response, Callback>> pending;
util::Timer timer;
};
}
#endif

View File

@@ -1,10 +1,12 @@
#include "../fixtures/util.hpp"
#include "../fixtures/fixture_log_observer.hpp"
#include "../fixtures/mock_file_source.hpp"
#include "../fixtures/stub_file_source.hpp"
#include "../fixtures/stub_style_observer.hpp"
#include <mbgl/sprite/sprite_store.hpp>
#include <mbgl/util/run_loop.hpp>
#include <mbgl/util/string.hpp>
#include <mbgl/util/io.hpp>
#include <utility>
@@ -151,17 +153,16 @@ TEST(SpriteStore, ReplaceWithDifferentDimensions) {
class SpriteStoreTest {
public:
SpriteStoreTest(MockFileSource::Type type, const std::string& resource)
: fileSource(type, resource),
spriteStore(1.0) {}
SpriteStoreTest()
: spriteStore(1.0) {}
util::ThreadContext context { "Map", util::ThreadType::Map, util::ThreadPriority::Regular };
util::RunLoop loop;
MockFileSource fileSource;
StubFileSource fileSource;
StubStyleObserver observer;
SpriteStore spriteStore;
void run(const std::string& url) {
void run() {
// Squelch logging.
Log::setObserver(std::make_unique<Log::NullObserver>());
@@ -169,7 +170,7 @@ public:
util::ThreadContext::setFileSource(&fileSource);
spriteStore.setObserver(&observer);
spriteStore.setURL(url);
spriteStore.setURL("test/fixtures/resources/sprite");
loop.run();
}
@@ -179,70 +180,131 @@ public:
}
};
TEST(SpriteStore, LoadingSuccess) {
SpriteStoreTest test(MockFileSource::Success, "");
Response successfulSpriteImageResponse(const Resource& resource) {
EXPECT_EQ("test/fixtures/resources/sprite.png", resource.url);
Response response;
response.data = std::make_shared<std::string>(util::read_file(resource.url));
return response;
};
test.observer.spriteError = [&] (std::exception_ptr) {
FAIL();
Response successfulSpriteJSONResponse(const Resource& resource) {
EXPECT_EQ("test/fixtures/resources/sprite.json", resource.url);
Response response;
response.data = std::make_shared<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_shared<std::string>("CORRUPT");
return response;
};
TEST(SpriteStore, LoadingSuccess) {
SpriteStoreTest 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 = [&] () {
ASSERT_TRUE(!test.spriteStore.getDirty().empty());
ASSERT_EQ(test.spriteStore.pixelRatio, 1.0);
ASSERT_TRUE(test.spriteStore.isLoaded());
EXPECT_TRUE(!test.spriteStore.getDirty().empty());
EXPECT_EQ(1.0, test.spriteStore.pixelRatio);
EXPECT_TRUE(test.spriteStore.isLoaded());
test.end();
};
test.run("test/fixtures/resources/sprite");
test.run();
}
TEST(SpriteStore, LoadingFail) {
SpriteStoreTest test(MockFileSource::RequestFail, "sprite.json");
TEST(SpriteStore, JSONLoadingFail) {
SpriteStoreTest test;
test.fileSource.spriteImageResponse = successfulSpriteImageResponse;
test.fileSource.spriteJSONResponse = failedSpriteResponse;
test.observer.spriteError = [&] (std::exception_ptr error) {
ASSERT_TRUE(error != nullptr);
ASSERT_FALSE(test.spriteStore.isLoaded());
EXPECT_TRUE(error != nullptr);
EXPECT_EQ("Failed by the test case", util::toString(error));
EXPECT_FALSE(test.spriteStore.isLoaded());
test.end();
};
test.run("test/fixtures/resources/sprite");
test.run();
}
TEST(SpriteStore, LoadingCorrupted) {
SpriteStoreTest test(MockFileSource::RequestWithCorruptedData, "sprite.json");
TEST(SpriteStore, ImageLoadingFail) {
SpriteStoreTest test;
test.fileSource.spriteImageResponse = failedSpriteResponse;
test.fileSource.spriteJSONResponse = successfulSpriteJSONResponse;
test.observer.spriteError = [&] (std::exception_ptr error) {
ASSERT_TRUE(error != nullptr);
ASSERT_FALSE(test.spriteStore.isLoaded());
EXPECT_TRUE(error != nullptr);
EXPECT_EQ("Failed by the test case", util::toString(error));
EXPECT_FALSE(test.spriteStore.isLoaded());
test.end();
};
test.run("test/fixtures/resources/sprite");
test.run();
}
TEST(SpriteStore, JSONLoadingCorrupted) {
SpriteStoreTest 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.spriteStore.isLoaded());
test.end();
};
test.run();
}
TEST(SpriteStore, ImageLoadingCorrupted) {
SpriteStoreTest 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.spriteStore.isLoaded());
test.end();
};
test.run();
}
TEST(SpriteStore, LoadingCancel) {
SpriteStoreTest test(MockFileSource::Success, "sprite.json");
SpriteStoreTest test;
test.fileSource.spriteImageResponse =
test.fileSource.spriteJSONResponse = [&] (const Resource&) {
test.end();
return Response();
};
test.observer.spriteLoaded = [&] () {
FAIL() << "Should never be called";
};
test.fileSource.requestEnqueuedCallback = [&]{
test.end();
};
test.run("test/fixtures/resources/sprite");
}
TEST(SpriteStore, InvalidURL) {
SpriteStoreTest test(MockFileSource::Success, "");
test.observer.spriteError = [&] (std::exception_ptr error) {
ASSERT_TRUE(error != nullptr);
ASSERT_EQ(test.spriteStore.isLoaded(), false);
test.end();
};
test.run("foo bar");
test.run();
}

View File

@@ -1,22 +1,21 @@
#include "../fixtures/util.hpp"
#include "../fixtures/mock_file_source.hpp"
#include "../fixtures/stub_file_source.hpp"
#include "../fixtures/stub_style_observer.hpp"
#include <mbgl/text/font_stack.hpp>
#include <mbgl/text/glyph_store.hpp>
#include <mbgl/util/run_loop.hpp>
#include <mbgl/util/string.hpp>
#include <mbgl/util/io.hpp>
#include <mbgl/platform/log.hpp>
using namespace mbgl;
class GlyphStoreTest {
public:
GlyphStoreTest(MockFileSource::Type type, const std::string& resource)
: fileSource(type, resource) {}
util::ThreadContext context { "Map", util::ThreadType::Map, util::ThreadPriority::Regular };
util::RunLoop loop;
MockFileSource fileSource;
StubFileSource fileSource;
StubStyleObserver observer;
GlyphStore glyphStore;
@@ -40,7 +39,14 @@ public:
};
TEST(GlyphStore, LoadingSuccess) {
GlyphStoreTest test(MockFileSource::Success, "");
GlyphStoreTest test;
test.fileSource.glyphsResponse = [&] (const Resource& resource) {
EXPECT_EQ(Resource::Kind::Glyphs, resource.kind);
Response response;
response.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/glyphs.pbf"));
return response;
};
test.observer.glyphsError = [&] (const std::string&, const GlyphRange&, std::exception_ptr) {
FAIL();
@@ -64,12 +70,22 @@ TEST(GlyphStore, LoadingSuccess) {
}
TEST(GlyphStore, LoadingFail) {
GlyphStoreTest test(MockFileSource::RequestFail, "glyphs.pbf");
GlyphStoreTest test;
test.fileSource.glyphsResponse = [&] (const Resource&) {
Response response;
response.error = std::make_unique<Response::Error>(
Response::Error::Reason::Other,
"Failed by the test case");
return response;
};
test.observer.glyphsError = [&] (const std::string& fontStack, const GlyphRange& glyphRange, std::exception_ptr error) {
ASSERT_TRUE(error != nullptr);
ASSERT_EQ(fontStack, "Test Stack");
ASSERT_EQ(glyphRange, GlyphRange(0, 255));
EXPECT_EQ(fontStack, "Test Stack");
EXPECT_EQ(glyphRange, GlyphRange(0, 255));
EXPECT_TRUE(error != nullptr);
EXPECT_EQ(util::toString(error), "Failed by the test case");
auto stack = test.glyphStore.getFontStack("Test Stack");
ASSERT_TRUE(stack->getSDFs().empty());
@@ -85,12 +101,20 @@ TEST(GlyphStore, LoadingFail) {
}
TEST(GlyphStore, LoadingCorrupted) {
GlyphStoreTest test(MockFileSource::RequestWithCorruptedData, "glyphs.pbf");
GlyphStoreTest test;
test.fileSource.glyphsResponse = [&] (const Resource&) {
Response response;
response.data = std::make_unique<std::string>("CORRUPTED");
return response;
};
test.observer.glyphsError = [&] (const std::string& fontStack, const GlyphRange& glyphRange, std::exception_ptr error) {
ASSERT_TRUE(error != nullptr);
ASSERT_EQ(fontStack, "Test Stack");
ASSERT_EQ(glyphRange, GlyphRange(0, 255));
EXPECT_EQ(fontStack, "Test Stack");
EXPECT_EQ(glyphRange, GlyphRange(0, 255));
EXPECT_TRUE(error != nullptr);
EXPECT_EQ(util::toString(error), "pbf unknown field type exception");
auto stack = test.glyphStore.getFontStack("Test Stack");
ASSERT_TRUE(stack->getSDFs().empty());
@@ -106,36 +130,19 @@ TEST(GlyphStore, LoadingCorrupted) {
}
TEST(GlyphStore, LoadingCancel) {
GlyphStoreTest test(MockFileSource::Success, "glyphs.pbf");
GlyphStoreTest test;
test.fileSource.glyphsResponse = [&] (const Resource&) {
test.end();
return Response();
};
test.observer.glyphsLoaded = [&] (const std::string&, const GlyphRange&) {
FAIL() << "Should never be called";
};
test.fileSource.requestEnqueuedCallback = [&]{
test.end();
};
test.run(
"test/fixtures/resources/glyphs.pbf",
"Test Stack",
{{0, 255}});
}
TEST(GlyphStore, InvalidURL) {
GlyphStoreTest test(MockFileSource::Success, "");
test.observer.glyphsError = [&] (const std::string&, const GlyphRange&, std::exception_ptr error) {
ASSERT_TRUE(error != nullptr);
auto stack = test.glyphStore.getFontStack("Test Stack");
ASSERT_TRUE(stack->getSDFs().empty());
test.end();
};
test.run(
"foo bar",
"Test Stack",
{{0, 255}});
}

View File

@@ -1,62 +0,0 @@
#include "../fixtures/fixture_log_observer.hpp"
#include "../fixtures/mock_file_source.hpp"
#include "../fixtures/util.hpp"
#include <mbgl/map/map.hpp>
#include <mbgl/platform/default/headless_display.hpp>
#include <mbgl/platform/default/headless_view.hpp>
#include <mbgl/util/async_task.hpp>
#include <mbgl/util/io.hpp>
#include <mbgl/util/run_loop.hpp>
using namespace mbgl;
class PendingResources : public ::testing::TestWithParam<std::string> {
};
// This test will load a Style but one of the resources requested will not be
// replied immediately like the others. We get an notification by the
// MockFileSource when some resource is artificially delayed and we destroy
// the Map object after that. The idea here is to test if these pending requests
// are getting canceled correctly if on shutdown.
TEST_P(PendingResources, DeleteMapObjectWithPendingRequest) {
util::RunLoop loop;
auto display = std::make_shared<mbgl::HeadlessDisplay>();
HeadlessView view(display, 1, 1000, 1000);
MockFileSource fileSource(MockFileSource::Success, GetParam());
std::unique_ptr<Map> map = std::make_unique<Map>(view, fileSource, MapMode::Still);
util::AsyncTask endTest([&map, &loop] {
map.reset();
loop.stop();
});
fileSource.requestEnqueuedCallback = [&endTest] { endTest.send(); };
const std::string style = util::read_file("test/fixtures/resources/style.json");
map->setStyleJSON(style, ".");
map->renderStill([](std::exception_ptr, PremultipliedImage&&) {
EXPECT_TRUE(false) << "Should never happen.";
});
loop.run();
}
// In the test data below, "sprite" will match both "sprite.json" and "sprite.png" and cause two
// requests to be canceled. "resources" will match everything but in practice will only test the
// cancellation of the sprites and "source_*.json" because we only load the rest after "source_*.json"
// gets parsed.
INSTANTIATE_TEST_CASE_P(Style, PendingResources,
::testing::Values(
"source_raster.json",
"source_vector.json",
"sprite.json",
"sprite.png",
"sprite",
"raster.png",
"vector.pbf",
"glyphs.pbf",
"resources"));

View File

@@ -1,283 +0,0 @@
#include "../fixtures/fixture_log_observer.hpp"
#include "../fixtures/util.hpp"
#include "../fixtures/mock_file_source.hpp"
#include "../fixtures/mock_view.hpp"
#include "../fixtures/stub_style_observer.hpp"
#include <mbgl/map/map_data.hpp>
#include <mbgl/map/transform.hpp>
#include <mbgl/style/style.hpp>
#include <mbgl/util/exception.hpp>
#include <mbgl/util/io.hpp>
#include <mbgl/util/run_loop.hpp>
#include <mbgl/util/texture_pool.hpp>
#include <mbgl/util/thread.hpp>
#include <mbgl/util/string.hpp>
using namespace mbgl;
class ResourceLoadingTest {
public:
ResourceLoadingTest(MockFileSource::Type type, const std::string& resource)
: fileSource(type, resource) {}
util::ThreadContext context { "Map", util::ThreadType::Map, util::ThreadPriority::Regular };
util::RunLoop loop;
MockFileSource fileSource;
StubStyleObserver observer;
std::function<void ()> onFullyLoaded;
MapData data { MapMode::Still, GLContextMode::Unique, 1.0 };
MockView view;
Transform transform { view, ConstrainMode::HeightOnly };
TexturePool texturePool;
Style style { data };
void run(const std::string& stylePath) {
// Squelch logging.
Log::setObserver(std::make_unique<Log::NullObserver>());
util::ThreadContext::Set(&context);
util::ThreadContext::setFileSource(&fileSource);
observer.resourceLoaded = [&] () {
style.update(transform.getState(), texturePool);
if (style.isLoaded() && onFullyLoaded) {
onFullyLoaded();
}
};
transform.resize({{ 512, 512 }});
transform.setLatLngZoom({0, 0}, 0);
style.setObserver(&observer);
style.setJSON(util::read_file(stylePath), "");
style.cascade();
style.recalculate(0);
loop.run();
}
void end() {
loop.stop();
}
};
TEST(ResourceLoading, Success) {
ResourceLoadingTest test(MockFileSource::Success, "");
test.observer.resourceError = [&] (std::exception_ptr error) {
FAIL() << util::toString(error);
};
test.onFullyLoaded = [&] () {
SUCCEED();
test.end();
};
test.run("test/fixtures/resources/style.json");
}
TEST(ResourceLoading, RasterSourceFail) {
ResourceLoadingTest test(MockFileSource::RequestFail, "source_raster.json");
test.observer.sourceError = [&] (Source& source, std::exception_ptr error) {
EXPECT_EQ(source.id, "rastersource");
EXPECT_EQ(util::toString(error), "Failed by the test case");
test.end();
};
test.run("test/fixtures/resources/style.json");
}
TEST(ResourceLoading, VectorSourceFail) {
ResourceLoadingTest test(MockFileSource::RequestFail, "source_vector.json");
test.observer.sourceError = [&] (Source& source, std::exception_ptr error) {
EXPECT_EQ(source.id, "vectorsource");
EXPECT_EQ(util::toString(error), "Failed by the test case");
test.end();
};
test.run("test/fixtures/resources/style.json");
}
TEST(ResourceLoading, SpriteJSONFail) {
ResourceLoadingTest test(MockFileSource::RequestFail, "sprite.json");
test.observer.spriteError = [&] (std::exception_ptr error) {
EXPECT_EQ(util::toString(error), "Failed by the test case");
test.end();
};
test.run("test/fixtures/resources/style.json");
}
TEST(ResourceLoading, SpriteImageFail) {
ResourceLoadingTest test(MockFileSource::RequestFail, "sprite.png");
test.observer.spriteError = [&] (std::exception_ptr error) {
EXPECT_EQ(util::toString(error), "Failed by the test case");
test.end();
};
test.run("test/fixtures/resources/style.json");
}
TEST(ResourceLoading, RasterTileFail) {
ResourceLoadingTest test(MockFileSource::RequestFail, "raster.png");
test.observer.tileError = [&] (Source& source, const TileID& tileID, std::exception_ptr error) {
EXPECT_EQ(source.id, "rastersource");
EXPECT_EQ(std::string(tileID), "0/0/0");
EXPECT_EQ(util::toString(error), "Failed by the test case");
test.end();
};
test.run("test/fixtures/resources/style.json");
}
TEST(ResourceLoading, VectorTileFail) {
ResourceLoadingTest test(MockFileSource::RequestFail, "vector.pbf");
test.observer.tileError = [&] (Source& source, const TileID& tileID, std::exception_ptr error) {
EXPECT_EQ(source.id, "vectorsource");
EXPECT_EQ(std::string(tileID), "0/0/0");
EXPECT_EQ(util::toString(error), "Failed by the test case");
test.end();
};
test.run("test/fixtures/resources/style.json");
}
TEST(ResourceLoading, GlyphsFail) {
ResourceLoadingTest test(MockFileSource::RequestFail, "glyphs.pbf");
test.observer.glyphsError = [&] (const std::string& fontStack, const GlyphRange&, std::exception_ptr error) {
EXPECT_EQ(fontStack, "Open Sans Regular,Arial Unicode MS Regular");
EXPECT_EQ(util::toString(error), "Failed by the test case");
test.end();
};
test.run("test/fixtures/resources/style.json");
}
TEST(ResourceLoading, RasterSourceCorrupt) {
ResourceLoadingTest test(MockFileSource::RequestWithCorruptedData, "source_raster.json");
test.observer.sourceError = [&] (Source& source, std::exception_ptr error) {
EXPECT_EQ(source.id, "rastersource");
EXPECT_EQ(util::toString(error), "0 - Invalid value.");
test.end();
};
test.run("test/fixtures/resources/style.json");
}
TEST(ResourceLoading, VectorSourceCorrupt) {
ResourceLoadingTest test(MockFileSource::RequestWithCorruptedData, "source_vector.json");
test.observer.sourceError = [&] (Source& source, std::exception_ptr error) {
EXPECT_EQ(source.id, "vectorsource");
EXPECT_EQ(util::toString(error), "0 - Invalid value.");
test.end();
};
test.run("test/fixtures/resources/style.json");
}
TEST(ResourceLoading, SpriteJSONCorrupt) {
ResourceLoadingTest test(MockFileSource::RequestWithCorruptedData, "sprite.json");
test.observer.spriteError = [&] (std::exception_ptr error) {
EXPECT_EQ(util::toString(error), "Failed to parse JSON: Invalid value. at offset 0");
test.end();
};
test.run("test/fixtures/resources/style.json");
}
TEST(ResourceLoading, SpriteImageCorrupt) {
ResourceLoadingTest test(MockFileSource::RequestWithCorruptedData, "sprite.png");
test.observer.spriteError = [&] (std::exception_ptr error) {
EXPECT_TRUE(bool(error));
// Not asserting on platform-specific error text.
test.end();
};
test.run("test/fixtures/resources/style.json");
}
TEST(ResourceLoading, RasterTileCorrupt) {
ResourceLoadingTest test(MockFileSource::RequestWithCorruptedData, "raster.png");
test.observer.tileError = [&] (Source& source, const TileID& tileID, std::exception_ptr error) {
EXPECT_EQ(source.id, "rastersource");
EXPECT_EQ(std::string(tileID), "0/0/0");
EXPECT_TRUE(bool(error));
// Not asserting on platform-specific error text.
test.end();
};
test.run("test/fixtures/resources/style.json");
}
TEST(ResourceLoading, VectorTileCorrupt) {
ResourceLoadingTest test(MockFileSource::RequestWithCorruptedData, "vector.pbf");
test.observer.tileError = [&] (Source& source, const TileID& tileID, std::exception_ptr error) {
EXPECT_EQ(source.id, "vectorsource");
EXPECT_EQ(std::string(tileID), "0/0/0");
EXPECT_EQ(util::toString(error), "pbf unknown field type exception");
test.end();
};
test.run("test/fixtures/resources/style.json");
}
TEST(ResourceLoading, GlyphsCorrupt) {
ResourceLoadingTest test(MockFileSource::RequestWithCorruptedData, "glyphs.pbf");
test.observer.glyphsError = [&] (const std::string& fontStack, const GlyphRange&, std::exception_ptr error) {
EXPECT_EQ(fontStack, "Open Sans Regular,Arial Unicode MS Regular");
EXPECT_EQ(util::toString(error), "pbf unknown field type exception");
test.end();
};
test.run("test/fixtures/resources/style.json");
}
TEST(ResourceLoading, UnusedSource) {
ResourceLoadingTest test(MockFileSource::Success, "");
test.onFullyLoaded = [&] () {
Source *usedSource = test.style.getSource("usedsource");
EXPECT_TRUE(usedSource);
EXPECT_TRUE(usedSource->isLoaded());
Source *unusedSource = test.style.getSource("unusedsource");
EXPECT_TRUE(unusedSource);
EXPECT_FALSE(unusedSource->isLoaded());
test.end();
};
test.run("test/fixtures/resources/style-unused-sources.json");
}
TEST(ResourceLoading, UnusedSourceActiveViaClassUpdate) {
ResourceLoadingTest test(MockFileSource::Success, "");
test.data.addClass("visible");
test.onFullyLoaded = [&] () {
Source *unusedSource = test.style.getSource("unusedsource");
EXPECT_TRUE(unusedSource);
EXPECT_TRUE(unusedSource->isLoaded());
test.end();
};
test.run("test/fixtures/resources/style-unused-sources.json");
}

290
test/style/source.cpp Normal file
View File

@@ -0,0 +1,290 @@
#include "../fixtures/util.hpp"
#include "../fixtures/stub_file_source.hpp"
#include "../fixtures/mock_view.hpp"
#include "../fixtures/stub_style_observer.hpp"
#include <mbgl/map/source.hpp>
#include <mbgl/util/run_loop.hpp>
#include <mbgl/util/string.hpp>
#include <mbgl/util/io.hpp>
#include <mbgl/platform/log.hpp>
#include <mbgl/map/transform.hpp>
#include <mbgl/map/map_data.hpp>
#include <mbgl/util/worker.hpp>
#include <mbgl/util/texture_pool.hpp>
#include <mbgl/style/style.hpp>
#include <mbgl/style/style_update_parameters.hpp>
#include <mbgl/layer/line_layer.hpp>
using namespace mbgl;
class SourceTest {
public:
util::ThreadContext context { "Map", util::ThreadType::Map, util::ThreadPriority::Regular };
util::RunLoop loop;
StubFileSource fileSource;
StubStyleObserver observer;
MockView view;
Transform transform { view, ConstrainMode::HeightOnly };
TransformState transformState;
Worker worker { 1 };
TexturePool texturePool;
MapData mapData { MapMode::Still, GLContextMode::Unique, 1.0 };
Style style { mapData };
StyleUpdateParameters updateParameters {
1.0,
MapDebugOptions(),
TimePoint(),
transformState,
worker,
texturePool,
true,
MapMode::Continuous,
mapData,
style
};
SourceTest() {
// Squelch logging.
Log::setObserver(std::make_unique<Log::NullObserver>());
util::ThreadContext::Set(&context);
util::ThreadContext::setFileSource(&fileSource);
transform.resize({{ 512, 512 }});
transform.setLatLngZoom({0, 0}, 0);
transformState = transform.getState();
}
void run() {
loop.run();
}
void end() {
loop.stop();
}
};
TEST(Source, LoadingFail) {
SourceTest test;
test.fileSource.sourceResponse = [&] (const Resource& resource) {
EXPECT_EQ("url", resource.url);
Response response;
response.error = std::make_unique<Response::Error>(
Response::Error::Reason::Other,
"Failed by the test case");
return response;
};
test.observer.sourceError = [&] (Source& source, std::exception_ptr error) {
EXPECT_EQ("url", source.url);
EXPECT_EQ("Failed by the test case", util::toString(error));
test.end();
};
Source source(SourceType::Vector, "source", "url", 512, nullptr, nullptr);
source.setObserver(&test.observer);
source.load();
test.run();
}
TEST(Source, LoadingCorrupt) {
SourceTest test;
test.fileSource.sourceResponse = [&] (const Resource& resource) {
EXPECT_EQ("url", resource.url);
Response response;
response.data = std::make_unique<std::string>("CORRUPTED");
return response;
};
test.observer.sourceError = [&] (Source& source, std::exception_ptr error) {
EXPECT_EQ("url", source.url);
EXPECT_EQ("0 - Invalid value.", util::toString(error));
test.end();
};
Source source(SourceType::Vector, "source", "url", 512, nullptr, nullptr);
source.setObserver(&test.observer);
source.load();
test.run();
}
TEST(Source, RasterTileFail) {
SourceTest test;
test.fileSource.tileResponse = [&] (const Resource&) {
Response response;
response.error = std::make_unique<Response::Error>(
Response::Error::Reason::Other,
"Failed by the test case");
return response;
};
test.observer.tileError = [&] (Source& source, const TileID& tileID, std::exception_ptr error) {
EXPECT_EQ(SourceType::Raster, source.type);
EXPECT_EQ("0/0/0", std::string(tileID));
EXPECT_EQ("Failed by the test case", util::toString(error));
test.end();
};
auto info = std::make_unique<SourceInfo>();
info->tiles = { "tiles" };
Source source(SourceType::Raster, "source", "", 512, std::move(info), nullptr);
source.setObserver(&test.observer);
source.load();
source.update(test.updateParameters);
test.run();
}
TEST(Source, VectorTileFail) {
SourceTest test;
test.fileSource.tileResponse = [&] (const Resource&) {
Response response;
response.error = std::make_unique<Response::Error>(
Response::Error::Reason::Other,
"Failed by the test case");
return response;
};
test.observer.tileError = [&] (Source& source, const TileID& tileID, std::exception_ptr error) {
EXPECT_EQ(SourceType::Vector, source.type);
EXPECT_EQ("0/0/0", std::string(tileID));
EXPECT_EQ("Failed by the test case", util::toString(error));
test.end();
};
auto info = std::make_unique<SourceInfo>();
info->tiles = { "tiles" };
Source source(SourceType::Vector, "source", "", 512, std::move(info), nullptr);
source.setObserver(&test.observer);
source.load();
source.update(test.updateParameters);
test.run();
}
TEST(Source, RasterTileCorrupt) {
SourceTest test;
test.fileSource.tileResponse = [&] (const Resource&) {
Response response;
response.data = std::make_unique<std::string>("CORRUPTED");
return response;
};
test.observer.tileError = [&] (Source& source, const TileID& tileID, std::exception_ptr error) {
EXPECT_EQ(source.type, SourceType::Raster);
EXPECT_EQ(std::string(tileID), "0/0/0");
EXPECT_TRUE(bool(error));
// Not asserting on platform-specific error text.
test.end();
};
auto info = std::make_unique<SourceInfo>();
info->tiles = { "tiles" };
Source source(SourceType::Raster, "source", "", 512, std::move(info), nullptr);
source.setObserver(&test.observer);
source.load();
source.update(test.updateParameters);
test.run();
}
TEST(Source, VectorTileCorrupt) {
SourceTest test;
test.fileSource.tileResponse = [&] (const Resource&) {
Response response;
response.data = std::make_unique<std::string>("CORRUPTED");
return response;
};
test.observer.tileError = [&] (Source& source, const TileID& tileID, std::exception_ptr error) {
EXPECT_EQ(source.type, SourceType::Vector);
EXPECT_EQ(std::string(tileID), "0/0/0");
EXPECT_EQ(util::toString(error), "pbf unknown field type exception");
test.end();
};
// Need to have at least one layer that uses the source.
auto layer = std::make_unique<LineLayer>();
layer->source = "source";
layer->sourceLayer = "water";
test.style.addLayer(std::move(layer));
auto info = std::make_unique<SourceInfo>();
info->tiles = { "tiles" };
Source source(SourceType::Vector, "source", "", 512, std::move(info), nullptr);
source.setObserver(&test.observer);
source.load();
source.update(test.updateParameters);
test.run();
}
TEST(Source, RasterTileCancel) {
SourceTest test;
test.fileSource.tileResponse = [&] (const Resource&) {
test.end();
return Response();
};
test.observer.tileLoaded = [&] (Source&, const TileID&, bool) {
FAIL() << "Should never be called";
};
test.observer.tileError = [&] (Source&, const TileID&, std::exception_ptr) {
FAIL() << "Should never be called";
};
auto info = std::make_unique<SourceInfo>();
info->tiles = { "tiles" };
Source source(SourceType::Raster, "source", "", 512, std::move(info), nullptr);
source.setObserver(&test.observer);
source.load();
source.update(test.updateParameters);
test.run();
}
TEST(Source, VectorTileCancel) {
SourceTest test;
test.fileSource.tileResponse = [&] (const Resource&) {
test.end();
return Response();
};
test.observer.tileLoaded = [&] (Source&, const TileID&, bool) {
FAIL() << "Should never be called";
};
test.observer.tileError = [&] (Source&, const TileID&, std::exception_ptr) {
FAIL() << "Should never be called";
};
auto info = std::make_unique<SourceInfo>();
info->tiles = { "tiles" };
Source source(SourceType::Vector, "source", "", 512, std::move(info), nullptr);
source.setObserver(&test.observer);
source.load();
source.update(test.updateParameters);
test.run();
}

45
test/style/style.cpp Normal file
View File

@@ -0,0 +1,45 @@
#include "../fixtures/util.hpp"
#include <mbgl/map/map_data.hpp>
#include <mbgl/style/style.hpp>
#include <mbgl/util/io.hpp>
using namespace mbgl;
TEST(Style, UnusedSource) {
util::ThreadContext context { "Map", util::ThreadType::Map, util::ThreadPriority::Regular };
util::ThreadContext::Set(&context);
MapData data { MapMode::Still, GLContextMode::Unique, 1.0 };
Style style { data };
style.setJSON(util::read_file("test/fixtures/resources/style-unused-sources.json"), "");
style.cascade();
style.recalculate(0);
Source *usedSource = style.getSource("usedsource");
EXPECT_TRUE(usedSource);
EXPECT_TRUE(usedSource->isLoaded());
Source *unusedSource = style.getSource("unusedsource");
EXPECT_TRUE(unusedSource);
EXPECT_FALSE(unusedSource->isLoaded());
}
TEST(Style, UnusedSourceActiveViaClassUpdate) {
util::ThreadContext context { "Map", util::ThreadType::Map, util::ThreadPriority::Regular };
util::ThreadContext::Set(&context);
MapData data { MapMode::Still, GLContextMode::Unique, 1.0 };
Style style { data };
data.addClass("visible");
style.setJSON(util::read_file("test/fixtures/resources/style-unused-sources.json"), "");
style.cascade();
style.recalculate(0);
Source *unusedSource = style.getSource("unusedsource");
EXPECT_TRUE(unusedSource);
EXPECT_TRUE(unusedSource->isLoaded());
}

View File

@@ -28,8 +28,8 @@
],
'sources': [
'fixtures/main.cpp',
'fixtures/mock_file_source.cpp',
'fixtures/mock_file_source.hpp',
'fixtures/stub_file_source.cpp',
'fixtures/stub_file_source.hpp',
'fixtures/mock_view.hpp',
'fixtures/util.hpp',
'fixtures/util.cpp',
@@ -90,8 +90,8 @@
'storage/resource.cpp',
'style/glyph_store.cpp',
'style/pending_resources.cpp',
'style/resource_loading.cpp',
'style/source.cpp',
'style/style.cpp',
'style/style_layer.cpp',
'sprite/sprite_atlas.cpp',