diff --git a/.ruby-version b/.ruby-version index fa376edcab7..a4dd9dba4fb 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -ruby-2.7 +2.7.4 diff --git a/FirebasePerformance/Sources/Instrumentation/FPRObjectInstrumentor.h b/FirebasePerformance/Sources/Instrumentation/FPRObjectInstrumentor.h index b016c070ef8..1716a56d4aa 100644 --- a/FirebasePerformance/Sources/Instrumentation/FPRObjectInstrumentor.h +++ b/FirebasePerformance/Sources/Instrumentation/FPRObjectInstrumentor.h @@ -33,6 +33,12 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)registerObject:(id)object; +/** Registers an instance of the delegate class to be instrumented. + * + * @param proxy The instance to instrument. + */ +- (void)registerProxy:(id)proxy; + @end /** This class allows the instrumentation of specific objects by isa swizzling specific instances diff --git a/FirebasePerformance/Sources/Instrumentation/FPRProxyObjectHelper.h b/FirebasePerformance/Sources/Instrumentation/FPRProxyObjectHelper.h index a746e8e0237..dadf4d50a09 100644 --- a/FirebasePerformance/Sources/Instrumentation/FPRProxyObjectHelper.h +++ b/FirebasePerformance/Sources/Instrumentation/FPRProxyObjectHelper.h @@ -31,4 +31,15 @@ forSuperclass:(Class)superclass varFoundHandler:(void (^)(id ivar))varFoundHandler; +/** Registers a proxy object for a given class and runs the onSuccess block whenever an ivar of the + * given class is discovered on the proxy object. + * + * @param proxy The proxy object whose ivars will be iterated. + * @param protocol The protocol all ivars will be compared against. See varFoundHandler. + * @param varFoundHandler The block to run when an ivar conformsToProtocol:protocol. + */ ++ (void)registerProxyObject:(id)proxy + forProtocol:(Protocol *)protocol + varFoundHandler:(void (^)(id ivar))varFoundHandler; + @end diff --git a/FirebasePerformance/Sources/Instrumentation/FPRProxyObjectHelper.m b/FirebasePerformance/Sources/Instrumentation/FPRProxyObjectHelper.m index ae46231ee5a..58a98c2a549 100644 --- a/FirebasePerformance/Sources/Instrumentation/FPRProxyObjectHelper.m +++ b/FirebasePerformance/Sources/Instrumentation/FPRProxyObjectHelper.m @@ -29,4 +29,15 @@ + (void)registerProxyObject:(id)proxy } } ++ (void)registerProxyObject:(id)proxy + forProtocol:(Protocol *)protocol + varFoundHandler:(void (^)(id ivar))varFoundHandler { + NSArray *ivars = [GULSwizzler ivarObjectsForObject:proxy]; + for (id ivar in ivars) { + if ([ivar conformsToProtocol:protocol]) { + varFoundHandler(ivar); + } + } +} + @end diff --git a/FirebasePerformance/Sources/Instrumentation/Network/Delegates/FPRNSURLConnectionDelegateInstrument.m b/FirebasePerformance/Sources/Instrumentation/Network/Delegates/FPRNSURLConnectionDelegateInstrument.m index a01fb2180a6..8123ae56b62 100644 --- a/FirebasePerformance/Sources/Instrumentation/Network/Delegates/FPRNSURLConnectionDelegateInstrument.m +++ b/FirebasePerformance/Sources/Instrumentation/Network/Delegates/FPRNSURLConnectionDelegateInstrument.m @@ -17,6 +17,7 @@ #import "FirebasePerformance/Sources/Instrumentation/FPRClassInstrumentor.h" #import "FirebasePerformance/Sources/Instrumentation/FPRInstrument_Private.h" #import "FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.h" +#import "FirebasePerformance/Sources/Instrumentation/FPRProxyObjectHelper.h" #import "FirebasePerformance/Sources/Instrumentation/FPRSelectorInstrumentor.h" #import "FirebasePerformance/Sources/Instrumentation/Network/Delegates/FPRNSURLConnectionDelegate.h" #import "FirebasePerformance/Sources/Instrumentation/Network/FPRNetworkInstrumentHelpers.h" @@ -304,4 +305,13 @@ - (void)registerObject:(id)object { }); } +- (void)registerProxy:(id)proxy { + [FPRProxyObjectHelper registerProxyObject:proxy + forProtocol:@protocol(NSURLSessionDelegate) + varFoundHandler:^(id ivar) { + [self registerClass:[ivar class]]; + [self registerObject:ivar]; + }]; +} + @end diff --git a/FirebasePerformance/Sources/Instrumentation/Network/Delegates/FPRNSURLSessionDelegateInstrument.m b/FirebasePerformance/Sources/Instrumentation/Network/Delegates/FPRNSURLSessionDelegateInstrument.m index c636d3e2ba7..6541caa2e27 100644 --- a/FirebasePerformance/Sources/Instrumentation/Network/Delegates/FPRNSURLSessionDelegateInstrument.m +++ b/FirebasePerformance/Sources/Instrumentation/Network/Delegates/FPRNSURLSessionDelegateInstrument.m @@ -18,6 +18,7 @@ #import "FirebasePerformance/Sources/Instrumentation/FPRClassInstrumentor.h" #import "FirebasePerformance/Sources/Instrumentation/FPRInstrument_Private.h" #import "FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.h" +#import "FirebasePerformance/Sources/Instrumentation/FPRProxyObjectHelper.h" #import "FirebasePerformance/Sources/Instrumentation/FPRSelectorInstrumentor.h" #import "FirebasePerformance/Sources/Instrumentation/Network/Delegates/FPRNSURLSessionDelegate.h" #import "FirebasePerformance/Sources/Instrumentation/Network/FPRNetworkInstrumentHelpers.h" @@ -263,4 +264,13 @@ - (void)registerObject:(id)object { }); } +- (void)registerProxy:(id)proxy { + [FPRProxyObjectHelper registerProxyObject:proxy + forProtocol:@protocol(NSURLSessionDelegate) + varFoundHandler:^(id ivar) { + [self registerClass:[ivar class]]; + [self registerObject:ivar]; + }]; +} + @end diff --git a/FirebasePerformance/Sources/Instrumentation/Network/FPRNSURLSessionInstrument.m b/FirebasePerformance/Sources/Instrumentation/Network/FPRNSURLSessionInstrument.m index 57394071199..610ba0fcc8c 100644 --- a/FirebasePerformance/Sources/Instrumentation/Network/FPRNSURLSessionInstrument.m +++ b/FirebasePerformance/Sources/Instrumentation/Network/FPRNSURLSessionInstrument.m @@ -157,9 +157,12 @@ void InstrumentSessionWithConfigurationDelegateDelegateQueue( ThrowExceptionBecauseInstrumentHasBeenDeallocated(selector, instrumentedClass); } if (delegate) { - [delegateInstrument registerClass:[delegate class]]; - [delegateInstrument registerObject:delegate]; - + if ([delegate isProxy]) { + [delegateInstrument registerProxy:delegate]; + } else { + [delegateInstrument registerClass:[delegate class]]; + [delegateInstrument registerObject:delegate]; + } } else { delegate = [[FPRNSURLSessionDelegate alloc] init]; } diff --git a/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m b/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m index ffffbe17c17..92d754e8385 100644 --- a/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m +++ b/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m @@ -62,6 +62,39 @@ - (void)forwardInvocation:(NSInvocation *)invocation { @end +@interface FPRNSURLSessionDelegateProxy : NSProxy { + // The wrapped delegate object. + id _delegate; +} + +/** @return an instance of the delegate proxy. */ +- (instancetype)initWithDelegate:(id)delegate; + +@end + +@implementation FPRNSURLSessionDelegateProxy + +- (instancetype)initWithDelegate:(id)delegate { + if (self) { + _delegate = delegate; + } + return self; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { + return [_delegate methodSignatureForSelector:selector]; +} + +- (BOOL)respondsToSelector:(SEL)aSelector { + return [_delegate respondsToSelector:aSelector]; +} + +- (void)forwardInvocation:(NSInvocation *)invocation { + [invocation invokeWithTarget:_delegate]; +} + +@end + @interface FPRNSURLSessionInstrumentTest : FPRTestCase /** Test server to create connections to. */ @@ -482,6 +515,33 @@ - (void)testDelegateURLSessionDownloadTaskDidFinishDownloadingToURL { [instrument deregisterInstrumentors]; } +/** Tests that the called delegate selector is wrapped and calls through. */ +- (void)testDelegateURLSessionDownloadDidReceiveResponseCompletionHandler { + FPRNSURLSessionInstrument *instrument; + NSURLSessionDataTask *dataTask; + @autoreleasepool { + instrument = [[FPRNSURLSessionInstrument alloc] init]; + [instrument registerInstrumentors]; + FPRNSURLSessionCompleteTestDelegate *delegate = + [[FPRNSURLSessionCompleteTestDelegate alloc] init]; + NSURLSessionConfiguration *configuration = + [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration + delegate:delegate + delegateQueue:nil]; + NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testBigDownload"]; + dataTask = [session dataTaskWithURL:URL]; + [dataTask resume]; + XCTAssertNotNil([FPRNetworkTrace networkTraceFromObject:dataTask]); + [self waitAndRunBlockAfterResponse:^(id self, GCDWebServerRequest *_Nonnull request, + GCDWebServerResponse *_Nonnull response) { + XCTAssertTrue(delegate.URLSessionDataTaskDidReceiveResponseCompletionHandlerCalled); + XCTAssertNil([FPRNetworkTrace networkTraceFromObject:dataTask]); + }]; + } + [instrument deregisterInstrumentors]; +} + /** Tests that the called delegate selector is wrapped and calls through. */ - (void)testDelegateURLSessionDownloadTaskDidResumeAtOffsetExpectedTotalBytes { FPRNSURLSessionInstrument *instrument = [[FPRNSURLSessionInstrument alloc] init]; @@ -548,6 +608,277 @@ - (void)testDelegateUnimplementedURLSessionTaskDidCompleteWithError { [instrument deregisterInstrumentors]; } +#pragma mark - Testing proxy delegate method wrapping + +/** Tests that the delegate class is instrumented once when its wrapped with NSProxy. */ +- (void)testProxyDelegateSwizzlesDelegateOnce { + FPRNSURLSessionInstrument *instrument = [[FPRNSURLSessionInstrument alloc] init]; + [instrument registerInstrumentors]; + FPRNSURLSessionCompleteTestDelegate *delegate = + [[FPRNSURLSessionCompleteTestDelegate alloc] init]; + FPRNSURLSessionDelegateProxy *proxyDelegate = + [[FPRNSURLSessionDelegateProxy alloc] initWithDelegate:delegate]; + NSURLSessionConfiguration *configuration = + [NSURLSessionConfiguration defaultSessionConfiguration]; + [NSURLSession sessionWithConfiguration:configuration delegate:proxyDelegate delegateQueue:nil]; + [NSURLSession sessionWithConfiguration:configuration delegate:proxyDelegate delegateQueue:nil]; + XCTAssertEqual(instrument.delegateInstrument.classInstrumentors.count, 1); + XCTAssertEqual(instrument.delegateInstrument.instrumentedClasses.count, 1); + XCTAssertTrue( + [instrument.delegateInstrument.instrumentedClasses containsObject:[delegate class]]); + [instrument deregisterInstrumentors]; +} + +/** Tests that the called delegate selector is wrapped and calls through. */ +- (void)testProxyDelegateURLSessionTaskDidCompleteWithError { + [self.testServer stop]; + FPRNSURLSessionInstrument *instrument = [[FPRNSURLSessionInstrument alloc] init]; + [instrument registerInstrumentors]; + FPRNSURLSessionCompleteTestDelegate *delegate = + [[FPRNSURLSessionCompleteTestDelegate alloc] init]; + FPRNSURLSessionDelegateProxy *proxyDelegate = + [[FPRNSURLSessionDelegateProxy alloc] initWithDelegate:delegate]; + // This request needs to fail. + NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://nonurl"]]; + NSURLSessionConfiguration *configuration = + [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration + delegate:proxyDelegate + delegateQueue:nil]; + NSURLSessionTask *task; + @autoreleasepool { + task = [session dataTaskWithRequest:request]; + [task resume]; + XCTAssertNotNil([FPRNetworkTrace networkTraceFromObject:task]); + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]]; + } + XCTAssertNil([FPRNetworkTrace networkTraceFromObject:task]); + XCTAssertTrue(delegate.URLSessionTaskDidCompleteWithErrorCalled); + [instrument deregisterInstrumentors]; + [self.testServer start]; +} + +/** Tests that the called delegate selector is wrapped and calls through. */ +- (void)testProxyDelegateURLSessionTaskDidSendBodyDataTotalBytesSentTotalBytesExpectedToSend { + FPRNSURLSessionInstrument *instrument = [[FPRNSURLSessionInstrument alloc] init]; + [instrument registerInstrumentors]; + FPRNSURLSessionCompleteTestDelegate *delegate = + [[FPRNSURLSessionCompleteTestDelegate alloc] init]; + FPRNSURLSessionDelegateProxy *proxyDelegate = + [[FPRNSURLSessionDelegateProxy alloc] initWithDelegate:delegate]; + NSURLSessionConfiguration *configuration = + [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration + delegate:proxyDelegate + delegateQueue:nil]; + NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testUpload"]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; + request.HTTPMethod = @"POST"; + + NSBundle *bundle = [FPRTestUtils getBundle]; + NSURL *fileURL = [bundle URLForResource:@"smallDownloadFile" withExtension:@""]; + NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromFile:fileURL]; + [uploadTask resume]; + + XCTAssertNotNil([FPRNetworkTrace networkTraceFromObject:uploadTask]); + FPRNetworkTrace *networkTrace = [FPRNetworkTrace networkTraceFromObject:uploadTask]; + + [self waitAndRunBlockAfterResponse:^(id self, GCDWebServerRequest *_Nonnull request, + GCDWebServerResponse *_Nonnull response) { + XCTAssertTrue(delegate.URLSessionTaskDidSendBodyDataTotalBytesSentTotalBytesExpectedCalled); + XCTAssert(networkTrace.requestSize > 0); + XCTAssert( + [networkTrace + timeIntervalBetweenCheckpointState:FPRNetworkTraceCheckpointStateInitiated + andState:FPRNetworkTraceCheckpointStateRequestCompleted] > 0); + XCTAssertNil([FPRNetworkTrace networkTraceFromObject:uploadTask]); + }]; + [instrument deregisterInstrumentors]; +} + +/** Tests that the called delegate selector is wrapped and calls through. */ +- (void)testProxyDelegateURLSessionTaskWillPerformHTTPRedirectionNewRequestCompletionHandler { + FPRNSURLSessionInstrument *instrument = [[FPRNSURLSessionInstrument alloc] init]; + [instrument registerInstrumentors]; + FPRNSURLSessionCompleteTestDelegate *delegate = + [[FPRNSURLSessionCompleteTestDelegate alloc] init]; + FPRNSURLSessionDelegateProxy *proxyDelegate = + [[FPRNSURLSessionDelegateProxy alloc] initWithDelegate:delegate]; + NSURLSessionConfiguration *configuration = + [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testRedirect"]; + NSURLRequest *request = [NSURLRequest requestWithURL:URL]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration + delegate:proxyDelegate + delegateQueue:nil]; + NSURLSessionTask *task = [session dataTaskWithRequest:request]; + [task resume]; + XCTAssertNotNil([FPRNetworkTrace networkTraceFromObject:task]); + [self waitAndRunBlockAfterResponse:^(id self, GCDWebServerRequest *_Nonnull request, + GCDWebServerResponse *_Nonnull response) { + XCTAssertTrue( + delegate.URLSessionTaskWillPerformHTTPRedirectionNewRequestCompletionHandlerCalled); + }]; + [instrument deregisterInstrumentors]; +} + +/** Tests that the called delegate selector is wrapped and calls through. */ +- (void)testProxyDelegateURLSessionDataTaskDidReceiveData { + FPRNSURLSessionInstrument *instrument; + NSURLSessionDataTask *dataTask; + @autoreleasepool { + instrument = [[FPRNSURLSessionInstrument alloc] init]; + [instrument registerInstrumentors]; + FPRNSURLSessionCompleteTestDelegate *delegate = + [[FPRNSURLSessionCompleteTestDelegate alloc] init]; + FPRNSURLSessionDelegateProxy *proxyDelegate = + [[FPRNSURLSessionDelegateProxy alloc] initWithDelegate:delegate]; + NSURLSessionConfiguration *configuration = + [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration + delegate:proxyDelegate + delegateQueue:nil]; + NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testBigDownload"]; + dataTask = [session dataTaskWithURL:URL]; + [dataTask resume]; + XCTAssertNotNil([FPRNetworkTrace networkTraceFromObject:dataTask]); + [self waitAndRunBlockAfterResponse:^(id self, GCDWebServerRequest *_Nonnull request, + GCDWebServerResponse *_Nonnull response) { + XCTAssertTrue(delegate.URLSessionDataTaskDidReceiveDataCalled); + XCTAssertNil([FPRNetworkTrace networkTraceFromObject:dataTask]); + }]; + } + [instrument deregisterInstrumentors]; +} + +/** Tests that the called delegate selector is wrapped and calls through. */ +- (void)testProxyDelegateURLSessionDownloadTaskDidFinishDownloadingToURL { + FPRNSURLSessionInstrument *instrument = [[FPRNSURLSessionInstrument alloc] init]; + [instrument registerInstrumentors]; + FPRNSURLSessionCompleteTestDelegate *delegate = + [[FPRNSURLSessionCompleteTestDelegate alloc] init]; + FPRNSURLSessionDelegateProxy *proxyDelegate = + [[FPRNSURLSessionDelegateProxy alloc] initWithDelegate:delegate]; + NSURLSessionConfiguration *configuration = + [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration + delegate:proxyDelegate + delegateQueue:nil]; + NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testDownload"]; + NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:URL]; + [downloadTask resume]; + XCTAssertNotNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); + [self waitAndRunBlockAfterResponse:^(id self, GCDWebServerRequest *_Nonnull request, + GCDWebServerResponse *_Nonnull response) { + XCTAssertTrue(delegate.URLSessionDownloadTaskDidFinishDownloadingToURLCalled); + XCTAssertNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); + }]; + [instrument deregisterInstrumentors]; +} + +/** Tests that the called delegate selector is wrapped and calls through. */ +- (void)testProxyDelegateURLSessionDownloadDidReceiveResponseCompletionHandler { + FPRNSURLSessionInstrument *instrument; + NSURLSessionDataTask *dataTask; + @autoreleasepool { + instrument = [[FPRNSURLSessionInstrument alloc] init]; + [instrument registerInstrumentors]; + FPRNSURLSessionCompleteTestDelegate *delegate = + [[FPRNSURLSessionCompleteTestDelegate alloc] init]; + FPRNSURLSessionDelegateProxy *proxyDelegate = + [[FPRNSURLSessionDelegateProxy alloc] initWithDelegate:delegate]; + NSURLSessionConfiguration *configuration = + [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration + delegate:proxyDelegate + delegateQueue:nil]; + NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testBigDownload"]; + dataTask = [session dataTaskWithURL:URL]; + [dataTask resume]; + XCTAssertNotNil([FPRNetworkTrace networkTraceFromObject:dataTask]); + [self waitAndRunBlockAfterResponse:^(id self, GCDWebServerRequest *_Nonnull request, + GCDWebServerResponse *_Nonnull response) { + XCTAssertTrue(delegate.URLSessionDataTaskDidReceiveResponseCompletionHandlerCalled); + XCTAssertTrue(delegate.URLSessionTaskDidCompleteWithErrorCalled); + XCTAssertNil([FPRNetworkTrace networkTraceFromObject:dataTask]); + }]; + } + [instrument deregisterInstrumentors]; +} + +/** Tests that the called delegate selector is wrapped and calls through. */ +- (void)testProxyDelegateURLSessionDownloadTaskDidResumeAtOffsetExpectedTotalBytes { + FPRNSURLSessionInstrument *instrument = [[FPRNSURLSessionInstrument alloc] init]; + [instrument registerInstrumentors]; + FPRNSURLSessionTestDownloadDelegate *delegate = + [[FPRNSURLSessionTestDownloadDelegate alloc] init]; + FPRNSURLSessionDelegateProxy *proxyDelegate = + [[FPRNSURLSessionDelegateProxy alloc] initWithDelegate:delegate]; + NSURLSessionConfiguration *configuration = + [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration + delegate:proxyDelegate + delegateQueue:nil]; + NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testBigDownload"]; + NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:URL]; + [downloadTask resume]; + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]]; + XCTAssertNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); + XCTAssertTrue(delegate.URLSessionDownloadTaskDidResumeAtOffsetExpectedTotalBytesCalled); + [instrument deregisterInstrumentors]; +} + +/** Tests that the called delegate selector is wrapped and calls through. */ +- (void) + testProxyDelegateURLSessionDownloadTaskDidWriteDataTotalBytesWrittenTotalBytesExpectedToWrite { + FPRNSURLSessionInstrument *instrument = [[FPRNSURLSessionInstrument alloc] init]; + [instrument registerInstrumentors]; + FPRNSURLSessionCompleteTestDelegate *delegate = + [[FPRNSURLSessionCompleteTestDelegate alloc] init]; + FPRNSURLSessionDelegateProxy *proxyDelegate = + [[FPRNSURLSessionDelegateProxy alloc] initWithDelegate:delegate]; + NSURLSessionConfiguration *configuration = + [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration + delegate:proxyDelegate + delegateQueue:nil]; + NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testBigDownload"]; + NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:URL]; + [downloadTask resume]; + XCTAssertNotNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); + [self waitAndRunBlockAfterResponse:^(id self, GCDWebServerRequest *_Nonnull request, + GCDWebServerResponse *_Nonnull response) { + XCTAssertTrue(delegate.URLSessionDownloadTaskDidWriteDataTotalBytesWrittenTotalBytesCalled); + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; + XCTAssertNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); + }]; + [instrument deregisterInstrumentors]; +} + +/** Tests that even if a delegate doesn't implement a method, we add it to the delegate class. */ +- (void)testProxyDelegateUnimplementedURLSessionTaskDidCompleteWithError { + FPRNSURLSessionTestDelegate *delegate = [[FPRNSURLSessionTestDelegate alloc] init]; + FPRNSURLSessionDelegateProxy *proxyDelegate = + [[FPRNSURLSessionDelegateProxy alloc] initWithDelegate:delegate]; + FPRNSURLSessionInstrument *instrument = [[FPRNSURLSessionInstrument alloc] init]; + [instrument registerInstrumentors]; + NSURLSessionConfiguration *configuration = + [NSURLSessionConfiguration defaultSessionConfiguration]; + XCTAssertFalse([delegate respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]); + NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration + delegate:proxyDelegate + delegateQueue:nil]; + XCTAssertTrue([delegate respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]); + NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:self.testServer.serverURL]; + [downloadTask resume]; + XCTAssertNotNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); + [self waitAndRunBlockAfterResponse:^(id self, GCDWebServerRequest *_Nonnull request, + GCDWebServerResponse *_Nonnull response) { + XCTAssertNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); + }]; + [instrument deregisterInstrumentors]; +} + #pragma mark - Testing instance method wrapping /** Tests that dataTaskWithRequest: returns a non-nil object. */ diff --git a/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTestDelegates.h b/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTestDelegates.h index 722f508fa84..1fe8a746943 100644 --- a/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTestDelegates.h +++ b/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTestDelegates.h @@ -52,6 +52,12 @@ NS_ASSUME_NONNULL_BEGIN /** Set to YES when URLSession:dataTask:didReceiveData: is called, used for testing. */ @property(nonatomic) BOOL URLSessionDataTaskDidReceiveDataCalled; +/** Set to YES when + * URLSession:dataTask:didReceiveResponse:completionHandler: is called, used + * for testing. + */ +@property(nonatomic) BOOL URLSessionDataTaskDidReceiveResponseCompletionHandlerCalled; + /** Set to YES when URLSession:downloadTask:didFinishDownloadingToURL: is called, used for testing. */ @property(nonatomic) BOOL URLSessionDownloadTaskDidFinishDownloadingToURLCalled; diff --git a/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTestDelegates.m b/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTestDelegates.m index 28525bd4967..9d84a70aa43 100644 --- a/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTestDelegates.m +++ b/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTestDelegates.m @@ -72,6 +72,14 @@ - (void)URLSession:(NSURLSession *)session self.URLSessionDataTaskDidReceiveDataCalled = YES; } +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + didReceiveResponse:(NSURLResponse *)response + completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { + self.URLSessionDataTaskDidReceiveResponseCompletionHandlerCalled = YES; + completionHandler(NSURLSessionResponseAllow); +} + #pragma mark - NSURLSessionDownloadDelegate methods - (void)URLSession:(NSURLSession *)session