diff options
author | Julian Rex <julian.rex@gmail.com> | 2018-09-05 11:53:12 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-09-05 11:53:12 -0400 |
commit | 315a9e3cdfef828da9d359209ce367d0d2b3ebe8 (patch) | |
tree | c7d374a515989d4c1f388c68879ed7b44748375d /platform/ios/Integration Tests | |
parent | 5aca8e9ffbc90e9b431869e616f8451b09c352ef (diff) | |
download | qtlocation-mapboxgl-315a9e3cdfef828da9d359209ce367d0d2b3ebe8.tar.gz |
[ios, macos] Call snapshotter (Obj-C) completion block on dealloc/cancel if snapshot hasn't finished. (#12355)
Diffstat (limited to 'platform/ios/Integration Tests')
5 files changed, 223 insertions, 38 deletions
diff --git a/platform/ios/Integration Tests/MGLMapViewIntegrationTest.h b/platform/ios/Integration Tests/MGLMapViewIntegrationTest.h index 4884f30b77..f513df8b20 100644 --- a/platform/ios/Integration Tests/MGLMapViewIntegrationTest.h +++ b/platform/ios/Integration Tests/MGLMapViewIntegrationTest.h @@ -28,6 +28,7 @@ @property (nonatomic) void (^regionDidChange)(MGLMapView *mapView, MGLCameraChangeReason reason, BOOL animated); // Utility methods +- (NSString*)validAccessToken; - (void)waitForMapViewToFinishLoadingStyleWithTimeout:(NSTimeInterval)timeout; - (void)waitForMapViewToBeRenderedWithTimeout:(NSTimeInterval)timeout; @end diff --git a/platform/ios/Integration Tests/MGLMapViewIntegrationTest.m b/platform/ios/Integration Tests/MGLMapViewIntegrationTest.m index f67150ba9b..68023f3556 100644 --- a/platform/ios/Integration Tests/MGLMapViewIntegrationTest.m +++ b/platform/ios/Integration Tests/MGLMapViewIntegrationTest.m @@ -6,6 +6,17 @@ @implementation MGLMapViewIntegrationTest +- (NSString*)validAccessToken { + NSString *accessToken = [[NSProcessInfo processInfo] environment][@"MAPBOX_ACCESS_TOKEN"]; + if (!accessToken) { + printf("warning: MAPBOX_ACCESS_TOKEN env var is required for this test - skipping.\n"); + return nil; + } + + [MGLAccountManager setAccessToken:accessToken]; + return accessToken; +} + - (void)setUp { [super setUp]; diff --git a/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterSwiftTests.swift b/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterSwiftTests.swift new file mode 100644 index 0000000000..bbdfce7531 --- /dev/null +++ b/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterSwiftTests.swift @@ -0,0 +1,62 @@ +import XCTest + +class MGLMapSnapshotterSwiftTests: MGLMapViewIntegrationTest { + + // Create snapshot options + private class func snapshotterOptions(size: CGSize) -> MGLMapSnapshotOptions { + let camera = MGLMapCamera() + + let options = MGLMapSnapshotOptions(styleURL: MGLStyle.satelliteStreetsStyleURL, camera: camera, size: size) + + let sw = CLLocationCoordinate2D(latitude: 52.3, longitude: 13.0) + let ne = CLLocationCoordinate2D(latitude: 52.5, longitude: 13.2) + options.coordinateBounds = MGLCoordinateBounds(sw:sw, ne:ne) + + return options + } + + func testCapturingSnapshotterInSnapshotCompletion() { + // See the Obj-C testDeallocatingSnapshotterDuringSnapshot + // This Swift test, is essentially the same except for capturing the snapshotter + guard validAccessToken() != nil else { + return + } + + let timeout: TimeInterval = 5.0 + let expectation = self.expectation(description: "snapshot") + + let options = MGLMapSnapshotterSwiftTests.snapshotterOptions(size: mapView.bounds.size) + + let backgroundQueue = DispatchQueue.main + + backgroundQueue.async { + let dg = DispatchGroup() + dg.enter() + + DispatchQueue.main.async { + + let snapshotter = MGLMapSnapshotter(options: options) + + snapshotter.start(completionHandler: { (snapshot, error) in + +// // Without capturing snapshotter: +// XCTAssertNil(snapshot) +// XCTAssertNotNil(error) + + // Capture snapshotter + dump(snapshotter) + XCTAssertNotNil(snapshot) + XCTAssertNil(error) + + dg.leave() + }) + } + + dg.notify(queue: .main) { + expectation.fulfill() + } + } + + wait(for: [expectation], timeout: timeout) + } +} diff --git a/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterTest.m b/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterTest.m index 22f7fc5911..fd5b3db978 100644 --- a/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterTest.m +++ b/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterTest.m @@ -1,5 +1,10 @@ #import "MGLMapViewIntegrationTest.h" +@interface MGLMapSnapshotter () +@property (nonatomic) BOOL cancelled; +@end + + @interface MGLMapSnapshotterTest : MGLMapViewIntegrationTest @end @@ -19,21 +24,10 @@ MGLMapSnapshotter* snapshotterWithCoordinates(CLLocationCoordinate2D coordinates return snapshotter; } -NSString* validAccessToken() { - NSString *accessToken = [[NSProcessInfo processInfo] environment][@"MAPBOX_ACCESS_TOKEN"]; - if (!accessToken) { - printf("warning: MAPBOX_ACCESS_TOKEN env var is required for this test - skipping.\n"); - return nil; - } - - [MGLAccountManager setAccessToken:accessToken]; - return accessToken; -} - @implementation MGLMapSnapshotterTest - (void)testMultipleSnapshotsWithASingleSnapshotter { - if (!validAccessToken()) { + if (![self validAccessToken]) { return; } @@ -66,8 +60,137 @@ NSString* validAccessToken() { [self waitForExpectations:@[expectation] timeout:10.0]; } +- (void)testDeallocatingSnapshotterDuringSnapshot { + // See also https://github.com/mapbox/mapbox-gl-native/issues/12336 + if (![self validAccessToken]) { + return; + } + + NSTimeInterval timeout = 5.0; + XCTestExpectation *expectation = [self expectationWithDescription:@"snapshot"]; + CGSize size = self.mapView.bounds.size; + CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(30.0, 30.0); + + // Test triggering to main queue + dispatch_queue_t backgroundQueue = dispatch_get_main_queue(); +// dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + + __weak __typeof__(self) weakself = self; + + dispatch_async(backgroundQueue, ^{ + + dispatch_group_t dg = dispatch_group_create(); + dispatch_group_enter(dg); + + dispatch_async(dispatch_get_main_queue(), ^{ + MGLMapSnapshotter *snapshotter = snapshotterWithCoordinates(coord, size); + __weak MGLMapSnapshotter *weakSnapshotter = snapshotter; + + [snapshotter startWithCompletionHandler:^(MGLMapSnapshot * _Nullable snapshot, NSError * _Nullable error) { + // We expect this completion block to be called with an error + __typeof__(self) strongself = weakself; + + MGLTestAssertNil(strongself, snapshot); + MGLTestAssert(strongself, + ([error.domain isEqualToString:MGLErrorDomain] && error.code == MGLErrorCodeSnapshotFailed), + @"Should have errored"); + MGLTestAssertNil(strongself, weakSnapshotter, @"Snapshotter should have been deallocated"); + + dispatch_group_leave(dg); + }]; + }); + + dispatch_group_notify(dg, dispatch_get_main_queue(), ^{ + [expectation fulfill]; + }); + }); + + [self waitForExpectations:@[expectation] timeout:timeout]; +} + +- (void)testSnapshotterUsingNestedDispatchQueues { + // This is the opposite pair to the above test `testDeallocatingSnapshotterDuringSnapshot` + // The only significant difference is that the snapshotter is a `__block` variable, so + // its lifetime should continue until it's set to nil in the completion block. + // See also https://github.com/mapbox/mapbox-gl-native/issues/12336 + + if (![self validAccessToken]) { + return; + } + + NSTimeInterval timeout = 5.0; + XCTestExpectation *expectation = [self expectationWithDescription:@"snapshot"]; + CGSize size = self.mapView.bounds.size; + CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(30.0, 30.0); + + // Test triggering to main queue + dispatch_queue_t backgroundQueue = dispatch_get_main_queue(); + // dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + + __weak __typeof__(self) weakself = self; + + dispatch_async(backgroundQueue, ^{ + + dispatch_group_t dg = dispatch_group_create(); + dispatch_group_enter(dg); + + dispatch_async(dispatch_get_main_queue(), ^{ + + __block MGLMapSnapshotter *snapshotter = snapshotterWithCoordinates(coord, size); + + [snapshotter startWithCompletionHandler:^(MGLMapSnapshot * _Nullable snapshot, NSError * _Nullable error) { + // We expect this completion block to be called with an error + __typeof__(self) strongself = weakself; + MGLTestAssertNotNil(strongself, snapshot); + MGLTestAssertNil(strongself, error, @"Snapshotter should have completed"); + dispatch_group_leave(dg); + snapshotter = nil; + }]; + }); + + dispatch_group_notify(dg, dispatch_get_main_queue(), ^{ + [expectation fulfill]; + }); + }); + + [self waitForExpectations:@[expectation] timeout:timeout]; +} + +- (void)testCancellingSnapshot { + if (![self validAccessToken]) { + return; + } + + XCTestExpectation *expectation = [self expectationWithDescription:@"snapshots"]; + expectation.assertForOverFulfill = YES; + expectation.expectedFulfillmentCount = 1; + + CGSize size = self.mapView.bounds.size; + CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(30.0, 30.0); + + MGLMapSnapshotter *snapshotter = snapshotterWithCoordinates(coord, size); + + __weak __typeof__(self) weakself = self; + + [snapshotter startWithCompletionHandler:^(MGLMapSnapshot * _Nullable snapshot, NSError * _Nullable error) { + // We expect this completion block to be called with an error + __typeof__(self) strongself = weakself; + + MGLTestAssertNil(strongself, snapshot); + MGLTestAssert(strongself, + ([error.domain isEqualToString:MGLErrorDomain] && error.code == MGLErrorCodeSnapshotFailed), + @"Should have been cancelled"); + MGLTestAssert(strongself, snapshotter.cancelled, @"Should have been cancelled"); + [expectation fulfill]; + }]; + + [snapshotter cancel]; + + [self waitForExpectations:@[expectation] timeout:0.5]; +} + - (void)testAllocatingSnapshotOnBackgroundQueue { - if (!validAccessToken()) { + if (![self validAccessToken]) { return; } @@ -79,7 +202,6 @@ NSString* validAccessToken() { dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, QOS_MIN_RELATIVE_PRIORITY); dispatch_queue_t backgroundQueue = dispatch_queue_create(__PRETTY_FUNCTION__, attr); - // This crashes maybe 1 in 10 times. dispatch_async(backgroundQueue, ^{ // Create the snapshotter - DO NOT START. @@ -104,8 +226,8 @@ NSString* validAccessToken() { [self waitForExpectations:@[expectation] timeout:2.0]; } -- (void)testSnapshotterFromBackgroundQueue { - if (!validAccessToken()) { +- (void)testSnapshotterFromBackgroundQueueShouldFail { + if (![self validAccessToken]) { return; } @@ -121,41 +243,25 @@ NSString* validAccessToken() { dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, QOS_MIN_RELATIVE_PRIORITY); // also for concurrent dispatch_queue_t backgroundQueue = dispatch_queue_create(__PRETTY_FUNCTION__, attr); - // Use dispatch_group to keep the backgroundQueue block around (and // so also the MGLMapSnapshotter dispatch_group_t group = dispatch_group_create(); dispatch_group_enter(group); - dispatch_async(backgroundQueue, ^{ MGLMapSnapshotter *snapshotter = snapshotterWithCoordinates(coord, size); XCTAssertNotNil(snapshotter); MGLMapSnapshotCompletionHandler completion = ^(MGLMapSnapshot * _Nullable snapshot, NSError * _Nullable error) { - - // This should be the main queue - __typeof__(self) strongself = weakself; - - MGLTestAssertNotNil(strongself, strongself); - - MGLTestAssertNotNil(strongself, snapshot); - MGLTestAssertNotNil(strongself, snapshot.image); - MGLTestAssertNil(strongself, error, @"Snapshot should not error with: %@", error); - - // Change this to XCTAttachmentLifetimeKeepAlways to be able to look at the snapshots after running - XCTAttachment *attachment = [XCTAttachment attachmentWithImage:snapshot.image]; - attachment.lifetime = XCTAttachmentLifetimeDeleteOnSuccess; - [strongself addAttachment:attachment]; - + // The completion block should not be called + MGLTestFail(weakself); dispatch_group_leave(group); }; - // untested @try { [snapshotter startWithCompletionHandler:completion]; - MGLTestFail(weakself); + MGLTestFail(weakself, @"startWithCompletionHandler: should raise an exception"); } @catch (NSException *exception) { MGLTestAssert(weakself, exception.name == NSInvalidArgumentException); @@ -177,7 +283,7 @@ NSString* validAccessToken() { - (void)testMultipleSnapshottersPENDING { MGL_CHECK_IF_PENDING_TEST_SHOULD_RUN(); - if (!validAccessToken()) { + if (![self validAccessToken]) { return; } @@ -235,7 +341,7 @@ NSString* validAccessToken() { } - (void)testSnapshotPointConversion { - if (!validAccessToken()) { + if (![self validAccessToken]) { return; } @@ -277,7 +383,7 @@ NSString* validAccessToken() { } - (void)testSnapshotPointConversionCoordinateOrdering { - if (!validAccessToken()) { + if (![self validAccessToken]) { return; } diff --git a/platform/ios/Integration Tests/integration-Bridging-Header.h b/platform/ios/Integration Tests/integration-Bridging-Header.h new file mode 100644 index 0000000000..55b44ea57b --- /dev/null +++ b/platform/ios/Integration Tests/integration-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// +#import "MGLMapViewIntegrationTest.h" + |