diff options
Diffstat (limited to 'platform/darwin/test/MGLOfflineStorageTests.mm')
-rw-r--r-- | platform/darwin/test/MGLOfflineStorageTests.mm | 198 |
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); } |