summaryrefslogtreecommitdiff
path: root/platform/darwin/test/MGLOfflineStorageTests.mm
diff options
context:
space:
mode:
Diffstat (limited to 'platform/darwin/test/MGLOfflineStorageTests.mm')
-rw-r--r--platform/darwin/test/MGLOfflineStorageTests.mm198
1 files changed, 195 insertions, 3 deletions
diff --git a/platform/darwin/test/MGLOfflineStorageTests.mm b/platform/darwin/test/MGLOfflineStorageTests.mm
index ee4bcc2c65..b44d4c51cd 100644
--- a/platform/darwin/test/MGLOfflineStorageTests.mm
+++ b/platform/darwin/test/MGLOfflineStorageTests.mm
@@ -1,15 +1,16 @@
#import <Mapbox/Mapbox.h>
+#import <XCTest/XCTest.h>
#import "MGLOfflineStorage_Private.h"
#import "NSBundle+MGLAdditions.h"
#import "NSDate+MGLAdditions.h"
-
-#import <XCTest/XCTest.h>
+#import "MGLTestAssertionHandler.h"
#include <mbgl/util/run_loop.hpp>
#pragma clang diagnostic ignored "-Wshadow"
+
@interface MGLOfflineStorageTests : XCTestCase <MGLOfflineStorageDelegate>
@end
@@ -34,7 +35,7 @@
- (void)setUp {
[super setUp];
-
+
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
XCTestExpectation *expectation = [self keyValueObservingExpectationForObject:[MGLOfflineStorage sharedOfflineStorage] keyPath:@"packs" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) {
@@ -310,6 +311,197 @@
XCTAssertEqual([MGLOfflineStorage sharedOfflineStorage].packs.count, countOfPacks - 1, @"Removed pack should have been removed from the canonical collection of packs owned by the shared offline storage object. This assertion can fail if this test is run before -testAAALoadPacks or -testAddPack.");
}
+- (void)addPacks:(NSInteger)count {
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"added packs"];
+
+ NSURL *styleURL = [MGLStyle lightStyleURLWithVersion:8];
+
+ MGLCoordinateBounds bounds[] = {
+ {{51.5, -0.2}, {51.6, -0.1}}, // London
+ {{60.1, 24.8}, {60.3, 25.1}}, // Helsinki
+ {{38.9, -77.1}, {38.9, -77.0}}, // DC
+ {{37.7, -122.5}, {37.9, -122.4}} // SF
+ };
+
+ int arraySize = sizeof(bounds)/sizeof(bounds[0]);
+
+ count = MIN(count, arraySize);
+
+ dispatch_group_t group = dispatch_group_create();
+
+ for (int i = 0; i < count; i++) {
+
+ dispatch_group_enter(group);
+ MGLTilePyramidOfflineRegion *region = [[MGLTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL bounds:bounds[i] fromZoomLevel:20 toZoomLevel:20];
+ NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{
+ @"index": @(i)
+ }];
+
+ [[MGLOfflineStorage sharedOfflineStorage] addPackForRegion:region
+ withContext:context
+ completionHandler:^(MGLOfflinePack * _Nullable pack, NSError * _Nullable error) {
+ XCTAssertNotNil(pack);
+ XCTAssertNil(error);
+
+ dispatch_group_leave(group);
+ }];
+ }
+
+ dispatch_group_notify(group, dispatch_get_main_queue(), ^{
+ [expectation fulfill];
+ });
+
+ [self waitForExpectations:@[expectation] timeout:1.0];
+}
+
+- (void)testRemovePackTwiceInSuccession {
+
+ [self addPacks:1];
+
+ NSUInteger countOfPacks = [MGLOfflineStorage sharedOfflineStorage].packs.count;
+
+ MGLOfflinePack *pack = [MGLOfflineStorage sharedOfflineStorage].packs.lastObject;
+ XCTAssertNotNil(pack, @"Added pack should still exist.");
+
+ [self keyValueObservingExpectationForObject:[MGLOfflineStorage sharedOfflineStorage] keyPath:@"packs" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) {
+ const auto changeKind = static_cast<NSKeyValueChange>([change[NSKeyValueChangeKindKey] unsignedLongValue]);
+ NSIndexSet *indices = change[NSKeyValueChangeIndexesKey];
+ return changeKind == NSKeyValueChangeRemoval && indices.count == 1;
+ }];
+
+ XCTestExpectation *completionHandlerExpectation = [self expectationWithDescription:@"remove pack completion handler"];
+
+ [[MGLOfflineStorage sharedOfflineStorage] removePack:pack withCompletionHandler:nil];
+
+ NSAssertionHandler *oldHandler = [NSAssertionHandler currentHandler];
+ MGLTestAssertionHandler *newHandler = [[MGLTestAssertionHandler alloc] initWithTestCase:self];
+
+ [[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey];
+
+ [[MGLOfflineStorage sharedOfflineStorage] removePack:pack withCompletionHandler:^(NSError * _Nullable error) {
+ XCTAssertEqual(pack.state, MGLOfflinePackStateInvalid, @"Removed pack should be invalid in the completion handler.");
+ [completionHandlerExpectation fulfill];
+ }];
+
+ [self waitForExpectationsWithTimeout:5 handler:nil];
+
+ [[[NSThread currentThread] threadDictionary] setValue:oldHandler forKey:NSAssertionHandlerKey];
+
+ XCTAssertEqual(pack.state, MGLOfflinePackStateInvalid, @"Removed pack should have been invalidated synchronously.");
+
+ XCTAssertEqual([MGLOfflineStorage sharedOfflineStorage].packs.count, countOfPacks - 1, @"Removed pack should have been removed from the canonical collection of packs owned by the shared offline storage object. This assertion can fail if this test is run before -testAAALoadPacks or -testAddPack.");
+
+ NSLog(@"Test `%@` complete", NSStringFromSelector(_cmd));
+}
+
+- (void)test15536RemovePacksWhileReloading {
+
+ // This test triggers
+ //
+ // throw std::runtime_error("Malformed offline region definition");
+ //
+ // in offline.cpp
+ //
+ // Reloading packs, while trying to remove them is currently problematic.
+
+ [self addPacks:4];
+
+ NSInteger countOfPacks = [MGLOfflineStorage sharedOfflineStorage].packs.count;
+ XCTAssert(countOfPacks > 0);
+
+ // Now delete packs one by one
+ XCTestExpectation *expectation = [self expectationWithDescription:@"All packs removed"];
+ expectation.expectedFulfillmentCount = countOfPacks;
+
+ MGLOfflineStorage *storage = [MGLOfflineStorage sharedOfflineStorage];
+ NSArray *packs = [storage.packs copy];
+
+ // Simulate what happens the first time sharedOfflineStorage is accessed
+ [storage reloadPacks];
+
+ NSArray *validPacks = [packs filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
+ MGLOfflinePack *pack = (MGLOfflinePack*)evaluatedObject;
+ return pack.state != MGLOfflinePackStateInvalid;
+ }]];
+
+ NSAssertionHandler *oldHandler = [NSAssertionHandler currentHandler];
+ MGLTestAssertionHandler *newHandler = [[MGLTestAssertionHandler alloc] initWithTestCase:self];
+
+ [[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey];
+
+ for (MGLOfflinePack *pack in validPacks) {
+ [storage removePack:pack withCompletionHandler:^(NSError * _Nullable error) {
+ [expectation fulfill];
+ }];
+ }
+
+ [[[NSThread currentThread] threadDictionary] setValue:oldHandler forKey:NSAssertionHandlerKey];
+
+ [self waitForExpectations:@[expectation] timeout:10.0];
+
+ // TODO: What should we expect here? All packs removed?
+
+ NSLog(@"Test `%@` complete", NSStringFromSelector(_cmd));
+}
+
+// Test to explore https://github.com/mapbox/mapbox-gl-native/issues/15536
+- (void)test15536RemovePacksOnBackgroundQueueWhileReloading {
+
+ [self addPacks:4];
+
+ NSInteger countOfPacks = [MGLOfflineStorage sharedOfflineStorage].packs.count;
+ XCTAssert(countOfPacks > 0);
+
+ // Now delete packs one by one
+ dispatch_queue_t queue = dispatch_queue_create("com.mapbox.testRemovePacks", DISPATCH_QUEUE_SERIAL);
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"all packs removed"];
+ expectation.expectedFulfillmentCount = countOfPacks;
+
+ MGLOfflineStorage *storage = [MGLOfflineStorage sharedOfflineStorage];
+
+ // Simulate what happens the first time sharedOfflineStorage is accessed
+ [storage reloadPacks];
+
+// NSArray *packs = [storage.packs copy];
+
+ dispatch_async(queue, ^{
+ NSArray *packs = storage.packs;
+ NSAssertionHandler *oldHandler = [NSAssertionHandler currentHandler];
+ MGLTestAssertionHandler *newHandler = [[MGLTestAssertionHandler alloc] initWithTestCase:self];
+
+ [[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey];
+
+ NSArray *validPacks = [packs filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
+ MGLOfflinePack *pack = (MGLOfflinePack*)evaluatedObject;
+ return pack.state != MGLOfflinePackStateInvalid;
+ }]];
+
+ for (MGLOfflinePack *pack in validPacks) {
+ // NOTE: pack can be invalid, as we have two threads potentially
+ // modifying the same MGLOfflinePack.
+
+ dispatch_group_t group = dispatch_group_create();
+ dispatch_group_enter(group);
+ [storage removePack:pack withCompletionHandler:^(NSError * _Nullable error) {
+ dispatch_group_leave(group);
+ }];
+ dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
+
+ [expectation fulfill];
+ }
+
+ [[[NSThread currentThread] threadDictionary] setValue:oldHandler forKey:NSAssertionHandlerKey];
+ });
+
+ [self waitForExpectations:@[expectation] timeout:60.0];
+
+ // TODO: What should we expect here? All packs removed?
+
+ NSLog(@"Test `%@` complete", NSStringFromSelector(_cmd));
+}
+
- (void)testCountOfBytesCompleted {
XCTAssertGreaterThan([MGLOfflineStorage sharedOfflineStorage].countOfBytesCompleted, 0UL);
}