diff options
author | John Firebaugh <john.firebaugh@gmail.com> | 2015-12-22 15:10:24 -0800 |
---|---|---|
committer | John Firebaugh <john.firebaugh@gmail.com> | 2015-12-23 12:50:42 -0800 |
commit | 16de579d7cfc2960793cbcb5e95741f22ab73768 (patch) | |
tree | b4c3b7651f605e3d3dd61b469f61036bd2c4dcc3 | |
parent | 7bd4745cf10c504a4899a37016e87bce45e51472 (diff) | |
download | qtlocation-mapboxgl-16de579d7cfc2960793cbcb5e95741f22ab73768.tar.gz |
[core] Rationalize error handling for resource loading
* Standardize on std::exception_ptr as the error representation
(fixes #2854).
* Don't format textual strings at the error source; pass on the
constituent data via observer method parameters instead.
* Use the null object pattern to simplify observer notification code.
* Further refactoring for ResourceLoading tests.
28 files changed, 402 insertions, 469 deletions
diff --git a/include/mbgl/util/exception.hpp b/include/mbgl/util/exception.hpp index 06098840a8..b2214b36b8 100644 --- a/include/mbgl/util/exception.hpp +++ b/include/mbgl/util/exception.hpp @@ -16,11 +16,6 @@ struct SpriteImageException : Exception { inline SpriteImageException(const std::string &msg) : Exception(msg) {} }; -struct GlyphRangeLoadingException : Exception { - inline GlyphRangeLoadingException(const char *msg) : Exception(msg) {} - inline GlyphRangeLoadingException(const std::string &msg) : Exception(msg) {} -}; - struct MisuseException : Exception { inline MisuseException(const char *msg) : Exception(msg) {} inline MisuseException(const std::string &msg) : Exception(msg) {} @@ -31,21 +26,6 @@ struct ShaderException : Exception { inline ShaderException(const std::string &msg) : Exception(msg) {} }; -struct SourceLoadingException : Exception { - inline SourceLoadingException(const char *msg) : Exception(msg) {} - inline SourceLoadingException(const std::string &msg) : Exception(msg) {} -}; - -struct SpriteLoadingException : Exception { - inline SpriteLoadingException(const char *msg) : Exception(msg) {} - inline SpriteLoadingException(const std::string &msg) : Exception(msg) {} -}; - -struct TileLoadingException : Exception { - inline TileLoadingException(const char *msg) : Exception(msg) {} - inline TileLoadingException(const std::string &msg) : Exception(msg) {} -}; - } // namespace util } // namespace mbgl diff --git a/platform/node/test/js/map.test.js b/platform/node/test/js/map.test.js index 9c983828ff..df7b5f8706 100644 --- a/platform/node/test/js/map.test.js +++ b/platform/node/test/js/map.test.js @@ -224,8 +224,6 @@ test('Map', function(t) { map.release(); t.ok(err, 'returns error'); - t.ok(err.message.match(/Failed to load/), 'error text matches'); - t.end(); }); }); diff --git a/src/mbgl/map/map_context.cpp b/src/mbgl/map/map_context.cpp index 75ca188fbb..f2c17824c3 100644 --- a/src/mbgl/map/map_context.cpp +++ b/src/mbgl/map/map_context.cpp @@ -311,16 +311,13 @@ void MapContext::onLowMemory() { style->onLowMemory(); asyncInvalidate.send(); } - -void MapContext::onTileDataChanged() { - assert(util::ThreadContext::currentlyOn(util::ThreadType::Map)); + +void MapContext::onResourceLoaded() { updateFlags |= Update::Repaint; asyncUpdate.send(); } -void MapContext::onResourceLoadingFailed(std::exception_ptr error) { - assert(util::ThreadContext::currentlyOn(util::ThreadType::Map)); - +void MapContext::onResourceError(std::exception_ptr error) { if (data.mode == MapMode::Still && callback) { callback(error, {}); callback = nullptr; diff --git a/src/mbgl/map/map_context.hpp b/src/mbgl/map/map_context.hpp index 24459f270a..012da785c9 100644 --- a/src/mbgl/map/map_context.hpp +++ b/src/mbgl/map/map_context.hpp @@ -65,14 +65,12 @@ public: void onLowMemory(); void cleanup(); - - // Style::Observer implementation. - void onTileDataChanged() override; - void onResourceLoadingFailed(std::exception_ptr error) override; - void dumpDebugLogs() const; private: + void onResourceLoaded() override; + void onResourceError(std::exception_ptr) override; + // Update the state indicated by the accumulated Update flags, then render. void update(); diff --git a/src/mbgl/map/raster_tile_data.cpp b/src/mbgl/map/raster_tile_data.cpp index 7ef13301e8..5c85cbd034 100644 --- a/src/mbgl/map/raster_tile_data.cpp +++ b/src/mbgl/map/raster_tile_data.cpp @@ -6,8 +6,6 @@ #include <mbgl/util/worker.hpp> #include <mbgl/util/work_request.hpp> -#include <sstream> - using namespace mbgl; RasterTileData::RasterTileData(const TileID& id_, @@ -41,9 +39,7 @@ void RasterTileData::request(float pixelRatio, if (res.error->reason == Response::Error::Reason::NotFound) { state = State::parsed; } else { - std::stringstream message; - message << "Failed to load [" << url << "]: " << res.error->message; - error = message.str(); + error = std::make_exception_ptr(std::runtime_error(res.error->message)); state = State::obsolete; } callback(); @@ -68,9 +64,7 @@ void RasterTileData::request(float pixelRatio, state = State::parsed; bucket = std::move(result.get<std::unique_ptr<Bucket>>()); } else { - std::stringstream message; - message << "Failed to parse [" << std::string(id) << "]: " << result.get<std::string>(); - error = message.str(); + error = result.get<std::exception_ptr>(); state = State::obsolete; } diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index f09d6db85a..a814e484ff 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -31,6 +31,7 @@ #include <rapidjson/error/en.h> #include <algorithm> +#include <sstream> namespace mbgl { @@ -75,9 +76,7 @@ void Source::load() { req = nullptr; if (res.error) { - std::stringstream message; - message << "Failed to load [" << info.url << "]: " << res.error->message; - emitSourceLoadingFailed(message.str()); + observer->onSourceError(*this, std::make_exception_ptr(std::runtime_error(res.error->message))); return; } @@ -86,8 +85,8 @@ void Source::load() { if (d.HasParseError()) { std::stringstream message; - message << "Failed to parse [" << info.url << "]: " << d.GetErrorOffset() << " - " << rapidjson::GetParseError_En(d.GetParseError()); - emitSourceLoadingFailed(message.str()); + message << d.GetErrorOffset() << " - " << rapidjson::GetParseError_En(d.GetParseError()); + observer->onSourceError(*this, std::make_exception_ptr(std::runtime_error(message.str()))); return; } @@ -98,8 +97,7 @@ void Source::load() { } loaded = true; - - emitSourceLoaded(); + observer->onSourceLoaded(*this); }); } @@ -166,8 +164,8 @@ bool Source::handlePartialTile(const TileID& id, Worker&) { return true; } - return tileData->parsePending([this]() { - emitTileLoaded(false); + return tileData->parsePending([this, id]() { + observer->onTileLoaded(*this, id, false); }); } @@ -464,12 +462,12 @@ void Source::onLowMemory() { cache.clear(); } -void Source::setObserver(Observer* observer) { - observer_ = observer; +void Source::setObserver(Observer* observer_) { + observer = observer_; } -void Source::tileLoadingCompleteCallback(const TileID& normalized_id, const TransformState& transformState, bool collisionDebug) { - auto it = tileDataMap.find(normalized_id); +void Source::tileLoadingCompleteCallback(const TileID& id, const TransformState& transformState, bool collisionDebug) { + auto it = tileDataMap.find(id); if (it == tileDataMap.end()) { return; } @@ -479,43 +477,13 @@ void Source::tileLoadingCompleteCallback(const TileID& normalized_id, const Tran return; } - if (tileData->getState() == TileData::State::obsolete && !tileData->getError().empty()) { - emitTileLoadingFailed(tileData->getError()); + if (tileData->getState() == TileData::State::obsolete && tileData->getError()) { + observer->onTileError(*this, id, tileData->getError()); return; } tileData->redoPlacement({ transformState.getAngle(), transformState.getPitch(), collisionDebug }); - emitTileLoaded(true); -} - -void Source::emitSourceLoaded() { - if (observer_) { - observer_->onSourceLoaded(); - } -} - -void Source::emitSourceLoadingFailed(const std::string& message) { - if (!observer_) { - return; - } - - auto error = std::make_exception_ptr(util::SourceLoadingException(message)); - observer_->onSourceLoadingFailed(error); -} - -void Source::emitTileLoaded(bool isNewTile) { - if (observer_) { - observer_->onTileLoaded(isNewTile); - } -} - -void Source::emitTileLoadingFailed(const std::string& message) { - if (!observer_) { - return; - } - - auto error = std::make_exception_ptr(util::TileLoadingException(message)); - observer_->onTileLoadingFailed(error); + observer->onTileLoaded(*this, id, true); } void Source::dumpDebugLogs() const { diff --git a/src/mbgl/map/source.hpp b/src/mbgl/map/source.hpp index dc939113b2..36613c2052 100644 --- a/src/mbgl/map/source.hpp +++ b/src/mbgl/map/source.hpp @@ -25,11 +25,11 @@ public: public: virtual ~Observer() = default; - virtual void onSourceLoaded() = 0; - virtual void onSourceLoadingFailed(std::exception_ptr error) = 0; + virtual void onSourceLoaded(Source&) {}; + virtual void onSourceError(Source&, std::exception_ptr) {}; - virtual void onTileLoaded(bool isNewTile) = 0; - virtual void onTileLoadingFailed(std::exception_ptr error) = 0; + virtual void onTileLoaded(Source&, const TileID&, bool /* isNewTile */) {}; + virtual void onTileError(Source&, const TileID&, std::exception_ptr) {}; }; Source(); @@ -64,12 +64,6 @@ public: private: void tileLoadingCompleteCallback(const TileID&, const TransformState&, bool collisionDebug); - - void emitSourceLoaded(); - void emitSourceLoadingFailed(const std::string& message); - void emitTileLoaded(bool isNewTile); - void emitTileLoadingFailed(const std::string& message); - bool handlePartialTile(const TileID &id, Worker &worker); bool findLoadedChildren(const TileID& id, int32_t maxCoveringZoom, std::forward_list<TileID>& retain); void findLoadedParent(const TileID& id, int32_t minCoveringZoom, std::forward_list<TileID>& retain); @@ -82,7 +76,6 @@ private: double getZoom(const TransformState &state) const; - // Stores the time when this source was most recently updated. TimePoint updated = TimePoint::min(); @@ -92,7 +85,9 @@ private: TileCache cache; std::unique_ptr<FileRequest> req; - Observer* observer_ = nullptr; + + Observer nullObserver; + Observer* observer = &nullObserver; }; } // namespace mbgl diff --git a/src/mbgl/map/tile_data.cpp b/src/mbgl/map/tile_data.cpp index bba7d7fe22..d9fd3f0047 100644 --- a/src/mbgl/map/tile_data.cpp +++ b/src/mbgl/map/tile_data.cpp @@ -1,5 +1,6 @@ #include <mbgl/map/tile_data.hpp> #include <mbgl/renderer/debug_bucket.hpp> +#include <mbgl/util/string.hpp> namespace mbgl { @@ -26,7 +27,10 @@ const char* TileData::StateToString(const State state) { void TileData::dumpDebugLogs() const { Log::Info(Event::General, "TileData::id: %s", std::string(id).c_str()); Log::Info(Event::General, "TileData::state: %s", TileData::StateToString(state)); - Log::Info(Event::General, "TileData::error: %s", error.c_str()); + + if (error) { + Log::Info(Event::General, "TileData::error: %s", util::toString(error).c_str()); + } } } // namespace mbgl diff --git a/src/mbgl/map/tile_data.hpp b/src/mbgl/map/tile_data.hpp index ed3426cf80..2bb11054a6 100644 --- a/src/mbgl/map/tile_data.hpp +++ b/src/mbgl/map/tile_data.hpp @@ -88,7 +88,7 @@ public: return state; } - std::string getError() const { + std::exception_ptr getError() const { return error; } @@ -103,7 +103,7 @@ public: protected: std::atomic<State> state; - std::string error; + std::exception_ptr error; }; } // namespace mbgl diff --git a/src/mbgl/map/tile_worker.hpp b/src/mbgl/map/tile_worker.hpp index 14f996c1b0..d25b6e50f2 100644 --- a/src/mbgl/map/tile_worker.hpp +++ b/src/mbgl/map/tile_worker.hpp @@ -33,8 +33,9 @@ public: std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets; }; -using TileParseResult = mapbox::util::variant<TileParseResultBuckets, // success - std::string>; // error +using TileParseResult = mapbox::util::variant< + TileParseResultBuckets, // success + std::exception_ptr>; // error class TileWorker : public util::noncopyable { public: diff --git a/src/mbgl/map/vector_tile.cpp b/src/mbgl/map/vector_tile.cpp index 00307f04a9..39e31da0d4 100644 --- a/src/mbgl/map/vector_tile.cpp +++ b/src/mbgl/map/vector_tile.cpp @@ -5,7 +5,6 @@ #include <mbgl/storage/file_source.hpp> #include <mbgl/util/thread_context.hpp> -#include <sstream> #include <utility> namespace mbgl { @@ -194,9 +193,7 @@ std::unique_ptr<FileRequest> VectorTileMonitor::monitorTile(const GeometryTileMo callback(nullptr, nullptr, res.modified, res.expires); return; } else { - std::stringstream message; - message << "Failed to load [" << url << "]: " << res.error->message; - callback(std::make_exception_ptr(std::runtime_error(message.str())), nullptr, res.modified, res.expires); + callback(std::make_exception_ptr(std::runtime_error(res.error->message)), nullptr, res.modified, res.expires); return; } } diff --git a/src/mbgl/map/vector_tile_data.cpp b/src/mbgl/map/vector_tile_data.cpp index 708ded02cc..3fe423b502 100644 --- a/src/mbgl/map/vector_tile_data.cpp +++ b/src/mbgl/map/vector_tile_data.cpp @@ -6,8 +6,6 @@ #include <mbgl/style/style.hpp> #include <mbgl/storage/file_source.hpp> -#include <sstream> - namespace mbgl { VectorTileData::VectorTileData(const TileID& id_, @@ -32,14 +30,10 @@ VectorTileData::VectorTileData(const TileID& id_, Seconds modified_, Seconds expires_) { if (err) { - try { - std::rethrow_exception(err); - } catch (const std::exception& e) { - error = e.what(); - state = State::obsolete; - callback(); - return; - } + error = err; + state = State::obsolete; + callback(); + return; } if (!tile) { @@ -86,9 +80,7 @@ VectorTileData::VectorTileData(const TileID& id_, redoPlacement(); } } else { - std::stringstream message; - message << "Failed to parse [" << std::string(id) << "]: " << result.get<std::string>(); - error = message.str(); + error = result.get<std::exception_ptr>(); state = State::obsolete; } @@ -130,9 +122,7 @@ bool VectorTileData::parsePending(std::function<void()> callback) { redoPlacement(); } } else { - std::stringstream message; - message << "Failed to parse [" << std::string(id) << "]: " << result.get<std::string>(); - error = message.str(); + error = result.get<std::exception_ptr>(); state = State::obsolete; } diff --git a/src/mbgl/sprite/sprite_parser.cpp b/src/mbgl/sprite/sprite_parser.cpp index 59dce93241..1aa95310c5 100644 --- a/src/mbgl/sprite/sprite_parser.cpp +++ b/src/mbgl/sprite/sprite_parser.cpp @@ -106,7 +106,7 @@ SpriteParseResult parseSprite(const std::string& image, const std::string& json) try { raster = decodeImage(image); } catch (...) { - return std::string("Could not parse sprite image"); + return std::current_exception(); } JSDocument doc; @@ -115,9 +115,9 @@ SpriteParseResult parseSprite(const std::string& image, const std::string& json) if (doc.HasParseError()) { std::stringstream message; message << "Failed to parse JSON: " << rapidjson::GetParseError_En(doc.GetParseError()) << " at offset " << doc.GetErrorOffset(); - return message.str(); + return std::make_exception_ptr(std::runtime_error(message.str())); } else if (!doc.IsObject()) { - return std::string("Sprite JSON root must be an object"); + return std::make_exception_ptr(std::runtime_error("Sprite JSON root must be an object")); } else { for (JSValue::ConstMemberIterator itr = doc.MemberBegin(); itr != doc.MemberEnd(); ++itr) { const std::string name = { itr->name.GetString(), itr->name.GetStringLength() }; diff --git a/src/mbgl/sprite/sprite_parser.hpp b/src/mbgl/sprite/sprite_parser.hpp index 468ba13c53..36e8947992 100644 --- a/src/mbgl/sprite/sprite_parser.hpp +++ b/src/mbgl/sprite/sprite_parser.hpp @@ -30,8 +30,8 @@ using Sprites = std::map<std::string, SpriteImagePtr>; using SpriteParseResult = mapbox::util::variant< - Sprites, // success - std::string>; // error + Sprites, // success + std::exception_ptr>; // error // Parses an image and an associated JSON file and returns the sprite objects. SpriteParseResult parseSprite(const std::string& image, const std::string& json); diff --git a/src/mbgl/sprite/sprite_store.cpp b/src/mbgl/sprite/sprite_store.cpp index f54f3f18fd..0c3a7d3ba0 100644 --- a/src/mbgl/sprite/sprite_store.cpp +++ b/src/mbgl/sprite/sprite_store.cpp @@ -9,7 +9,6 @@ #include <cassert> #include <string> -#include <sstream> namespace mbgl { @@ -48,14 +47,11 @@ void SpriteStore::setURL(const std::string& url) { loader->jsonRequest = nullptr; if (res.error) { - std::stringstream message; - message << "Failed to load [" << jsonURL << "]: " << res.error->message; - emitSpriteLoadingFailed(message.str()); - return; + observer->onSpriteError(std::make_exception_ptr(std::runtime_error(res.error->message))); } else { loader->json = res.data; + emitSpriteLoadedIfComplete(); } - emitSpriteLoadedIfComplete(); }); loader->spriteRequest = @@ -68,14 +64,11 @@ void SpriteStore::setURL(const std::string& url) { loader->spriteRequest = nullptr; if (res.error) { - std::stringstream message; - message << "Failed to load [" << spriteURL << "]: " << res.error->message; - emitSpriteLoadingFailed(message.str()); - return; + observer->onSpriteError(std::make_exception_ptr(std::runtime_error(res.error->message))); } else { loader->image = res.data; + emitSpriteLoadedIfComplete(); } - emitSpriteLoadedIfComplete(); }); } @@ -91,23 +84,12 @@ void SpriteStore::emitSpriteLoadedIfComplete() { if (result.is<Sprites>()) { loaded = true; setSprites(result.get<Sprites>()); - if (observer) { - observer->onSpriteLoaded(); - } + observer->onSpriteLoaded(); } else { - emitSpriteLoadingFailed(result.get<std::string>()); + observer->onSpriteError(result.get<std::exception_ptr>()); } } -void SpriteStore::emitSpriteLoadingFailed(const std::string& message) { - if (!observer) { - return; - } - - auto error = std::make_exception_ptr(util::SpriteLoadingException(message)); - observer->onSpriteLoadingFailed(error); -} - void SpriteStore::setObserver(Observer* observer_) { observer = observer_; } diff --git a/src/mbgl/sprite/sprite_store.hpp b/src/mbgl/sprite/sprite_store.hpp index 98b43ac8e4..7c5fe71802 100644 --- a/src/mbgl/sprite/sprite_store.hpp +++ b/src/mbgl/sprite/sprite_store.hpp @@ -18,8 +18,8 @@ public: public: virtual ~Observer() = default; - virtual void onSpriteLoaded() = 0; - virtual void onSpriteLoadingFailed(std::exception_ptr) = 0; + virtual void onSpriteLoaded() {}; + virtual void onSpriteError(std::exception_ptr) {}; }; SpriteStore(float pixelRatio); @@ -54,16 +54,15 @@ public: private: void _setSprite(const std::string&, const std::shared_ptr<const SpriteImage>& = nullptr); - void emitSpriteLoadedIfComplete(); - void emitSpriteLoadingFailed(const std::string& message); struct Loader; std::unique_ptr<Loader> loader; bool loaded = false; - Observer* observer = nullptr; + Observer nullObserver; + Observer* observer = &nullObserver; // Lock for sprites and dirty maps. std::mutex mutex; diff --git a/src/mbgl/style/style.cpp b/src/mbgl/style/style.cpp index 77a544714e..b1c867a692 100644 --- a/src/mbgl/style/style.cpp +++ b/src/mbgl/style/style.cpp @@ -17,6 +17,7 @@ #include <mbgl/geometry/glyph_atlas.hpp> #include <mbgl/geometry/line_atlas.hpp> #include <mbgl/util/constants.hpp> +#include <mbgl/util/string.hpp> #include <mbgl/platform/log.hpp> #include <mbgl/layer/background_layer.hpp> @@ -304,73 +305,64 @@ void Style::onLowMemory() { void Style::setObserver(Observer* observer_) { assert(util::ThreadContext::currentlyOn(util::ThreadType::Map)); - assert(!observer); - observer = observer_; } -void Style::onGlyphRangeLoaded() { +void Style::onGlyphsLoaded(const std::string& fontStack, const GlyphRange& glyphRange) { shouldReparsePartialTiles = true; - - emitTileDataChanged(); + observer->onGlyphsLoaded(fontStack, glyphRange); + observer->onResourceLoaded(); } -void Style::onGlyphRangeLoadingFailed(std::exception_ptr error) { - emitResourceLoadingFailed(error); +void Style::onGlyphsError(const std::string& fontStack, const GlyphRange& glyphRange, std::exception_ptr error) { + lastError = error; + Log::Error(Event::Style, "Failed to load glyph range %d-%d for font stack %s: %s", + glyphRange.first, glyphRange.second, fontStack.c_str(), util::toString(error).c_str()); + observer->onGlyphsError(fontStack, glyphRange, error); + observer->onResourceError(error); } -void Style::onSourceLoaded() { - emitTileDataChanged(); +void Style::onSourceLoaded(Source& source) { + observer->onSourceLoaded(source); + observer->onResourceLoaded(); } -void Style::onSourceLoadingFailed(std::exception_ptr error) { - emitResourceLoadingFailed(error); +void Style::onSourceError(Source& source, std::exception_ptr error) { + lastError = error; + Log::Error(Event::Style, "Failed to load source %s: %s", + source.info.source_id.c_str(), util::toString(error).c_str()); + observer->onSourceError(source, error); + observer->onResourceError(error); } -void Style::onTileLoaded(bool isNewTile) { +void Style::onTileLoaded(Source& source, const TileID& tileID, bool isNewTile) { if (isNewTile) { shouldReparsePartialTiles = true; } - emitTileDataChanged(); + observer->onTileLoaded(source, tileID, isNewTile); + observer->onResourceLoaded(); } -void Style::onTileLoadingFailed(std::exception_ptr error) { - emitResourceLoadingFailed(error); +void Style::onTileError(Source& source, const TileID& tileID, std::exception_ptr error) { + lastError = error; + Log::Error(Event::Style, "Failed to load tile %s for source %s: %s", + std::string(tileID).c_str(), source.info.source_id.c_str(), util::toString(error).c_str()); + observer->onTileError(source, tileID, error); + observer->onResourceError(error); } void Style::onSpriteLoaded() { shouldReparsePartialTiles = true; - emitTileDataChanged(); -} - -void Style::onSpriteLoadingFailed(std::exception_ptr error) { - emitResourceLoadingFailed(error); + observer->onSpriteLoaded(); + observer->onResourceLoaded(); } -void Style::emitTileDataChanged() { - assert(util::ThreadContext::currentlyOn(util::ThreadType::Map)); - - if (observer) { - observer->onTileDataChanged(); - } -} - -void Style::emitResourceLoadingFailed(std::exception_ptr error) { - assert(util::ThreadContext::currentlyOn(util::ThreadType::Map)); - - try { - if (error) { - lastError = error; - std::rethrow_exception(error); - } - } catch(const std::exception& e) { - Log::Error(Event::Style, "%s", e.what()); - } - - if (observer) { - observer->onResourceLoadingFailed(error); - } +void Style::onSpriteError(std::exception_ptr error) { + lastError = error; + Log::Error(Event::Style, "Failed to load sprite: %s", util::toString(error).c_str()); + observer->onSpriteError(error); + observer->onResourceError(error); } void Style::dumpDebugLogs() const { diff --git a/src/mbgl/style/style.hpp b/src/mbgl/style/style.hpp index ed5df04256..ba5da7435a 100644 --- a/src/mbgl/style/style.hpp +++ b/src/mbgl/style/style.hpp @@ -58,12 +58,18 @@ public: Style(MapData&); ~Style(); - class Observer { + class Observer : public GlyphStore::Observer, + public SpriteStore::Observer, + public Source::Observer { public: - virtual ~Observer() = default; - - virtual void onTileDataChanged() = 0; - virtual void onResourceLoadingFailed(std::exception_ptr error) = 0; + /** + * In addition to the individual glyph, sprite, and source events, the + * following "rollup" events are provided for convenience. They are + * strictly additive; e.g. when a source is loaded, both `onSourceLoaded` + * and `onResourceLoaded` will be called. + */ + virtual void onResourceLoaded() {}; + virtual void onResourceError(std::exception_ptr) {}; }; void setJSON(const std::string& data, const std::string& base); @@ -115,25 +121,23 @@ private: std::vector<std::unique_ptr<StyleLayer>>::const_iterator findLayer(const std::string& layerID) const; // GlyphStore::Observer implementation. - void onGlyphRangeLoaded() override; - void onGlyphRangeLoadingFailed(std::exception_ptr error) override; + void onGlyphsLoaded(const std::string& fontStack, const GlyphRange&) override; + void onGlyphsError(const std::string& fontStack, const GlyphRange&, std::exception_ptr) override; // SpriteStore::Observer implementation. void onSpriteLoaded() override; - void onSpriteLoadingFailed(std::exception_ptr error) override; + void onSpriteError(std::exception_ptr) override; // Source::Observer implementation. - void onSourceLoaded() override; - void onSourceLoadingFailed(std::exception_ptr error) override; - void onTileLoaded(bool isNewTile) override; - void onTileLoadingFailed(std::exception_ptr error) override; - - void emitTileDataChanged(); - void emitResourceLoadingFailed(std::exception_ptr error); + void onSourceLoaded(Source&) override; + void onSourceError(Source&, std::exception_ptr) override; + void onTileLoaded(Source&, const TileID&, bool isNewTile) override; + void onTileError(Source&, const TileID&, std::exception_ptr) override; bool shouldReparsePartialTiles = false; - Observer* observer = nullptr; + Observer nullObserver; + Observer* observer = &nullObserver; std::exception_ptr lastError; diff --git a/src/mbgl/text/glyph_pbf.cpp b/src/mbgl/text/glyph_pbf.cpp index c14f52de7a..0c0626c0de 100644 --- a/src/mbgl/text/glyph_pbf.cpp +++ b/src/mbgl/text/glyph_pbf.cpp @@ -12,8 +12,6 @@ #include <mbgl/util/token.hpp> #include <mbgl/util/url.hpp> -#include <sstream> - namespace { void parseGlyphPBF(mbgl::FontStack& stack, const std::string& data) { @@ -65,8 +63,10 @@ namespace mbgl { GlyphPBF::GlyphPBF(GlyphStore* store, const std::string& fontStack, - const GlyphRange& glyphRange) - : parsed(false) { + const GlyphRange& glyphRange, + GlyphStore::Observer* observer_) + : parsed(false), + observer(observer_) { // Load the glyph set URL std::string url = util::replaceTokens(store->getURL(), [&](const std::string &name) -> std::string { if (name == "fontstack") return util::percentEncode(fontStack); @@ -74,7 +74,7 @@ GlyphPBF::GlyphPBF(GlyphStore* store, return ""; }); - auto requestCallback = [this, store, fontStack, url](Response res) { + auto requestCallback = [this, store, fontStack, glyphRange](Response res) { if (res.stale) { // Only handle fresh responses. return; @@ -82,12 +82,10 @@ GlyphPBF::GlyphPBF(GlyphStore* store, req = nullptr; if (res.error) { - std::stringstream message; - message << "Failed to load [" << url << "]: " << res.error->message; - emitGlyphPBFLoadingFailed(message.str()); + observer->onGlyphsError(fontStack, glyphRange, std::make_exception_ptr(std::runtime_error(res.error->message))); } else { data = res.data; - parse(store, fontStack, url); + parse(store, fontStack, glyphRange); } }; @@ -97,7 +95,7 @@ GlyphPBF::GlyphPBF(GlyphStore* store, GlyphPBF::~GlyphPBF() = default; -void GlyphPBF::parse(GlyphStore* store, const std::string& fontStack, const std::string& url) { +void GlyphPBF::parse(GlyphStore* store, const std::string& fontStack, const GlyphRange& glyphRange) { assert(data); if (data->empty()) { // If there is no data, this means we either haven't @@ -107,35 +105,13 @@ void GlyphPBF::parse(GlyphStore* store, const std::string& fontStack, const std: try { parseGlyphPBF(**store->getFontStack(fontStack), *data); - } catch (const std::exception& ex) { - std::stringstream message; - message << "Failed to parse [" << url << "]: " << ex.what(); - emitGlyphPBFLoadingFailed(message.str()); + } catch (...) { + observer->onGlyphsError(fontStack, glyphRange, std::current_exception()); return; } parsed = true; - - emitGlyphPBFLoaded(); -} - -void GlyphPBF::setObserver(Observer* observer_) { - observer = observer_; -} - -void GlyphPBF::emitGlyphPBFLoaded() { - if (observer) { - observer->onGlyphPBFLoaded(); - } -} - -void GlyphPBF::emitGlyphPBFLoadingFailed(const std::string& message) { - if (!observer) { - return; - } - - auto error = std::make_exception_ptr(util::GlyphRangeLoadingException(message)); - observer->onGlyphPBFLoadingFailed(error); + observer->onGlyphsLoaded(fontStack, glyphRange); } } // namespace mbgl diff --git a/src/mbgl/text/glyph_pbf.hpp b/src/mbgl/text/glyph_pbf.hpp index d595298516..e0f65e7789 100644 --- a/src/mbgl/text/glyph_pbf.hpp +++ b/src/mbgl/text/glyph_pbf.hpp @@ -2,6 +2,7 @@ #define MBGL_TEXT_GLYPH_PBF #include <mbgl/text/glyph.hpp> +#include <mbgl/text/glyph_store.hpp> #include <mbgl/util/noncopyable.hpp> #include <atomic> @@ -11,43 +12,30 @@ namespace mbgl { -class GlyphStore; class FontStack; class FileRequest; class GlyphPBF : private util::noncopyable { public: - class Observer { - public: - virtual ~Observer() = default; - - virtual void onGlyphPBFLoaded() = 0; - virtual void onGlyphPBFLoadingFailed(std::exception_ptr error) = 0; - }; - GlyphPBF(GlyphStore* store, const std::string& fontStack, - const GlyphRange& glyphRange); - virtual ~GlyphPBF(); + const GlyphRange&, + GlyphStore::Observer*); + ~GlyphPBF(); bool isParsed() const { return parsed; - }; - - void setObserver(Observer* observer); + } private: - void emitGlyphPBFLoaded(); - void emitGlyphPBFLoadingFailed(const std::string& message); - - void parse(GlyphStore* store, const std::string& fontStack, const std::string& url); + void parse(GlyphStore*, const std::string& fontStack, const GlyphRange&); std::shared_ptr<const std::string> data; std::atomic<bool> parsed; std::unique_ptr<FileRequest> req; - Observer* observer = nullptr; + GlyphStore::Observer* observer = nullptr; }; } // namespace mbgl diff --git a/src/mbgl/text/glyph_store.cpp b/src/mbgl/text/glyph_store.cpp index 047db17b7d..a3d1530e3d 100644 --- a/src/mbgl/text/glyph_store.cpp +++ b/src/mbgl/text/glyph_store.cpp @@ -7,6 +7,9 @@ namespace mbgl { +GlyphStore::GlyphStore() = default; +GlyphStore::~GlyphStore() = default; + void GlyphStore::requestGlyphRange(const std::string& fontStackName, const GlyphRange& range) { assert(util::ThreadContext::currentlyOn(util::ThreadType::Map)); @@ -18,10 +21,8 @@ void GlyphStore::requestGlyphRange(const std::string& fontStackName, const Glyph return; } - auto glyphPBF = std::make_unique<GlyphPBF>(this, fontStackName, range); - glyphPBF->setObserver(this); - - rangeSets.emplace(range, std::move(glyphPBF)); + rangeSets.emplace(range, + std::make_unique<GlyphPBF>(this, fontStackName, range, observer)); } @@ -66,18 +67,6 @@ util::exclusive<FontStack> GlyphStore::getFontStack(const std::string& fontStack return { it->second.get(), std::move(lock) }; } -void GlyphStore::onGlyphPBFLoaded() { - if (observer) { - observer->onGlyphRangeLoaded(); - } -} - -void GlyphStore::onGlyphPBFLoadingFailed(std::exception_ptr error) { - if (observer) { - observer->onGlyphRangeLoadingFailed(error); - } -} - void GlyphStore::setObserver(Observer* observer_) { observer = observer_; } diff --git a/src/mbgl/text/glyph_store.hpp b/src/mbgl/text/glyph_store.hpp index 4e665087ea..2236bce38c 100644 --- a/src/mbgl/text/glyph_store.hpp +++ b/src/mbgl/text/glyph_store.hpp @@ -3,7 +3,6 @@ #include <mbgl/text/font_stack.hpp> #include <mbgl/text/glyph.hpp> -#include <mbgl/text/glyph_pbf.hpp> #include <mbgl/util/exclusive.hpp> #include <mbgl/util/noncopyable.hpp> #include <mbgl/util/work_queue.hpp> @@ -15,21 +14,23 @@ namespace mbgl { +class GlyphPBF; + // The GlyphStore manages the loading and storage of Glyphs // and creation of FontStack objects. The GlyphStore lives // on the MapThread but can be queried from any thread. -class GlyphStore : public GlyphPBF::Observer, private util::noncopyable { +class GlyphStore : private util::noncopyable { public: class Observer { public: virtual ~Observer() = default; - virtual void onGlyphRangeLoaded() = 0; - virtual void onGlyphRangeLoadingFailed(std::exception_ptr error) = 0; + virtual void onGlyphsLoaded(const std::string& /* fontStack */, const GlyphRange&) {}; + virtual void onGlyphsError(const std::string& /* fontStack */, const GlyphRange&, std::exception_ptr) {}; }; - GlyphStore() = default; - virtual ~GlyphStore() = default; + GlyphStore(); + ~GlyphStore(); util::exclusive<FontStack> getFontStack(const std::string& fontStack); @@ -38,7 +39,7 @@ public: // made and when the glyph if finally parsed, it gets added to the respective // FontStack and a signal is emitted to notify the observers. This method // can be called from any thread. - bool hasGlyphRanges(const std::string& fontStackName, const std::set<GlyphRange>& glyphRanges); + bool hasGlyphRanges(const std::string& fontStack, const std::set<GlyphRange>& glyphRanges); void setURL(const std::string &url) { glyphURL = url; @@ -48,10 +49,6 @@ public: return glyphURL; } - // GlyphPBF::Observer implementation. - void onGlyphPBFLoaded() override; - void onGlyphPBFLoadingFailed(std::exception_ptr error) override; - void setObserver(Observer* observer); private: @@ -67,7 +64,8 @@ private: util::WorkQueue workQueue; - Observer* observer = nullptr; + Observer nullObserver; + Observer* observer = &nullObserver; }; } // namespace mbgl diff --git a/src/mbgl/util/worker.cpp b/src/mbgl/util/worker.cpp index d23831b440..49d1c2bc5b 100644 --- a/src/mbgl/util/worker.cpp +++ b/src/mbgl/util/worker.cpp @@ -18,18 +18,12 @@ public: void parseRasterTile(std::unique_ptr<RasterBucket> bucket, const std::shared_ptr<const std::string> data, std::function<void(RasterTileParseResult)> callback) { - PremultipliedImage image; - try { - image = decodeImage(*data); + bucket->setImage(decodeImage(*data)); + callback(RasterTileParseResult(std::move(bucket))); } catch (...) { - callback(RasterTileParseResult("error parsing raster image")); - return; + callback(std::current_exception()); } - - bucket->setImage(std::move(image)); - - callback(RasterTileParseResult(std::move(bucket))); } void parseGeometryTile(TileWorker* worker, @@ -39,8 +33,8 @@ public: std::function<void(TileParseResult)> callback) { try { callback(worker->parseAllLayers(std::move(layers), *tile, config)); - } catch (const std::exception& ex) { - callback(TileParseResult(ex.what())); + } catch (...) { + callback(std::current_exception()); } } @@ -48,8 +42,8 @@ public: std::function<void(TileParseResult)> callback) { try { callback(worker->parsePendingLayers()); - } catch (const std::exception& ex) { - callback(TileParseResult(ex.what())); + } catch (...) { + callback(std::current_exception()); } } diff --git a/src/mbgl/util/worker.hpp b/src/mbgl/util/worker.hpp index 5ba477491d..d4660d9e7c 100644 --- a/src/mbgl/util/worker.hpp +++ b/src/mbgl/util/worker.hpp @@ -16,7 +16,7 @@ class GeometryTileLoader; using RasterTileParseResult = mapbox::util::variant< std::unique_ptr<Bucket>, // success - std::string>; // error + std::exception_ptr>; // error class Worker : public mbgl::util::noncopyable { public: diff --git a/test/sprite/sprite_parser.cpp b/test/sprite/sprite_parser.cpp index dfa3a1055b..0eb298b23e 100644 --- a/test/sprite/sprite_parser.cpp +++ b/test/sprite/sprite_parser.cpp @@ -5,6 +5,7 @@ #include <mbgl/sprite/sprite_image.hpp> #include <mbgl/util/image.hpp> #include <mbgl/util/io.hpp> +#include <mbgl/util/string.hpp> #include <algorithm> @@ -211,9 +212,9 @@ TEST(Sprite, SpriteParsingInvalidJSON) { const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); const auto json_1x = R"JSON({ "image": " })JSON"; - const auto error = parseSprite(image_1x, json_1x).get<std::string>(); + const auto error = parseSprite(image_1x, json_1x).get<std::exception_ptr>(); - EXPECT_EQ(error, + EXPECT_EQ(util::toString(error), std::string("Failed to parse JSON: Missing a closing quotation mark in string. at offset 13")); } diff --git a/test/sprite/sprite_store.cpp b/test/sprite/sprite_store.cpp index 574af51b81..6209bde2a3 100644 --- a/test/sprite/sprite_store.cpp +++ b/test/sprite/sprite_store.cpp @@ -176,7 +176,7 @@ public: callback_(spriteStore_.get(), nullptr); } - void onSpriteLoadingFailed(std::exception_ptr error) override { + void onSpriteError(std::exception_ptr error) override { callback_(spriteStore_.get(), error); } diff --git a/test/style/glyph_store.cpp b/test/style/glyph_store.cpp index 4524716212..8e7518c33f 100644 --- a/test/style/glyph_store.cpp +++ b/test/style/glyph_store.cpp @@ -39,11 +39,11 @@ public: glyphStore_.reset(); } - void onGlyphRangeLoaded() override { + void onGlyphsLoaded(const std::string&, const GlyphRange&) override { callback_(glyphStore_.get(), nullptr); } - void onGlyphRangeLoadingFailed(std::exception_ptr error) override { + void onGlyphsError(const std::string&, const GlyphRange&, std::exception_ptr error) override { callback_(glyphStore_.get(), error); } diff --git a/test/style/resource_loading.cpp b/test/style/resource_loading.cpp index 9e0185286e..0476c53aa1 100644 --- a/test/style/resource_loading.cpp +++ b/test/style/resource_loading.cpp @@ -17,234 +17,322 @@ using namespace mbgl; class StyleObserver : public Style::Observer { public: - void onTileDataChanged() override { - if (tileDataChanged) tileDataChanged(); + void onGlyphsLoaded(const std::string& fontStack, const GlyphRange& glyphRange) override { + if (glyphsLoaded) glyphsLoaded(fontStack, glyphRange); } - void onResourceLoadingFailed(std::exception_ptr error) override { - if (resourceLoadingFailed) resourceLoadingFailed(error); + void onGlyphsError(const std::string& fontStack, const GlyphRange& glyphRange, std::exception_ptr error) override { + if (glyphsError) glyphsError(fontStack, glyphRange, error); } - std::function<void ()> tileDataChanged = nullptr; - std::function<void (std::exception_ptr)> resourceLoadingFailed = nullptr; -}; + void onSpriteLoaded() override { + if (spriteLoaded) spriteLoaded(); + } -struct StyleTestData { - MockFileSource::Type type; - std::string style; - std::string resource; - std::string expectedError; - std::string styleClass; - std::function<void (const Style&)> callback = nullptr; -}; + void onSpriteError(std::exception_ptr error) override { + if (spriteError) spriteError(error); + } -void runTest(StyleTestData& testData) { - Log::setObserver(std::make_unique<Log::NullObserver>()); // Squelch logging. + void onSourceLoaded(Source& source) override { + if (sourceLoaded) sourceLoaded(source); + } - util::RunLoop loop; + void onSourceError(Source& source, std::exception_ptr error) override { + if (sourceError) sourceError(source, error); + } - util::ThreadContext context("Map", util::ThreadType::Map, util::ThreadPriority::Regular); - util::ThreadContext::Set(&context); + void onTileLoaded(Source& source, const TileID& tileID, bool isNewTile) override { + if (tileLoaded) tileLoaded(source, tileID, isNewTile); + } - MockFileSource fileSource(testData.type, testData.resource); - util::ThreadContext::setFileSource(&fileSource); + void onTileError(Source& source, const TileID& tileID, std::exception_ptr error) override { + if (tileError) tileError(source, tileID, error); + } - MapData data(MapMode::Still, GLContextMode::Unique, 1.0); - MockView view; - Transform transform(view, ConstrainMode::HeightOnly); - TexturePool texturePool; - Style style(data); + void onResourceLoaded() override { + if (resourceLoaded) resourceLoaded(); + }; + + void onResourceError(std::exception_ptr error) override { + if (resourceError) resourceError(error); + }; + + std::function<void (const std::string& fontStack, const GlyphRange&)> glyphsLoaded; + std::function<void (const std::string& fontStack, const GlyphRange&, std::exception_ptr)> glyphsError; + std::function<void ()> spriteLoaded; + std::function<void (std::exception_ptr)> spriteError; + std::function<void (Source&)> sourceLoaded; + std::function<void (Source&, std::exception_ptr)> sourceError; + std::function<void (Source&, const TileID&, bool isNewTile)> tileLoaded; + std::function<void (Source&, const TileID&, std::exception_ptr)> tileError; + std::function<void ()> resourceLoaded; + std::function<void (std::exception_ptr)> resourceError; + + std::function<void ()> fullyLoaded; +}; +class ResourceLoadingTest { +public: + ResourceLoadingTest(MockFileSource::Type type, const std::string& resource) + : fileSource(type, resource) {} + + util::ThreadContext context { "Map", util::ThreadType::Map, util::ThreadPriority::Regular }; + + MockFileSource fileSource; StyleObserver observer; + util::RunLoop loop; - observer.tileDataChanged = [&] () { - // Prompt tile loading after sources load. - style.update(transform.getState(), texturePool); + MapData data { MapMode::Still, GLContextMode::Unique, 1.0 }; + MockView view; + Transform transform { view, ConstrainMode::HeightOnly }; + TexturePool texturePool; + Style style { data }; - if (style.isLoaded()) { - loop.stop(); - if (testData.callback) testData.callback(style); - } - }; + void run(const std::string& stylePath) { + // Squelch logging. + Log::setObserver(std::make_unique<Log::NullObserver>()); - observer.resourceLoadingFailed = [&] (std::exception_ptr error) { - loop.stop(); - testData.expectedError = util::toString(error); - }; + util::ThreadContext::Set(&context); + util::ThreadContext::setFileSource(&fileSource); - transform.resize({{ 512, 512 }}); - transform.setLatLngZoom({0, 0}, 0); + observer.resourceLoaded = [&] () { + style.update(transform.getState(), texturePool); + if (style.isLoaded() && observer.fullyLoaded) { + observer.fullyLoaded(); + } + }; - style.setObserver(&observer); - style.setJSON(util::read_file(testData.style), ""); + transform.resize({{ 512, 512 }}); + transform.setLatLngZoom({0, 0}, 0); - if (!testData.styleClass.empty()) data.addClass(testData.styleClass); + style.setObserver(&observer); + style.setJSON(util::read_file(stylePath), ""); + style.cascade(); + style.recalculate(0); - style.cascade(); - style.recalculate(16); + loop.run(); + } - loop.run(); -} + void end() { + loop.stop(); + } +}; TEST(ResourceLoading, Success) { - StyleTestData testData; - testData.type = MockFileSource::Success; - testData.style = "test/fixtures/resources/style.json"; - runTest(testData); - EXPECT_EQ(testData.expectedError, ""); + ResourceLoadingTest test(MockFileSource::Success, ""); + + test.observer.resourceError = [&] (std::exception_ptr error) { + FAIL() << util::toString(error); + }; + + test.observer.fullyLoaded = [&] () { + SUCCEED(); + test.end(); + }; + + test.run("test/fixtures/resources/style.json"); } TEST(ResourceLoading, RasterSourceFail) { - StyleTestData testData; - testData.type = MockFileSource::RequestFail; - testData.style = "test/fixtures/resources/style.json"; - testData.resource = "source_raster.json"; - runTest(testData); - EXPECT_EQ(testData.expectedError, "Failed to load [test/fixtures/resources/source_raster.json]: Failed by the test case"); + ResourceLoadingTest test(MockFileSource::RequestFail, "source_raster.json"); + + test.observer.sourceError = [&] (Source& source, std::exception_ptr error) { + EXPECT_EQ(source.info.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) { - StyleTestData testData; - testData.type = MockFileSource::RequestFail; - testData.style = "test/fixtures/resources/style.json"; - testData.resource = "source_vector.json"; - runTest(testData); - EXPECT_EQ(testData.expectedError, "Failed to load [test/fixtures/resources/source_vector.json]: Failed by the test case"); + ResourceLoadingTest test(MockFileSource::RequestFail, "source_vector.json"); + + test.observer.sourceError = [&] (Source& source, std::exception_ptr error) { + EXPECT_EQ(source.info.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) { - StyleTestData testData; - testData.type = MockFileSource::RequestFail; - testData.style = "test/fixtures/resources/style.json"; - testData.resource = "sprite.json"; - runTest(testData); - EXPECT_EQ(testData.expectedError, "Failed to load [test/fixtures/resources/sprite.json]: Failed by the test case"); + 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) { - StyleTestData testData; - testData.type = MockFileSource::RequestFail; - testData.style = "test/fixtures/resources/style.json"; - testData.resource = "sprite.png"; - runTest(testData); - EXPECT_EQ(testData.expectedError, "Failed to load [test/fixtures/resources/sprite.png]: Failed by the test case"); + 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) { - StyleTestData testData; - testData.type = MockFileSource::RequestFail; - testData.style = "test/fixtures/resources/style.json"; - testData.resource = "raster.png"; - runTest(testData); - EXPECT_EQ(testData.expectedError, "Failed to load [test/fixtures/resources/raster.png]: Failed by the test case"); + ResourceLoadingTest test(MockFileSource::RequestFail, "raster.png"); + + test.observer.tileError = [&] (Source& source, const TileID& tileID, std::exception_ptr error) { + EXPECT_EQ(source.info.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) { - StyleTestData testData; - testData.type = MockFileSource::RequestFail; - testData.style = "test/fixtures/resources/style.json"; - testData.resource = "vector.pbf"; - runTest(testData); - EXPECT_EQ(testData.expectedError, "Failed to load [test/fixtures/resources/vector.pbf]: Failed by the test case"); + ResourceLoadingTest test(MockFileSource::RequestFail, "vector.pbf"); + + test.observer.tileError = [&] (Source& source, const TileID& tileID, std::exception_ptr error) { + EXPECT_EQ(source.info.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) { - StyleTestData testData; - testData.type = MockFileSource::RequestFail; - testData.style = "test/fixtures/resources/style.json"; - testData.resource = "glyphs.pbf"; - runTest(testData); - EXPECT_EQ(testData.expectedError, "Failed to load [test/fixtures/resources/glyphs.pbf]: Failed by the test case"); + 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) { - StyleTestData testData; - testData.type = MockFileSource::RequestWithCorruptedData; - testData.style = "test/fixtures/resources/style.json"; - testData.resource = "source_raster.json"; - runTest(testData); - EXPECT_EQ(testData.expectedError, "Failed to parse [test/fixtures/resources/source_raster.json]: 0 - Invalid value."); + ResourceLoadingTest test(MockFileSource::RequestWithCorruptedData, "source_raster.json"); + + test.observer.sourceError = [&] (Source& source, std::exception_ptr error) { + EXPECT_EQ(source.info.source_id, "rastersource"); + EXPECT_EQ(util::toString(error), "0 - Invalid value."); + test.end(); + }; + + test.run("test/fixtures/resources/style.json"); } TEST(ResourceLoading, VectorSourceCorrupt) { - StyleTestData testData; - testData.type = MockFileSource::RequestWithCorruptedData; - testData.style = "test/fixtures/resources/style.json"; - testData.resource = "source_vector.json"; - runTest(testData); - EXPECT_EQ(testData.expectedError, "Failed to parse [test/fixtures/resources/source_vector.json]: 0 - Invalid value."); + ResourceLoadingTest test(MockFileSource::RequestWithCorruptedData, "source_vector.json"); + + test.observer.sourceError = [&] (Source& source, std::exception_ptr error) { + EXPECT_EQ(source.info.source_id, "vectorsource"); + EXPECT_EQ(util::toString(error), "0 - Invalid value."); + test.end(); + }; + + test.run("test/fixtures/resources/style.json"); } TEST(ResourceLoading, SpriteJSONCorrupt) { - StyleTestData testData; - testData.type = MockFileSource::RequestWithCorruptedData; - testData.style = "test/fixtures/resources/style.json"; - testData.resource = "sprite.json"; - runTest(testData); - EXPECT_EQ(testData.expectedError, "Failed to parse JSON: Invalid value. at offset 0"); + 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) { - StyleTestData testData; - testData.type = MockFileSource::RequestWithCorruptedData; - testData.style = "test/fixtures/resources/style.json"; - testData.resource = "sprite.png"; - runTest(testData); - EXPECT_EQ(testData.expectedError, "Could not parse sprite image"); + 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) { - StyleTestData testData; - testData.type = MockFileSource::RequestWithCorruptedData; - testData.style = "test/fixtures/resources/style.json"; - testData.resource = "raster.png"; - runTest(testData); - EXPECT_EQ(testData.expectedError, "Failed to parse [0/0/0]: error parsing raster image"); + ResourceLoadingTest test(MockFileSource::RequestWithCorruptedData, "raster.png"); + + test.observer.tileError = [&] (Source& source, const TileID& tileID, std::exception_ptr error) { + EXPECT_EQ(source.info.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) { - StyleTestData testData; - testData.type = MockFileSource::RequestWithCorruptedData; - testData.style = "test/fixtures/resources/style.json"; - testData.resource = "vector.pbf"; - runTest(testData); - EXPECT_EQ(testData.expectedError, "Failed to parse [0/0/0]: pbf unknown field type exception"); + ResourceLoadingTest test(MockFileSource::RequestWithCorruptedData, "vector.pbf"); + + test.observer.tileError = [&] (Source& source, const TileID& tileID, std::exception_ptr error) { + EXPECT_EQ(source.info.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) { - StyleTestData testData; - testData.type = MockFileSource::RequestWithCorruptedData; - testData.style = "test/fixtures/resources/style.json"; - testData.resource = "glyphs.pbf"; - runTest(testData); - EXPECT_EQ(testData.expectedError, "Failed to parse [test/fixtures/resources/glyphs.pbf]: pbf unknown field type exception"); + 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) { - StyleTestData testData; - testData.type = MockFileSource::Success; - testData.style = "test/fixtures/resources/style-unused-sources.json"; - testData.callback = [] (const Style& style) { - Source *usedSource = style.getSource("usedsource"); + ResourceLoadingTest test(MockFileSource::Success, ""); + + test.observer.fullyLoaded = [&] () { + Source *usedSource = test.style.getSource("usedsource"); EXPECT_TRUE(usedSource); EXPECT_TRUE(usedSource->isLoaded()); - Source *unusedSource = style.getSource("unusedsource"); + Source *unusedSource = test.style.getSource("unusedsource"); EXPECT_TRUE(unusedSource); EXPECT_FALSE(unusedSource->isLoaded()); + + test.end(); }; - runTest(testData); + + test.run("test/fixtures/resources/style-unused-sources.json"); } TEST(ResourceLoading, UnusedSourceActiveViaClassUpdate) { - StyleTestData testData; - testData.type = MockFileSource::Success; - testData.style = "test/fixtures/resources/style-unused-sources.json"; - testData.styleClass = "visible"; - testData.callback = [] (const Style& style) { - Source *unusedSource = style.getSource("unusedsource"); + ResourceLoadingTest test(MockFileSource::Success, ""); + + test.data.addClass("visible"); + + test.observer.fullyLoaded = [&] () { + Source *unusedSource = test.style.getSource("unusedsource"); EXPECT_TRUE(unusedSource); EXPECT_TRUE(unusedSource->isLoaded()); + + test.end(); }; - runTest(testData); + + test.run("test/fixtures/resources/style-unused-sources.json"); } |