diff options
author | Konstantin Käfer <mail@kkaefer.com> | 2015-01-21 19:30:58 +0100 |
---|---|---|
committer | Konstantin Käfer <mail@kkaefer.com> | 2015-02-04 10:49:05 +0100 |
commit | 8a1fce547e9ad0bf750418c844c9b23a3ee6d8dd (patch) | |
tree | e3f0e8f4b16071667c6a4fdf706740335500dbc6 | |
parent | 5503aef6907b1fea74d6bdbe696f02b9f016f752 (diff) | |
download | qtlocation-mapboxgl-8a1fce547e9ad0bf750418c844c9b23a3ee6d8dd.tar.gz |
rearrange tests and add storage tests
49 files changed, 1116 insertions, 497 deletions
diff --git a/.gitignore b/.gitignore index 98d9e28281..f004dcf30f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ /config-ios.gypi /config-android.gypi /build +/test/node_modules /include/mbgl/shader/shaders.hpp /src/shader/shaders_gl.cpp /src/shader/shaders_gles2.cpp @@ -105,10 +105,12 @@ android: test: build/test/Makefile $(MAKE) -C build/test BUILDTYPE=$(BUILDTYPE) V=$(V) test + ./scripts/run_tests.sh -test_%: build/test/Makefile - $(MAKE) -C build/test BUILDTYPE=$(BUILDTYPE) V=$(V) $* - (cd build/$(BUILDTYPE) && exec ./test_$*) + +test-%: build/test/Makefile + $(MAKE) -C build/test BUILDTYPE=$(BUILDTYPE) V=$(V) test + build/$(BUILDTYPE)/test --gtest_filter=$* # build Mac OS X project for Xcode xtest: build/test/test.xcodeproj diff --git a/android/cpp/native_map_view.cpp b/android/cpp/native_map_view.cpp index 71db99ff8a..bf9a3a0532 100644 --- a/android/cpp/native_map_view.cpp +++ b/android/cpp/native_map_view.cpp @@ -52,9 +52,14 @@ void log_gl_string(GLenum name, const char *label) { } } +// Returns the path to the default cache database on this system. +std::string defaultCacheDatabase() { + return mbgl::android::cachePath + "/mbgl-cache.db"; +} + NativeMapView::NativeMapView(JNIEnv *env, jobject obj_) : mbgl::View(*this), - fileSource(mbgl::platform::defaultCacheDatabase()), + fileSource(defaultCacheDatabase()), map(*this, fileSource) { mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::NativeMapView"); diff --git a/gyp/mbgl-android.gypi b/gyp/mbgl-android.gypi index 85115910cd..17ce85eab2 100644 --- a/gyp/mbgl-android.gypi +++ b/gyp/mbgl-android.gypi @@ -33,7 +33,6 @@ ], }, 'sources': [ - '../platform/android/cache_database_data.cpp', '../platform/android/log_android.cpp', '../platform/android/asset_request_libzip.cpp', '../platform/default/string_stdlib.cpp', diff --git a/gyp/mbgl-linux.gypi b/gyp/mbgl-linux.gypi index c515e68bed..98449d744f 100644 --- a/gyp/mbgl-linux.gypi +++ b/gyp/mbgl-linux.gypi @@ -30,11 +30,11 @@ ], }, 'sources': [ - '../platform/default/cache_database_tmp.cpp', + '../platform/default/shader_cache_tmp.cpp', '../platform/default/log_stderr.cpp', '../platform/default/string_stdlib.cpp', + '../platform/default/http_request_curl.cpp', '../platform/default/asset_request_libuv.cpp', - '../platform/default/http_request_baton_curl.cpp', '../platform/default/application_root.cpp', '../platform/default/image.cpp', '../platform/default/image_reader.cpp', diff --git a/gyp/mbgl-osx.gypi b/gyp/mbgl-osx.gypi index 49135fd6c4..8de90a8a22 100644 --- a/gyp/mbgl-osx.gypi +++ b/gyp/mbgl-osx.gypi @@ -9,7 +9,7 @@ 'version', ], 'sources': [ - '../platform/osx/cache_database_application_support.mm', + '../platform/osx/shader_cache_application_support.mm', '../platform/darwin/log_nslog.mm', '../platform/darwin/string_nsstring.mm', '../platform/darwin/http_request_cocoa.mm', @@ -33,6 +33,7 @@ ], 'xcode_settings': { 'OTHER_LDFLAGS': [ + '-framework Foundation', '-framework ImageIO', '-framework CoreServices', ], diff --git a/include/mbgl/platform/platform.hpp b/include/mbgl/platform/platform.hpp index b4aaccb8bd..024771cc7c 100644 --- a/include/mbgl/platform/platform.hpp +++ b/include/mbgl/platform/platform.hpp @@ -17,8 +17,8 @@ std::string uppercase(const std::string &string); // Lowercase a string, potentially using platform-specific routines. std::string lowercase(const std::string &string); -// Returns the path to the default cache database on this system. -std::string defaultCacheDatabase(); +// Returns the path to the default shader cache on this system. +std::string defaultShaderCache(); std::string applicationRoot(); diff --git a/linux/main.cpp b/linux/main.cpp index 245cb527f0..c324e76094 100644 --- a/linux/main.cpp +++ b/linux/main.cpp @@ -64,7 +64,7 @@ int main(int argc, char *argv[]) { sigaction(SIGINT, &sigIntHandler, NULL); view = new GLFWView(); - mbgl::CachingHTTPFileSource fileSource(mbgl::platform::defaultCacheDatabase()); + mbgl::CachingHTTPFileSource fileSource(); mbgl::Map map(*view, fileSource); // Load settings diff --git a/macosx/main.mm b/macosx/main.mm index 6956f04d6d..a81d5e2c16 100644 --- a/macosx/main.mm +++ b/macosx/main.mm @@ -72,12 +72,35 @@ } @end +// Returns the path to the default cache database on this system. +std::string defaultCacheDatabase() { + NSArray *paths = + NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); + if ([paths count] == 0) { + // Disable the cache if we don't have a location to write. + return ""; + } + + NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"Mapbox GL"]; + + if (![[NSFileManager defaultManager] createDirectoryAtPath:path + withIntermediateDirectories:YES + attributes:nil + error:nil]) { + // Disable the cache if we couldn't create the directory. + return ""; + } + + return [[path stringByAppendingPathComponent:@"cache.db"] UTF8String]; +} + + int main() { mbgl::Log::Set<mbgl::NSLogBackend>(); GLFWView view; - mbgl::SQLiteCache cache(mbgl::platform::defaultCacheDatabase()); + mbgl::SQLiteCache cache(defaultCacheDatabase()); mbgl::DefaultFileSource fileSource(&cache); mbgl::Map map(view, fileSource); diff --git a/platform/android/cache_database_data.cpp b/platform/android/cache_database_data.cpp deleted file mode 100644 index 2fefcdc4a3..0000000000 --- a/platform/android/cache_database_data.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include <mbgl/platform/platform.hpp> -#include <mbgl/android/jni.hpp> - -namespace mbgl { -namespace platform { - -// Returns the path to the default cache database on this system. -std::string defaultCacheDatabase() { - return mbgl::android::cachePath + "/mbgl-cache.db"; -} - -} -} diff --git a/platform/osx/cache_database_application_support.mm b/platform/osx/cache_database_application_support.mm deleted file mode 100644 index 974ea537cb..0000000000 --- a/platform/osx/cache_database_application_support.mm +++ /dev/null @@ -1,31 +0,0 @@ -#import <Foundation/Foundation.h> - -#include <mbgl/platform/platform.hpp> - -namespace mbgl { -namespace platform { - -// Returns the path to the default cache database on this system. -std::string defaultCacheDatabase() { - NSArray *paths = - NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); - if ([paths count] == 0) { - // Disable the cache if we don't have a location to write. - return ""; - } - - NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"Mapbox GL"]; - - if (![[NSFileManager defaultManager] createDirectoryAtPath:path - withIntermediateDirectories:YES - attributes:nil - error:nil]) { - // Disable the cache if we couldn't create the directory. - return ""; - } - - return [[path stringByAppendingPathComponent:@"cache.db"] UTF8String]; -} - -} -} diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index e107000761..c5eb4e0ec2 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -3,13 +3,13 @@ set -e set -o pipefail -cd build/${BUILDTYPE:-Release} - -for TEST in ./test_* ; do +for TEST in build/${BUILDTYPE:-Release}/test* ; do # allow writing core files ulimit -c unlimited -S echo 'ulimit -c: '`ulimit -c` - echo '/proc/sys/kernel/core_pattern: '`cat /proc/sys/kernel/core_pattern` + if [ -f /proc/sys/kernel/core_pattern ]; then + echo '/proc/sys/kernel/core_pattern: '`cat /proc/sys/kernel/core_pattern` + fi if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then sysctl kernel.core_pattern diff --git a/src/mbgl/map/sprite.cpp b/src/mbgl/map/sprite.cpp index a3e264b762..19f7b7c8c6 100644 --- a/src/mbgl/map/sprite.cpp +++ b/src/mbgl/map/sprite.cpp @@ -92,7 +92,6 @@ void Sprite::load(FileSource& fileSource) { void Sprite::complete() { if (loadedImage && loadedJSON) { - Log::Info(Event::Sprite, "loaded %s", spriteURL.c_str()); promise.set_value(); } } diff --git a/test/comparisons.cpp b/test/comparisons.cpp index 9b74d6e36f..0daa78ab77 100644 --- a/test/comparisons.cpp +++ b/test/comparisons.cpp @@ -1,5 +1,5 @@ #include <iostream> -#include "gtest/gtest.h" +#include "../util.hpp" #include <mbgl/map/vector_tile.hpp> #include <mbgl/style/filter_expression.hpp> diff --git a/test/fixtures/fixture_log.cpp b/test/fixtures/fixture_log.cpp deleted file mode 100644 index 1b1646e665..0000000000 --- a/test/fixtures/fixture_log.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "fixture_log.hpp" - -#include <iostream> - -namespace mbgl { - -FixtureLogBackend::~FixtureLogBackend() { - std::cerr << unchecked(); -} - -size_t FixtureLogBackend::count(const LogMessage &message) const { - size_t message_count = 0; - for (const LogMessage &msg : messages) { - if (msg == message) { - message_count++; - msg.checked = true; - } - } - return message_count; -} - -std::vector<FixtureLogBackend::LogMessage> FixtureLogBackend::unchecked() const { - std::vector<LogMessage> unchecked_messages; - for (const LogMessage &msg : messages) { - if (!msg.checked) { - unchecked_messages.push_back(msg); - msg.checked = true; - } - } - return unchecked_messages; -} - -::std::ostream& operator<<(::std::ostream& os, const std::vector<FixtureLogBackend::LogMessage>& messages) { - for (const FixtureLogBackend::LogMessage &message : messages) { - os << "- " << message; - } - return os; -} - -::std::ostream& operator<<(::std::ostream& os, const FixtureLogBackend::LogMessage& message) { - os << "[\"" << message.severity.get() << "\", \"" << message.event.get() << "\""; - if (message.code) os << ", " << message.code.get(); - if (message.msg) os << ", \"" << message.msg.get() << "\""; - return os << "]" << std::endl; -} - -} diff --git a/test/fixtures/fixture_log.hpp b/test/fixtures/fixture_log.hpp index 99097a2c47..8df16e3086 100644 --- a/test/fixtures/fixture_log.hpp +++ b/test/fixtures/fixture_log.hpp @@ -7,6 +7,8 @@ #include <vector> #include <cstdarg> +#include <iostream> + namespace mbgl { @@ -37,13 +39,13 @@ public: mutable bool checked = false; }; - ~FixtureLogBackend(); + inline ~FixtureLogBackend(); - void record(EventSeverity severity, Event event, const std::string &msg) { + inline void record(EventSeverity severity, Event event, const std::string &msg) { messages.emplace_back(severity, event, msg); } - void record(EventSeverity severity, Event event, const char* format, ...) { + inline void record(EventSeverity severity, Event event, const char* format, ...) { va_list args; va_start(args, format); const size_t len = vsnprintf(NULL, 0, format, args); @@ -55,24 +57,63 @@ public: messages.emplace_back(severity, event, std::string { buffer.get(), len }); } - void record(EventSeverity severity, Event event, int64_t code) { + inline void record(EventSeverity severity, Event event, int64_t code) { messages.emplace_back(severity, event, code); } - void record(EventSeverity severity, Event event, int64_t code, const std::string &msg) { + inline void record(EventSeverity severity, Event event, int64_t code, const std::string &msg) { messages.emplace_back(severity, event, code, msg); } - size_t count(const LogMessage &message) const; - std::vector<LogMessage> unchecked() const; + inline size_t count(const LogMessage &message) const; + inline std::vector<LogMessage> unchecked() const; public: std::vector<LogMessage> messages; }; -::std::ostream& operator<<(::std::ostream& os, const std::vector<FixtureLogBackend::LogMessage>& messages); -::std::ostream& operator<<(::std::ostream& os, const FixtureLogBackend::LogMessage& message); +inline ::std::ostream& operator<<(::std::ostream& os, const std::vector<FixtureLogBackend::LogMessage>& messages); +inline ::std::ostream& operator<<(::std::ostream& os, const FixtureLogBackend::LogMessage& message); + +FixtureLogBackend::~FixtureLogBackend() { + std::cerr << unchecked(); +} + +size_t FixtureLogBackend::count(const LogMessage &message) const { + size_t message_count = 0; + for (const LogMessage &msg : messages) { + if (msg == message) { + message_count++; + msg.checked = true; + } + } + return message_count; +} +std::vector<FixtureLogBackend::LogMessage> FixtureLogBackend::unchecked() const { + std::vector<LogMessage> unchecked_messages; + for (const LogMessage &msg : messages) { + if (!msg.checked) { + unchecked_messages.push_back(msg); + msg.checked = true; + } + } + return unchecked_messages; +} + +::std::ostream& operator<<(::std::ostream& os, const std::vector<FixtureLogBackend::LogMessage>& messages) { + for (const FixtureLogBackend::LogMessage &message : messages) { + os << "- " << message; + } + return os; +} + +::std::ostream& operator<<(::std::ostream& os, const FixtureLogBackend::LogMessage& message) { + os << "[\"" << message.severity.get() << "\", \"" << message.event.get() << "\""; + if (message.code) os << ", " << message.code.get(); + if (message.msg) os << ", \"" << message.msg.get() << "\""; + return os << "]" << std::endl; +} } diff --git a/test/fixtures/server_environment.hpp b/test/fixtures/server_environment.hpp new file mode 100644 index 0000000000..54dbb24bdd --- /dev/null +++ b/test/fixtures/server_environment.hpp @@ -0,0 +1,51 @@ +#ifndef MBGL_TEST_FIXTURES_SERVER_ENVIRONMENT +#define MBGL_TEST_FIXTURES_SERVER_ENVIRONMENT + +#include <gtest/gtest.h> + +#include <dirent.h> +#include <signal.h> +#include <libgen.h> + +class ServerEnvironment : public ::testing::Environment { +public: + inline ServerEnvironment(const std::string &executable); + inline virtual void SetUp(); + inline virtual void TearDown(); + +private: + const std::string executable; + const std::string parent_pid = std::to_string(getpid()); + pid_t pid = 0; +}; + +ServerEnvironment::ServerEnvironment(const std::string &executable_) : executable(executable_) {} + +void ServerEnvironment::SetUp() { + pid = fork(); + if (pid < 0) { + throw std::runtime_error("Cannot create server process"); + } else if (pid == 0) { + char *arg[] = { + const_cast<char *>(executable.c_str()), + const_cast<char *>(parent_pid.c_str()), + nullptr + }; + int ret = execv(executable.c_str(), arg); + // This call should not return. In case execve failed, we exit anyway. + if (ret < 0) { + fprintf(stderr, "Failed to start server: %s\n", strerror(errno)); + } + exit(0); + } else { + // Wait until the server process sends SIGCONT. + raise(SIGSTOP); + } +} + +void ServerEnvironment::TearDown() { + ASSERT_TRUE(pid); + kill(pid, SIGHUP); +} + +#endif
\ No newline at end of file diff --git a/test/fixtures/storage/empty b/test/fixtures/storage/empty new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/fixtures/storage/empty diff --git a/test/headless.cpp b/test/headless/headless.cpp index c39e5b74a0..d131254322 100644 --- a/test/headless.cpp +++ b/test/headless/headless.cpp @@ -1,4 +1,5 @@ -#include "gtest/gtest.h" +#include "../util.hpp" +#include "../fixtures/server_environment.hpp" #include <mbgl/map/map.hpp> #include <mbgl/util/image.hpp> @@ -13,81 +14,9 @@ #include <mbgl/platform/default/headless_display.hpp> #include <mbgl/storage/default/default_file_source.hpp> -#include "./fixtures/fixture_log.hpp" +#include "../fixtures/fixture_log.hpp" -#include <dirent.h> -#include <signal.h> -#include <libgen.h> -#ifdef __linux__ -#include <sys/prctl.h> -#endif - -std::string base_directory; - -namespace mbgl { -namespace platform { - -std::string defaultCacheDatabase() { - // Disables the cache. - return ""; -} -} -} - -class ServerEnvironment : public ::testing::Environment { -public: - virtual void SetUp() { - pid = fork(); - if (pid < 0) { - throw std::runtime_error("Cannot create web server"); - } else if (pid == 0) { -#ifdef __linux__ - prctl(PR_SET_PDEATHSIG, SIGHUP); -#endif - const auto executable = base_directory + "bin/server.py"; - const char *port = "2900"; - char *arg[] = { const_cast<char *>(executable.c_str()), const_cast<char *>(port), nullptr }; - int ret = execv(executable.c_str(), arg); - // This call should not return. In case execve failed, we exit anyway. - if (ret < 0) { - fprintf(stderr, "Failed to start server: %s\n", strerror(errno)); - } - exit(0); - } else { - display = std::make_shared<mbgl::HeadlessDisplay>(); - } - } - virtual void TearDown() { - ASSERT_TRUE(pid); - kill(pid, SIGHUP); - } - - std::shared_ptr<mbgl::HeadlessDisplay> display; - -private: - pid_t pid = 0; -}; - - -ServerEnvironment* env = nullptr; - - -GTEST_API_ int main(int argc, char *argv[]) { - // Note: glibc's dirname() **modifies** the argument and can't handle static strings. - std::string file { __FILE__ }; file = dirname(const_cast<char *>(file.c_str())); - if (file[0] == '/') { - // If __FILE__ is an absolute path, we don't have to guess from the argv 0. - base_directory = file + "/suite/"; - } else { - std::string argv0 { argv[0] }; argv0 = dirname(const_cast<char *>(argv0.c_str())); - base_directory = argv0 + "/" + file + "/suite/"; - } - - testing::InitGoogleTest(&argc, argv); - env = new ServerEnvironment(); - ::testing::AddGlobalTestEnvironment(env); - return RUN_ALL_TESTS(); -} +std::shared_ptr<mbgl::HeadlessDisplay> display; void rewriteLocalScheme(rapidjson::Value &value, rapidjson::Document::AllocatorType &allocator) { ASSERT_TRUE(value.IsString()); @@ -105,8 +34,8 @@ TEST_P(HeadlessTest, render) { const std::string& base = GetParam(); - std::string style = util::read_file(base_directory + "tests/" + base + "/style.json"); - std::string info = util::read_file(base_directory + "tests/" + base + "/info.json"); + std::string style = util::read_file("test/suite/tests/" + base + "/style.json"); + std::string info = util::read_file("test/suite/tests/" + base + "/info.json"); // Parse style. rapidjson::Document styleDoc; @@ -169,7 +98,7 @@ TEST_P(HeadlessTest, render) { if (value.HasMember("center")) ASSERT_TRUE(value["center"].IsArray()); - const std::string actual_image = base_directory + "tests/" + base + "/" + name + "/actual.png"; + const std::string actual_image = "test/suite/tests/" + base + "/" + name + "/actual.png"; const double zoom = value.HasMember("zoom") ? value["zoom"].GetDouble() : 0; const double bearing = value.HasMember("bearing") ? value["bearing"].GetDouble() : 0; @@ -190,19 +119,22 @@ TEST_P(HeadlessTest, render) { } } - HeadlessView view(env->display); + if (!display) { + display = std::make_shared<mbgl::HeadlessDisplay>(); + } + + HeadlessView view(display); mbgl::DefaultFileSource fileSource(nullptr); Map map(view, fileSource); map.setClasses(classes); - map.setStyleJSON(style, base_directory); + map.setStyleJSON(style, "test/suite"); view.resize(width, height, pixelRatio); map.resize(width, height, pixelRatio); map.setLonLatZoom(longitude, latitude, zoom); map.setBearing(bearing); - // Run the loop. It will terminate when we don't have any further listeners. map.run(); @@ -219,19 +151,17 @@ TEST_P(HeadlessTest, render) { INSTANTIATE_TEST_CASE_P(Headless, HeadlessTest, ::testing::ValuesIn([] { std::vector<std::string> names; - DIR *dir = opendir((base_directory + "tests").c_str()); - if (dir == nullptr) { - return names; - } - - for (dirent *dp = nullptr; (dp = readdir(dir)) != nullptr;) { - const std::string name = dp->d_name; - if (name != "index.html" && !(name.size() >= 1 && name[0] == '.')) { - names.push_back(name); + DIR *dir = opendir("test/suite/tests"); + if (dir != nullptr) { + for (dirent *dp = nullptr; (dp = readdir(dir)) != nullptr;) { + const std::string name = dp->d_name; + if (name != "index.html" && !(name.size() >= 1 && name[0] == '.')) { + names.push_back(name); + } } + closedir(dir); } - closedir(dir); - + EXPECT_GT(names.size(), 0ul); return names; }())); diff --git a/test/headless/headless.gypi b/test/headless/headless.gypi new file mode 100644 index 0000000000..86b40fd543 --- /dev/null +++ b/test/headless/headless.gypi @@ -0,0 +1,17 @@ +{ + 'targets': [ + { 'target_name': 'test_headless', + 'type': 'executable', + 'dependencies': [ + 'test_base', + '../mapboxgl.gyp:mbgl-core', + '../mapboxgl.gyp:mbgl-<(platform)', + '../mapboxgl.gyp:mbgl-headless', + '../deps/gtest/gtest.gyp:gtest', + ], + 'sources': [ + 'headless.cpp', + ], + }, + ], +} diff --git a/test/headless/server.js b/test/headless/server.js new file mode 100755 index 0000000000..bb2c451f84 --- /dev/null +++ b/test/headless/server.js @@ -0,0 +1,19 @@ +#!/usr/bin/env node +/* jshint node: true */ +'use strict'; + +var express = require('express'); +var app = express(); + +app.use(express.static('test/suite')); + +var server = app.listen(2900, function () { + var host = server.address().address; + var port = server.address().port; + console.log('Test server listening at http://%s:%s', host, port); + + if (process.argv[2]) { + // Allow the test to continue running. + process.kill(+process.argv[2], 'SIGCONT'); + } +}); diff --git a/test/main.cpp b/test/main.cpp index ec813bde80..6ae9751c50 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -1,6 +1,10 @@ -#include "gtest/gtest.h" +#include "util.hpp" +#include "fixtures/server_environment.hpp" GTEST_API_ int main(int argc, char *argv[]) { + testing::AddGlobalTestEnvironment(new ServerEnvironment("test/headless/server.js")); + testing::AddGlobalTestEnvironment(new ServerEnvironment("test/storage/server.js")); + testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } diff --git a/test/clip_ids.cpp b/test/miscellaneous/clip_ids.cpp index 18ef9658e5..ebd819e264 100644 --- a/test/clip_ids.cpp +++ b/test/miscellaneous/clip_ids.cpp @@ -1,5 +1,5 @@ #include <iostream> -#include "gtest/gtest.h" +#include "../util.hpp" #include <algorithm> diff --git a/test/miscellaneous/comparisons.cpp b/test/miscellaneous/comparisons.cpp new file mode 100644 index 0000000000..0daa78ab77 --- /dev/null +++ b/test/miscellaneous/comparisons.cpp @@ -0,0 +1,103 @@ +#include <iostream> +#include "../util.hpp" + +#include <mbgl/map/vector_tile.hpp> +#include <mbgl/style/filter_expression.hpp> +#include <mbgl/style/filter_expression_private.hpp> + +#include <map> + +using namespace mbgl; + +typedef std::multimap<std::string, mbgl::Value> Properties; + +class Extractor { +public: + inline Extractor(const Properties& properties_, FeatureType type_) + : properties(properties_) + , type(type_) + {} + + mapbox::util::optional<Value> getValue(const std::string &key) const { + if (key == "$type") + return Value(uint64_t(type)); + auto it = properties.find(key); + if (it == properties.end()) + return mapbox::util::optional<Value>(); + return it->second; + } + + FeatureType getType() const { + return type; + } + +private: + const Properties properties; + FeatureType type; +}; + +FilterExpression parse(const char * expression) { + rapidjson::Document doc; + doc.Parse<0>(expression); + return parseFilterExpression(doc); +} + +bool evaluate(const FilterExpression& expression, const Properties& properties, FeatureType type = FeatureType::Unknown) { + return mbgl::evaluate(expression, Extractor(properties, type)); +} + +TEST(FilterComparison, EqualsString) { + FilterExpression f = parse("[\"==\", \"foo\", \"bar\"]"); + ASSERT_TRUE(evaluate(f, {{ "foo", std::string("bar") }})); + ASSERT_FALSE(evaluate(f, {{ "foo", std::string("baz") }})); +}; + +TEST(FilterComparison, EqualsNumber) { + FilterExpression f = parse("[\"==\", \"foo\", 0]"); + ASSERT_TRUE(evaluate(f, {{ "foo", int64_t(0) }})); + ASSERT_TRUE(evaluate(f, {{ "foo", uint64_t(0) }})); + ASSERT_TRUE(evaluate(f, {{ "foo", double(0) }})); + ASSERT_FALSE(evaluate(f, {{ "foo", int64_t(1) }})); + ASSERT_FALSE(evaluate(f, {{ "foo", uint64_t(1) }})); + ASSERT_FALSE(evaluate(f, {{ "foo", double(1) }})); + ASSERT_FALSE(evaluate(f, {{ "foo", std::string("0") }})); + ASSERT_FALSE(evaluate(f, {{ "foo", false }})); + ASSERT_FALSE(evaluate(f, {{ "foo", true }})); + ASSERT_FALSE(evaluate(f, {{}})); +} + +TEST(FilterComparison, EqualsType) { + FilterExpression f = parse("[\"==\", \"$type\", \"LineString\"]"); + ASSERT_FALSE(evaluate(f, {{}}, FeatureType::Point)); + ASSERT_TRUE(evaluate(f, {{}}, FeatureType::LineString)); +} + +TEST(FilterComparison, Any) { + ASSERT_FALSE(evaluate(parse("[\"any\"]"), {{}})); + ASSERT_TRUE(evaluate(parse("[\"any\", [\"==\", \"foo\", 1]]"), + {{ std::string("foo"), int64_t(1) }})); + ASSERT_FALSE(evaluate(parse("[\"any\", [\"==\", \"foo\", 0]]"), + {{ std::string("foo"), int64_t(1) }})); + ASSERT_TRUE(evaluate(parse("[\"any\", [\"==\", \"foo\", 0], [\"==\", \"foo\", 1]]"), + {{ std::string("foo"), int64_t(1) }})); +} + +TEST(FilterComparison, All) { + ASSERT_TRUE(evaluate(parse("[\"all\"]"), {{}})); + ASSERT_TRUE(evaluate(parse("[\"all\", [\"==\", \"foo\", 1]]"), + {{ std::string("foo"), int64_t(1) }})); + ASSERT_FALSE(evaluate(parse("[\"all\", [\"==\", \"foo\", 0]]"), + {{ std::string("foo"), int64_t(1) }})); + ASSERT_FALSE(evaluate(parse("[\"all\", [\"==\", \"foo\", 0], [\"==\", \"foo\", 1]]"), + {{ std::string("foo"), int64_t(1) }})); +} + +TEST(FilterComparison, None) { + ASSERT_TRUE(evaluate(parse("[\"none\"]"), {{}})); + ASSERT_FALSE(evaluate(parse("[\"none\", [\"==\", \"foo\", 1]]"), + {{ std::string("foo"), int64_t(1) }})); + ASSERT_TRUE(evaluate(parse("[\"none\", [\"==\", \"foo\", 0]]"), + {{ std::string("foo"), int64_t(1) }})); + ASSERT_FALSE(evaluate(parse("[\"none\", [\"==\", \"foo\", 0], [\"==\", \"foo\", 1]]"), + {{ std::string("foo"), int64_t(1) }})); +} diff --git a/test/enums.cpp b/test/miscellaneous/enums.cpp index b45fc0ed0d..dc71645128 100644 --- a/test/enums.cpp +++ b/test/miscellaneous/enums.cpp @@ -1,5 +1,5 @@ #include <iostream> -#include "gtest/gtest.h" +#include "../util.hpp" #include <algorithm> diff --git a/test/functions.cpp b/test/miscellaneous/functions.cpp index 56b2a31706..6543a32d1f 100644 --- a/test/functions.cpp +++ b/test/miscellaneous/functions.cpp @@ -1,5 +1,5 @@ #include <iostream> -#include "gtest/gtest.h" +#include "../util.hpp" #include <mbgl/style/function_properties.hpp> diff --git a/test/merge_lines.cpp b/test/miscellaneous/merge_lines.cpp index 3c887105fa..0e7328d195 100644 --- a/test/merge_lines.cpp +++ b/test/miscellaneous/merge_lines.cpp @@ -1,13 +1,11 @@ -#include "gtest/gtest.h" +#include "../fixtures/util.hpp" #include <mbgl/util/merge_lines.hpp> +const std::u32string a = U"a"; +const std::u32string b = U"b"; -TEST(mergeLines, mergeLines) { - - std::u32string a = U"a"; - std::u32string b = U"b"; - +TEST(MergeLines, SameText) { // merges lines with the same text std::vector<mbgl::SymbolFeature> input1 = { { {{{0, 0}, {1, 0}, {2, 0}}}, a, "" }, @@ -18,7 +16,7 @@ TEST(mergeLines, mergeLines) { { {{{5, 0}, {6, 0}}}, a, "" } }; - std::vector<mbgl::SymbolFeature> expected1 = { + const std::vector<mbgl::SymbolFeature> expected1 = { { {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}}}, a, "" }, { {{{4, 0}, {5, 0}, {6, 0}}}, b, "" }, { {{{5, 0}, {6, 0}, {7, 0}, {8, 0}, {9, 0}}}, a, "" }, @@ -32,7 +30,9 @@ TEST(mergeLines, mergeLines) { for (int i = 0; i < 6; i++) { EXPECT_EQ(input1[i].geometry, expected1[i].geometry); } +} +TEST(MergeLines, BothEnds) { // mergeLines handles merge from both ends std::vector<mbgl::SymbolFeature> input2 = { { {{{0, 0}, {1, 0}, {2, 0}}}, a, "" }, @@ -40,7 +40,7 @@ TEST(mergeLines, mergeLines) { { {{{2, 0}, {3, 0}, {4, 0}}}, a, "" } }; - std::vector<mbgl::SymbolFeature> expected2 = { + const std::vector<mbgl::SymbolFeature> expected2 = { { {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}}}, a, "" }, { {{}}, a, "" }, { {{}}, a, "" } @@ -51,7 +51,9 @@ TEST(mergeLines, mergeLines) { for (int i = 0; i < 3; i++) { EXPECT_EQ(input2[i].geometry, expected2[i].geometry); } +} +TEST(MergeLines, CircularLines) { // mergeLines handles circular lines std::vector<mbgl::SymbolFeature> input3 = { { {{{0, 0}, {1, 0}, {2, 0}}}, a, "" }, @@ -59,7 +61,7 @@ TEST(mergeLines, mergeLines) { { {{{4, 0}, {0, 0}}}, a, "" } }; - std::vector<mbgl::SymbolFeature> expected3 = { + const std::vector<mbgl::SymbolFeature> expected3 = { { {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {0, 0}}}, a, "" }, { {{}}, a, "" }, { {{}}, a, "" } diff --git a/test/miscellaneous/miscellaneous.gypi b/test/miscellaneous/miscellaneous.gypi new file mode 100644 index 0000000000..023f28fa6d --- /dev/null +++ b/test/miscellaneous/miscellaneous.gypi @@ -0,0 +1,26 @@ +{ + 'targets': [ + { 'target_name': 'test_miscellaneous', + 'type': 'executable', + 'dependencies': [ + 'test_base', + '../mapboxgl.gyp:mbgl-core', + '../mapboxgl.gyp:mbgl-<(platform)', + '../mapboxgl.gyp:mbgl-headless', + '../deps/gtest/gtest.gyp:gtest' + ], + 'sources': [ + 'main.cpp', + 'clip_ids.cpp', + 'comparisons.cpp', + 'enums.cpp', + 'functions.cpp', + 'rotation_range.cpp', + 'style_parser.cpp', + 'text_conversions.cpp', + 'tile.cpp', + 'variant.cpp', + ], + }, + ], +} diff --git a/test/rotation_range.cpp b/test/miscellaneous/rotation_range.cpp index 987d422686..3106e900f6 100644 --- a/test/rotation_range.cpp +++ b/test/miscellaneous/rotation_range.cpp @@ -1,5 +1,5 @@ #include <iostream> -#include "gtest/gtest.h" +#include "../util.hpp" #include <algorithm> diff --git a/test/style_parser.cpp b/test/miscellaneous/style_parser.cpp index fe237b7888..3cef6ae614 100644 --- a/test/style_parser.cpp +++ b/test/miscellaneous/style_parser.cpp @@ -1,23 +1,17 @@ -#include "gtest/gtest.h" +#include "../util.hpp" #include <mbgl/style/style.hpp> #include <mbgl/util/io.hpp> #include <rapidjson/document.h> -#include "./fixtures/fixture_log.hpp" +#include "../fixtures/fixture_log.hpp" #include <iostream> #include <fstream> #include <dirent.h> -const std::string base_directory = []{ - std::string fn = __FILE__; - fn.erase(fn.find_last_of("/")); - return fn + "/fixtures/style_parser"; -}(); - using namespace mbgl; typedef std::pair<uint32_t, std::string> Message; @@ -26,7 +20,7 @@ typedef std::vector<Message> Messages; class StyleParserTest : public ::testing::TestWithParam<std::string> {}; TEST_P(StyleParserTest, ParseStyle) { - const std::string &base = base_directory + "/" + GetParam(); + const std::string &base = "test/fixtures/style_parser/" + GetParam(); const std::string style_path = base + ".style.json"; const std::string info = util::read_file(base + ".info.json"); @@ -83,19 +77,18 @@ INSTANTIATE_TEST_CASE_P(StyleParser, StyleParserTest, ::testing::ValuesIn([] { std::vector<std::string> names; const std::string ending = ".info.json"; - DIR *dir = opendir(base_directory.c_str()); - if (dir == nullptr) { - return names; - } - - for (dirent *dp = nullptr; (dp = readdir(dir)) != nullptr;) { - const std::string name = dp->d_name; - if (name.length() >= ending.length() && name.compare(name.length() - ending.length(), ending.length(), ending) == 0) { - names.push_back(name.substr(0, name.length() - ending.length())); + const std::string style_directory = "test/fixtures/style_parser"; + DIR *dir = opendir(style_directory.c_str()); + if (dir != nullptr) { + for (dirent *dp = nullptr; (dp = readdir(dir)) != nullptr;) { + const std::string name = dp->d_name; + if (name.length() >= ending.length() && name.compare(name.length() - ending.length(), ending.length(), ending) == 0) { + names.push_back(name.substr(0, name.length() - ending.length())); + } } + closedir(dir); } - closedir(dir); - + EXPECT_GT(names.size(), 0ul); return names; }())); diff --git a/test/text_conversions.cpp b/test/miscellaneous/text_conversions.cpp index 756bc4db1f..4ea62b4686 100644 --- a/test/text_conversions.cpp +++ b/test/miscellaneous/text_conversions.cpp @@ -1,5 +1,5 @@ #include <iostream> -#include "gtest/gtest.h" +#include "../util.hpp" #include <mbgl/util/utf.hpp> #include <mbgl/platform/platform.hpp> diff --git a/test/tile.cpp b/test/miscellaneous/tile.cpp index 8a580f55a9..70ffd1ecd8 100644 --- a/test/tile.cpp +++ b/test/miscellaneous/tile.cpp @@ -1,5 +1,5 @@ #include <iostream> -#include "gtest/gtest.h" +#include "../util.hpp" #include <mbgl/map/tile.hpp> diff --git a/test/variant.cpp b/test/miscellaneous/variant.cpp index e4caba9f2d..979d73925e 100644 --- a/test/variant.cpp +++ b/test/miscellaneous/variant.cpp @@ -1,5 +1,5 @@ #include <iostream> -#include "gtest/gtest.h" +#include "../util.hpp" #include <mbgl/style/value.hpp> #include <mbgl/style/value_comparison.hpp> diff --git a/test/storage/cache_response.cpp b/test/storage/cache_response.cpp new file mode 100644 index 0000000000..c775647f77 --- /dev/null +++ b/test/storage/cache_response.cpp @@ -0,0 +1,39 @@ +#include "../util.hpp" + +#include <uv.h> + +#include <mbgl/storage/default/default_file_source.hpp> +#include <mbgl/storage/default/sqlite_cache.hpp> + +TEST(Storage, CacheResponse) { + SCOPED_TEST(CacheResponse); + + using namespace mbgl; + + SQLiteCache cache(":memory:"); + DefaultFileSource fs(&cache, uv_default_loop()); + + const Resource resource { Resource::Unknown, "http://127.0.0.1:3000/cache" }; + + fs.request(resource, uv_default_loop(), [&](const Response &res) { + EXPECT_EQ(res.status, Response::Successful); + EXPECT_EQ(res.data, "Response 1"); + EXPECT_GT(res.expires, 0); + EXPECT_EQ(res.modified, 0); + EXPECT_EQ(res.etag, ""); + EXPECT_EQ(res.message, ""); + + fs.request(resource, uv_default_loop(), [&, res](const Response &res2) { + EXPECT_EQ(res2.status, res.status); + EXPECT_EQ(res2.data, res.data); + EXPECT_EQ(res2.expires, res.expires); + EXPECT_EQ(res2.modified, res.modified); + EXPECT_EQ(res2.etag, res.etag); + EXPECT_EQ(res2.message, res.message); + + CacheResponse.finish(); + }); + }); + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); +} diff --git a/test/storage/cache_revalidate.cpp b/test/storage/cache_revalidate.cpp new file mode 100644 index 0000000000..c2bbcbc2a4 --- /dev/null +++ b/test/storage/cache_revalidate.cpp @@ -0,0 +1,85 @@ +#include "../util.hpp" + +#include <uv.h> + +#include <mbgl/storage/default/default_file_source.hpp> +#include <mbgl/storage/default/sqlite_cache.hpp> + +TEST(Storage, CacheRevalidate) { + SCOPED_TEST(CacheRevalidateSame) + SCOPED_TEST(CacheRevalidateModified) + SCOPED_TEST(CacheRevalidateEtag) + + using namespace mbgl; + + SQLiteCache cache(":memory:"); + DefaultFileSource fs(&cache); + + const Resource revalidateSame { Resource::Unknown, "http://127.0.0.1:3000/revalidate-same" }; + fs.request(revalidateSame, uv_default_loop(), [&](const Response &res) { + EXPECT_EQ(res.status, Response::Successful); + EXPECT_EQ(res.data, "Response"); + EXPECT_EQ(res.expires, 0); + EXPECT_EQ(res.modified, 0); + EXPECT_EQ(res.etag, "snowfall"); + EXPECT_EQ(res.message, ""); + + fs.request(revalidateSame, uv_default_loop(), [&, res](const Response &res2) { + EXPECT_EQ(res2.status, Response::Successful); + EXPECT_EQ(res2.data, "Response"); + // We use this to indicate that a 304 reply came back. + EXPECT_GT(res2.expires, 0); + EXPECT_EQ(res2.modified, 0); + // We're not sending the ETag in the 304 reply, but it should still be there. + EXPECT_EQ(res2.etag, "snowfall"); + EXPECT_EQ(res2.message, ""); + + CacheRevalidateSame.finish(); + }); + }); + + const Resource revalidateModified { Resource::Unknown, "http://127.0.0.1:3000/revalidate-modified" }; + fs.request(revalidateModified, uv_default_loop(), [&](const Response &res) { + EXPECT_EQ(res.status, Response::Successful); + EXPECT_EQ(res.data, "Response"); + EXPECT_EQ(res.expires, 0); + EXPECT_EQ(res.modified, 1420070400); + EXPECT_EQ(res.etag, ""); + EXPECT_EQ(res.message, ""); + + fs.request(revalidateModified, uv_default_loop(), [&, res](const Response &res2) { + EXPECT_EQ(res2.status, Response::Successful); + EXPECT_EQ(res2.data, "Response"); + // We use this to indicate that a 304 reply came back. + EXPECT_GT(res2.expires, 0); + EXPECT_EQ(res2.modified, 1420070400); + EXPECT_EQ(res2.etag, ""); + EXPECT_EQ(res2.message, ""); + + CacheRevalidateModified.finish(); + }); + }); + + const Resource revalidateEtag { Resource::Unknown, "http://127.0.0.1:3000/revalidate-etag" }; + fs.request(revalidateEtag, uv_default_loop(), [&](const Response &res) { + EXPECT_EQ(res.status, Response::Successful); + EXPECT_EQ(res.data, "Response 1"); + EXPECT_EQ(res.expires, 0); + EXPECT_EQ(res.modified, 0); + EXPECT_EQ(res.etag, "response-1"); + EXPECT_EQ(res.message, ""); + + fs.request(revalidateEtag, uv_default_loop(), [&, res](const Response &res2) { + EXPECT_EQ(res2.status, Response::Successful); + EXPECT_EQ(res2.data, "Response 2"); + EXPECT_EQ(res2.expires, 0); + EXPECT_EQ(res2.modified, 0); + EXPECT_EQ(res2.etag, "response-2"); + EXPECT_EQ(res2.message, ""); + + CacheRevalidateEtag.finish(); + }); + }); + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); +} diff --git a/test/storage/directory_reading.cpp b/test/storage/directory_reading.cpp new file mode 100644 index 0000000000..66d0f8f9e7 --- /dev/null +++ b/test/storage/directory_reading.cpp @@ -0,0 +1,25 @@ +#include "../util.hpp" + +#include <uv.h> + +#include <mbgl/storage/default/default_file_source.hpp> + +TEST(Storage, ReadDirectory) { + SCOPED_TEST(ReadDirectory) + + using namespace mbgl; + + DefaultFileSource fs(nullptr, uv_default_loop()); + + fs.request({ Resource::Unknown, "asset://test/fixtures/storage" }, uv_default_loop(), [&](const Response &res) { + EXPECT_EQ(res.status, Response::Error); + EXPECT_EQ(res.data.size(), 0ul); + EXPECT_EQ(res.expires, 0); + EXPECT_EQ(res.modified, 0); + EXPECT_EQ(res.etag, ""); + EXPECT_EQ(res.message, "illegal operation on a directory"); + ReadDirectory.finish(); + }); + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); +} diff --git a/test/storage/file_reading.cpp b/test/storage/file_reading.cpp new file mode 100644 index 0000000000..273b58218e --- /dev/null +++ b/test/storage/file_reading.cpp @@ -0,0 +1,45 @@ +#include "../util.hpp" + +#include <uv.h> + +#include <mbgl/storage/default/default_file_source.hpp> + +TEST(Storage, EmptyFile) { + SCOPED_TEST(EmptyFile) + + using namespace mbgl; + + DefaultFileSource fs(nullptr, uv_default_loop()); + + fs.request({ Resource::Unknown, "asset://test/fixtures/storage/empty" }, uv_default_loop(), [&](const Response &res) { + EXPECT_EQ(res.status, Response::Successful); + EXPECT_EQ(res.data.size(), 0ul); + EXPECT_EQ(res.expires, 0); + EXPECT_GT(res.modified, 0); + EXPECT_NE(res.etag, ""); + EXPECT_EQ(res.message, ""); + EmptyFile.finish(); + }); + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); +} + +TEST(Storage, NonExistentFile) { + SCOPED_TEST(NonExistentFile) + + using namespace mbgl; + + DefaultFileSource fs(nullptr, uv_default_loop()); + + fs.request({ Resource::Unknown, "asset://test/fixtures/storage/does_not_exist" }, uv_default_loop(), [&](const Response &res) { + EXPECT_EQ(res.status, Response::Error); + EXPECT_EQ(res.data.size(), 0ul); + EXPECT_EQ(res.expires, 0); + EXPECT_EQ(res.modified, 0); + EXPECT_EQ(res.etag, ""); + EXPECT_EQ(res.message, "no such file or directory"); + NonExistentFile.finish(); + }); + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); +} diff --git a/test/storage/http_cancel.cpp b/test/storage/http_cancel.cpp new file mode 100644 index 0000000000..4da70b965b --- /dev/null +++ b/test/storage/http_cancel.cpp @@ -0,0 +1,50 @@ +#include "../util.hpp" + +#include <uv.h> + +#include <mbgl/storage/default/default_file_source.hpp> +#include <mbgl/storage/network_status.hpp> + +#include <cmath> + +TEST(Storage, HTTPCancel) { + SCOPED_TEST(HTTPCancel) + + using namespace mbgl; + + DefaultFileSource fs(nullptr, uv_default_loop()); + + auto req = fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, uv_default_loop(), [&](const Response &) { + ADD_FAILURE() << "Callback should not be called"; + }); + + fs.cancel(req); + HTTPCancel.finish(); + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); +} + +TEST(Storage, HTTPCancelMultiple) { + SCOPED_TEST(HTTPCancelMultiple) + + using namespace mbgl; + + DefaultFileSource fs(nullptr, uv_default_loop()); + + const Resource resource { Resource::Unknown, "http://127.0.0.1:3000/test" }; + auto req2 = fs.request(resource, uv_default_loop(), [&](const Response &) { + ADD_FAILURE() << "Callback should not be called"; + }); + fs.request(resource, uv_default_loop(), [&](const Response &res) { + EXPECT_EQ(res.status, Response::Successful); + EXPECT_EQ(res.data, "Hello World!"); + EXPECT_EQ(res.expires, 0); + EXPECT_EQ(res.modified, 0); + EXPECT_EQ(res.etag, ""); + EXPECT_EQ(res.message, ""); + HTTPCancelMultiple.finish(); + }); + fs.cancel(req2); + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); +} diff --git a/test/storage/http_coalescing.cpp b/test/storage/http_coalescing.cpp new file mode 100644 index 0000000000..2a82abba2e --- /dev/null +++ b/test/storage/http_coalescing.cpp @@ -0,0 +1,48 @@ +#include "../util.hpp" + +#include <uv.h> + +#include <mbgl/storage/default/default_file_source.hpp> + +TEST(Storage, HTTPCoalescing) { + SCOPED_TEST(HTTPCoalescing) + + static int counter = 0; + const static int total = 4; + + using namespace mbgl; + + DefaultFileSource fs(nullptr, uv_default_loop()); + + static const Response *reference = nullptr; + + const auto complete = [&](const Response &res) { + counter++; + + // Make sure all of the Response objects are the same. + if (!reference) { + reference = &res; + } else { + EXPECT_EQ(reference, &res); + } + + EXPECT_EQ(res.status, Response::Successful); + EXPECT_EQ(res.data, "Hello World!"); + EXPECT_EQ(res.expires, 0); + EXPECT_EQ(res.modified, 0); + EXPECT_EQ(res.etag, ""); + EXPECT_EQ(res.message, ""); + + if (counter >= total) { + HTTPCoalescing.finish(); + } + }; + + const Resource resource { Resource::Unknown, "http://127.0.0.1:3000/test" }; + + for (int i = 0; i < total; i++) { + fs.request(resource, uv_default_loop(), complete); + } + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); +} diff --git a/test/storage/http_error.cpp b/test/storage/http_error.cpp new file mode 100644 index 0000000000..3f4b95a102 --- /dev/null +++ b/test/storage/http_error.cpp @@ -0,0 +1,63 @@ +#include "../util.hpp" + +#include <uv.h> + +#include <mbgl/storage/default/default_file_source.hpp> +#include <mbgl/storage/network_status.hpp> + +#include <cmath> + +TEST(Storage, HTTPError) { + SCOPED_TEST(HTTPTemporaryError) + SCOPED_TEST(HTTPConnectionError) + + using namespace mbgl; + + uv_timer_t statusChange; + uv_timer_init(uv_default_loop(), &statusChange); + uv_timer_start(&statusChange, [](uv_timer_t *, int) { + NetworkStatus::Reachable(); + }, 500, 500); + uv_unref((uv_handle_t *)&statusChange); + + DefaultFileSource fs(nullptr, uv_default_loop()); + + auto start = uv_hrtime(); + + fs.request({ Resource::Unknown, "http://127.0.0.1:3000/temporary-error" }, uv_default_loop(), [&](const Response &res) { + const auto duration = double(uv_hrtime() - start) / 1e9; + EXPECT_GT(duration, 1) << "Backoff timer didn't wait 1 second"; + EXPECT_LT(duration, 1.2) << "Backoff timer fired too late"; + EXPECT_EQ(res.status, Response::Successful); + EXPECT_EQ(res.data, "Hello World!"); + EXPECT_EQ(res.expires, 0); + EXPECT_EQ(res.modified, 0); + EXPECT_EQ(res.etag, ""); + EXPECT_EQ(res.message, ""); + + HTTPTemporaryError.finish(); + }); + + fs.request({ Resource::Unknown, "http://127.0.0.1:3001/" }, uv_default_loop(), [&](const Response &res) { + const auto duration = double(uv_hrtime() - start) / 1e9; + // 1.5 seconds == 4 retries, with a 500ms timeout (see above). + EXPECT_GT(duration, 1.5) << "Resource wasn't retried the correct number of times"; + EXPECT_LT(duration, 1.7) << "Resource wasn't retried the correct number of times"; + EXPECT_EQ(res.status, Response::Error); + EXPECT_TRUE(res.message == "Couldn't connect to server" || res.message == "The operation couldn’t be completed. (NSURLErrorDomain error -1004.)"); + EXPECT_EQ(res.data, ""); + EXPECT_EQ(res.expires, 0); + EXPECT_EQ(res.modified, 0); + EXPECT_EQ(res.etag, ""); + HTTPConnectionError.finish(); + }); + + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); + + uv_timer_stop(&statusChange); + uv_close(reinterpret_cast<uv_handle_t *>(&statusChange), nullptr); + + // Run again so that the timer handle can be properly closed. + uv_run(uv_default_loop(), UV_RUN_DEFAULT); +} diff --git a/test/storage/http_header_parsing.cpp b/test/storage/http_header_parsing.cpp new file mode 100644 index 0000000000..0d169e44c6 --- /dev/null +++ b/test/storage/http_header_parsing.cpp @@ -0,0 +1,42 @@ +#include "../util.hpp" + +#include <uv.h> + +#include <mbgl/storage/default/default_file_source.hpp> + +#include <cmath> + +TEST(Storage, HTTPHeaderParsing) { + SCOPED_TEST(HTTPExpiresTest) + SCOPED_TEST(HTTPCacheControlTest) + + using namespace mbgl; + + DefaultFileSource fs(nullptr, uv_default_loop()); + + fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test?modified=1420794326&expires=1420797926&etag=foo" }, uv_default_loop(), [&](const Response &res) { + EXPECT_EQ(res.status, Response::Successful); + EXPECT_EQ(res.data, "Hello World!"); + EXPECT_EQ(res.expires, 1420797926); + EXPECT_EQ(res.modified, 1420794326); + EXPECT_EQ(res.etag, "foo"); + EXPECT_EQ(res.message, ""); + HTTPExpiresTest.finish(); + }); + + + int64_t now = std::chrono::duration_cast<std::chrono::seconds>( + std::chrono::system_clock::now().time_since_epoch()).count(); + + fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test?cachecontrol=max-age=120" }, uv_default_loop(), [&](const Response &res) { + EXPECT_EQ(res.status, Response::Successful); + EXPECT_EQ(res.data, "Hello World!"); + EXPECT_LT(std::abs(res.expires - now - 120), 2) << "Expiration date isn't about 120 seconds in the future"; + EXPECT_EQ(res.modified, 0); + EXPECT_EQ(res.etag, ""); + EXPECT_EQ(res.message, ""); + HTTPCacheControlTest.finish(); + }); + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); +} diff --git a/test/storage/http_load.cpp b/test/storage/http_load.cpp new file mode 100644 index 0000000000..deefef9135 --- /dev/null +++ b/test/storage/http_load.cpp @@ -0,0 +1,44 @@ +#include "../util.hpp" + +#include <uv.h> + +#include <mbgl/storage/default/default_file_source.hpp> + + + +TEST(Storage, HTTPLoad) { + SCOPED_TEST(HTTPLoad) + + using namespace mbgl; + + DefaultFileSource fs(nullptr, uv_default_loop()); + + const int concurrency = 50; + const int max = 10000; + int number = 1; + + std::function<void()> req = [&]() { + const auto current = number++; + fs.request({ Resource::Unknown, std::string("http://127.0.0.1:3000/load/") + std::to_string(current) }, uv_default_loop(), [&, current](const Response &res) { + EXPECT_EQ(res.status, Response::Successful); + EXPECT_EQ(res.data, std::string("Request ") + std::to_string(current)); + EXPECT_EQ(res.expires, 0); + EXPECT_EQ(res.modified, 0); + EXPECT_EQ(res.etag, ""); + EXPECT_EQ(res.message, ""); + + if (number <= max) { + req(); + } else if (current == max) { + HTTPLoad.finish(); + } + }); + }; + + + for (int i = 0; i < concurrency; i++) { + req(); + } + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); +} diff --git a/test/storage/http_noloop.cpp b/test/storage/http_noloop.cpp new file mode 100644 index 0000000000..cb2b778aa7 --- /dev/null +++ b/test/storage/http_noloop.cpp @@ -0,0 +1,37 @@ +#include "../util.hpp" + +#include <uv.h> + +#include <mbgl/storage/default/default_file_source.hpp> +#include <mbgl/util/uv.hpp> + +TEST(Storage, HTTPNoLoop) { + SCOPED_TEST(HTTPNoLoop) + + using namespace mbgl; + + DefaultFileSource fs(nullptr); + + const auto mainThread = uv_thread_self(); + + // Async handle that keeps the main loop alive until the thread finished + auto async = new uv_async_t; + uv_async_init(uv_default_loop(), async, [] (uv_async_t *as, int) { + uv::close(as); + }); + + fs.request({ Resource::Unknown, "http://127.0.0.1:3000/temporary-error" }, [&](const Response &res) { + EXPECT_NE(mainThread, uv_thread_self()) << "Response was called in the same thread"; + EXPECT_EQ(res.status, Response::Successful); + EXPECT_EQ(res.data, "Hello World!"); + EXPECT_EQ(res.expires, 0); + EXPECT_EQ(res.modified, 0); + EXPECT_EQ(res.etag, ""); + EXPECT_EQ(res.message, ""); + HTTPNoLoop.finish(); + + uv_async_send(async); + }); + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); +} diff --git a/test/storage/http_other_loop.cpp b/test/storage/http_other_loop.cpp new file mode 100644 index 0000000000..01779453d5 --- /dev/null +++ b/test/storage/http_other_loop.cpp @@ -0,0 +1,26 @@ +#include "../util.hpp" + +#include <uv.h> + +#include <mbgl/storage/default/default_file_source.hpp> + +TEST(Storage, HTTPOtherLoop) { + SCOPED_TEST(HTTPOtherLoop) + + using namespace mbgl; + + // This file source launches a separate thread to do the processing. + DefaultFileSource fs(nullptr); + + fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, uv_default_loop(), [&](const Response &res) { + EXPECT_EQ(res.status, Response::Successful); + EXPECT_EQ(res.data, "Hello World!"); + EXPECT_EQ(res.expires, 0); + EXPECT_EQ(res.modified, 0); + EXPECT_EQ(res.etag, ""); + EXPECT_EQ(res.message, ""); + HTTPOtherLoop.finish(); + }); + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); +} diff --git a/test/storage/http_reading.cpp b/test/storage/http_reading.cpp new file mode 100644 index 0000000000..0fbabf57f9 --- /dev/null +++ b/test/storage/http_reading.cpp @@ -0,0 +1,39 @@ +#include "../util.hpp" + +#include <uv.h> + +#include <mbgl/storage/default/default_file_source.hpp> + +TEST(Storage, HTTPReading) { + SCOPED_TEST(HTTPTest) + SCOPED_TEST(HTTP404) + + using namespace mbgl; + + DefaultFileSource fs(nullptr, uv_default_loop()); + + const auto mainThread = uv_thread_self(); + + fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, uv_default_loop(), [&](const Response &res) { + EXPECT_EQ(mainThread, uv_thread_self()); + EXPECT_EQ(res.status, Response::Successful); + EXPECT_EQ(res.data, "Hello World!"); + EXPECT_EQ(res.expires, 0); + EXPECT_EQ(res.modified, 0); + EXPECT_EQ(res.etag, ""); + EXPECT_EQ(res.message, ""); + HTTPTest.finish(); + }); + + fs.request({ Resource::Unknown, "http://127.0.0.1:3000/doesnotexist" }, uv_default_loop(), [&](const Response &res) { + EXPECT_EQ(mainThread, uv_thread_self()); + EXPECT_EQ(res.status, Response::Error); + EXPECT_EQ(res.message, "HTTP status code 404"); + EXPECT_EQ(res.expires, 0); + EXPECT_EQ(res.modified, 0); + EXPECT_EQ(res.etag, ""); + HTTP404.finish(); + }); + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); +} diff --git a/test/storage/server.js b/test/storage/server.js new file mode 100755 index 0000000000..b55d70ae71 --- /dev/null +++ b/test/storage/server.js @@ -0,0 +1,101 @@ +#!/usr/bin/env node +/* jshint node: true */ +'use strict'; + +var express = require('express'); +var app = express(); + +// We're manually setting Etag headers. +app.disable('etag'); + +app.get('/test', function (req, res) { + if (req.query.modified) { + res.setHeader('Last-Modified', (new Date(req.query.modified * 1000)).toUTCString()); + } + if (req.query.expires) { + res.setHeader('Expires', (new Date(req.query.expires * 1000)).toUTCString()); + } + if (req.query.etag) { + res.setHeader('ETag', req.query.etag); + } + if (req.query.cachecontrol) { + res.setHeader('Cache-Control', req.query.cachecontrol); + } + res.send('Hello World!'); +}); + + +var cacheCounter = 0; +app.get('/cache', function(req, res) { + res.setHeader('Cache-Control', 'max-age=30'); // Allow caching for 30 seconds + res.send('Response ' + (++cacheCounter)); +}); + +app.get('/revalidate-same', function(req, res) { + if (req.headers['if-none-match'] == 'snowfall') { + // Second request can be cached for 30 seconds. + res.setHeader('Cache-Control', 'max-age=30'); + res.status(304).end(); + } else { + // First request must always be revalidated. + res.setHeader('ETag', 'snowfall'); + res.setHeader('Cache-Control', 'must-revalidate'); + res.status(200).send('Response'); + } +}); + + +app.get('/revalidate-modified', function(req, res) { + var jan1 = new Date('jan 1 2015 utc'); + + if (req.headers['if-modified-since']) { + var modified_since = new Date(req.headers['if-modified-since']); + if (modified_since >= jan1) { + res.setHeader('Cache-Control', 'max-age=30'); + res.status(304).end(); + return; + } + } + + // First request must always be revalidated. + res.setHeader('Last-Modified', jan1.toUTCString()); + res.setHeader('Cache-Control', 'must-revalidate'); + res.status(200).send('Response'); +}); + + +var revalidateEtagCounter = 1; +app.get('/revalidate-etag', function(req, res) { + res.setHeader('ETag', 'response-' + revalidateEtagCounter); + res.setHeader('Cache-Control', 'must-revalidate'); + + res.status(200).send('Response ' + revalidateEtagCounter); + revalidateEtagCounter++; +}); + + +var temporaryErrorCounter = 0; +app.get('/temporary-error', function(req, res) { + if (temporaryErrorCounter === 0) { + res.status(500).end(); + } else { + res.status(200).send('Hello World!'); + } + + temporaryErrorCounter++; +}); + +app.get('/load/:number(\\d+)', function(req, res) { + res.send('Request ' + req.params.number); +}); + +var server = app.listen(3000, function () { + var host = server.address().address; + var port = server.address().port; + console.log('Test server listening at http://%s:%s', host, port); + + if (process.argv[2]) { + // Allow the test to continue running. + process.kill(+process.argv[2], 'SIGCONT'); + } +}); diff --git a/test/storage/storage.gypi b/test/storage/storage.gypi new file mode 100644 index 0000000000..b9b7835317 --- /dev/null +++ b/test/storage/storage.gypi @@ -0,0 +1,28 @@ +{ + 'targets': [ + { 'target_name': 'test_storage', + 'type': 'executable', + 'dependencies': [ + 'test_base', + '../mapboxgl.gyp:mbgl-core', + '../mapboxgl.gyp:mbgl-<(platform)', + '../deps/gtest/gtest.gyp:gtest' + ], + 'sources': [ + 'main.cpp', + 'cache_response.cpp', + 'cache_revalidate.cpp', + 'directory_reading.cpp', + 'file_reading.cpp', + 'http_cancel.cpp', + 'http_coalescing.cpp', + 'http_error.cpp', + 'http_header_parsing.cpp', + 'http_load.cpp', + 'http_noloop.cpp', + 'http_other_loop.cpp', + 'http_reading.cpp', + ], + }, + ], +} diff --git a/test/test.gyp b/test/test.gyp index 85958b1f03..98a9d67834 100644 --- a/test/test.gyp +++ b/test/test.gyp @@ -4,265 +4,73 @@ '../gyp/version.gypi', '../gyp/mbgl-platform.gypi', ], - 'variables' : { - 'ldflags': [ - '<@(uv_ldflags)', - '<@(sqlite3_static_libs)', - '<@(uv_static_libs)', - '<@(sqlite3_ldflags)', - '<@(curl_ldflags)', - '<@(png_ldflags)', - ], - }, 'targets': [ - { 'target_name': 'rotation_range', - 'product_name': 'test_rotation_range', - 'type': 'executable', - 'sources': [ - './main.cpp', - './rotation_range.cpp', - ], - 'dependencies': [ - '../deps/gtest/gtest.gyp:gtest', - '../mapboxgl.gyp:mbgl-standalone', - ], - 'include_dirs': [ '../src' ], - 'conditions': [ - ['OS == "mac"', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] } - }, { - 'libraries': [ '<@(ldflags)' ], - }] - ] - }, - { 'target_name': 'clip_ids', - 'product_name': 'test_clip_ids', - 'type': 'executable', - 'sources': [ - './main.cpp', - './clip_ids.cpp', - ], - 'dependencies': [ - '../deps/gtest/gtest.gyp:gtest', - '../mapboxgl.gyp:mbgl-standalone', - ], - 'include_dirs': [ '../src' ], - 'conditions': [ - ['OS == "mac"', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)'] } - }, { - 'libraries': [ '<@(ldflags)' ], - }] - ] - }, - { 'target_name': 'enums', - 'product_name': 'test_enums', - 'type': 'executable', - 'sources': [ - './main.cpp', - './enums.cpp', - ], - 'dependencies': [ - '../deps/gtest/gtest.gyp:gtest', - '../mapboxgl.gyp:mbgl-standalone', - ], - 'include_dirs': [ '../src' ], - 'conditions': [ - ['OS == "mac"', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)'] } - }, { - 'libraries': [ '<@(ldflags)' ], - }] - ] - }, - { 'target_name': 'style_parser', - 'product_name': 'test_style_parser', - 'type': 'executable', - 'sources': [ - './main.cpp', - './style_parser.cpp', - './fixtures/fixture_log.hpp', - './fixtures/fixture_log.cpp', - ], - 'dependencies': [ - '../deps/gtest/gtest.gyp:gtest', - '../mapboxgl.gyp:mbgl-standalone' - ], - 'include_dirs': [ '../src' ], - 'conditions': [ - ['OS == "mac"', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] } - }, { - 'libraries': [ '<@(ldflags)' ], - }] - ] - }, - { 'target_name': 'variant', - 'product_name': 'test_variant', - 'type': 'executable', - 'sources': [ - './main.cpp', - './variant.cpp', - ], - 'dependencies': [ - '../deps/gtest/gtest.gyp:gtest', - '../mapboxgl.gyp:mbgl-standalone', - ], - 'include_dirs': [ '../src' ], - 'conditions': [ - ['OS == "mac"', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] } - }, { - 'libraries': [ '<@(ldflags)' ], - }] - ] - }, - { 'target_name': 'comparisons', - 'product_name': 'test_comparisons', - 'type': 'executable', - 'sources': [ - './main.cpp', - './comparisons.cpp', - ], - 'dependencies': [ - '../deps/gtest/gtest.gyp:gtest', - '../mapboxgl.gyp:mbgl-standalone', - ], - 'include_dirs': [ '../src' ], - 'conditions': [ - ['OS == "mac"', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] } - }, { - 'libraries': [ '<@(ldflags)' ], - }] - ] - }, - { 'target_name': 'tile', - 'product_name': 'test_tile', - 'type': 'executable', - 'sources': [ - './main.cpp', - './tile.cpp', - ], - 'dependencies': [ - '../deps/gtest/gtest.gyp:gtest', - '../mapboxgl.gyp:mbgl-standalone', - ], - 'include_dirs': [ '../src' ], - 'conditions': [ - ['OS == "mac"', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] } - }, { - 'libraries': [ '<@(ldflags)' ], - }] - ] - }, - { 'target_name': 'functions', - 'product_name': 'test_functions', - 'type': 'executable', - 'sources': [ - './main.cpp', - './functions.cpp', - ], - 'dependencies': [ - '../deps/gtest/gtest.gyp:gtest', - '../mapboxgl.gyp:mbgl-standalone', - ], - 'include_dirs': [ '../src' ], - 'conditions': [ - ['OS == "mac"', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] } - }, { - 'libraries': [ '<@(ldflags)' ], - }] - ] - }, - { 'target_name': 'merge_lines', - 'product_name': 'test_merge_lines', - 'type': 'executable', - 'sources': [ - './main.cpp', - './merge_lines.cpp', - ], - 'dependencies': [ - '../deps/gtest/gtest.gyp:gtest', - '../mapboxgl.gyp:mbgl-standalone', - ], - 'include_dirs': [ '../src' ], - 'conditions': [ - ['OS == "mac"', { 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] } - }, { - 'libraries': [ '<@(ldflags)' ], - }] - ] + { 'target_name': 'test_base', + 'type': 'none', + 'direct_dependent_settings': { + 'include_dirs': [ '../include', '../src' ], + }, + 'link_settings': { + 'libraries': [ + '<@(uv_static_libs)', + '<@(glfw3_static_libs)', + '<@(sqlite3_static_libs)', + '<@(zlib_static_libs)', + ], + 'xcode_settings': { + 'OTHER_LDFLAGS': [ + '<@(uv_ldflags)', + '<@(glfw3_ldflags)', + '<@(sqlite3_ldflags)', + '<@(zlib_ldflags)', + ], + 'OTHER_CFLAGS': [ '<@(uv_cflags)' ], + 'OTHER_CPLUSPLUSFLAGS': [ '<@(uv_cflags)' ], + }, + 'cflags': [ '<@(uv_cflags)' ], + 'cxxflags': [ '<@(uv_cflags)' ], + }, }, - { 'target_name': 'headless', - 'product_name': 'test_headless', + + # Build all targets + { 'target_name': 'test', 'type': 'executable', - 'sources': [ - './headless.cpp', - './fixtures/fixture_log.cpp', - ], - 'conditions': [ - # add libuv include path and OpenGL libs - ['OS == "mac"', - { - 'xcode_settings': { - 'OTHER_CPLUSPLUSFLAGS': ['<@(uv_cflags)','<@(png_cflags)'], - 'OTHER_LDFLAGS': ['<@(glfw3_ldflags)', '<@(ldflags)'], - }, - }, - { - 'cflags': ['<@(uv_cflags)','<@(png_cflags)'], - 'libraries': ['<@(glfw3_ldflags)', '<@(ldflags)'], - }], - ], 'dependencies': [ - '../deps/gtest/gtest.gyp:gtest', + 'test_base', '../mapboxgl.gyp:mbgl-core', + '../mapboxgl.gyp:mbgl-<(platform)', '../mapboxgl.gyp:mbgl-headless', - '<(platform_library)', + '../deps/gtest/gtest.gyp:gtest' ], - 'include_dirs': [ '../src' ] - }, - { 'target_name': 'text_conversions', - 'product_name': 'test_text_conversions', - 'type': 'executable', 'sources': [ - './main.cpp', - './text_conversions.cpp', + 'main.cpp', + + 'headless/headless.cpp', + + 'miscellaneous/clip_ids.cpp', + 'miscellaneous/comparisons.cpp', + 'miscellaneous/enums.cpp', + 'miscellaneous/functions.cpp', + 'miscellaneous/merge_lines.cpp', + 'miscellaneous/rotation_range.cpp', + 'miscellaneous/style_parser.cpp', + 'miscellaneous/text_conversions.cpp', + 'miscellaneous/tile.cpp', + 'miscellaneous/variant.cpp', + + 'storage/cache_response.cpp', + 'storage/cache_revalidate.cpp', + 'storage/directory_reading.cpp', + 'storage/file_reading.cpp', + 'storage/http_cancel.cpp', + 'storage/http_coalescing.cpp', + 'storage/http_error.cpp', + 'storage/http_header_parsing.cpp', + 'storage/http_load.cpp', + 'storage/http_noloop.cpp', + 'storage/http_other_loop.cpp', + 'storage/http_reading.cpp', ], - 'dependencies': [ - '../deps/gtest/gtest.gyp:gtest', - '../mapboxgl.gyp:mbgl-standalone', - '<(platform_library)', - ], - 'include_dirs': [ '../src' ], - 'variables': { - 'cflags_cc': [ - '-I<(boost_root)/include', - ] - }, - 'conditions': [ - ['OS == "mac"', { - 'xcode_settings': { - 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ], - 'OTHER_LDFLAGS': [ '<@(ldflags)', '-framework Foundation' ] - }, - }, { - 'cflags_cc': [ '<@(cflags_cc)' ], - 'libraries': [ '<@(ldflags)'], - }] - ] }, - # Build all targets - { 'target_name': 'test', - 'type': 'none', - 'dependencies': [ - 'rotation_range', - 'clip_ids', - 'enums', - 'variant', - 'tile', - 'functions', - 'merge_lines', - 'headless', - 'style_parser', - 'comparisons', - 'text_conversions', - ], - } ] } diff --git a/test/util.hpp b/test/util.hpp new file mode 100644 index 0000000000..3511662c88 --- /dev/null +++ b/test/util.hpp @@ -0,0 +1,14 @@ +#ifndef MBGL_TEST_UTIL +#define MBGL_TEST_UTIL + +#include <gtest/gtest.h> + +#define SCOPED_TEST(name) \ + static class name { \ + bool completed = false; \ + public: \ + void finish() { EXPECT_FALSE(completed) << #name " was already completed."; completed = true; } \ + ~name() { if (!completed) ADD_FAILURE() << #name " didn't complete."; } \ + } name; + +#endif |