From e0ca919e5cb43c076bb30e88868ac16d3841b367 Mon Sep 17 00:00:00 2001 From: Jesus Rojas Date: Fri, 12 Jun 2026 16:08:12 -0600 Subject: [PATCH 1/9] [Perf] Migrate unswizzle-based unit tests to SPM (#11903) --- FirebasePerformance.podspec | 3 --- .../Instrumentation/FPRSelectorInstrumentor.m | 19 +++++++++++-------- .../Tests/Unit/FPRClassInstrumentorTest.m | 4 ---- .../Tests/Unit/FPRInstrumentTest.m | 4 ---- .../Tests/Unit/FPRInstrumentationTest.m | 4 ---- .../Tests/Unit/FPRSelectorInstrumentorTest.m | 4 ---- .../FPRNSURLConnectionInstrumentTest.m | 4 ---- .../FPRNSURLSessionInstrumentTest.m | 4 ---- .../FPRUIViewControllerInstrumentTest.m | 17 ++++++++++++----- .../Tests/Unit/Timer/FPRCounterListTest.m | 4 ---- FirebasePerformance/generate_project.sh | 3 --- Package.swift | 2 ++ scripts/build.sh | 5 +---- 13 files changed, 26 insertions(+), 51 deletions(-) diff --git a/FirebasePerformance.podspec b/FirebasePerformance.podspec index 47c61234552..8ab8bae697c 100644 --- a/FirebasePerformance.podspec +++ b/FirebasePerformance.podspec @@ -45,9 +45,6 @@ Firebase Performance library to measure performance of Mobile and Web Apps. preprocessor_definitions = 'FIRPerformance_LIB_VERSION=' + String(s.version) preprocessor_definitions += ' PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1' - if ENV['FPR_UNSWIZZLE_AVAILABLE'] && ENV['FPR_UNSWIZZLE_AVAILABLE'] == '1' then - preprocessor_definitions += ' UNSWIZZLE_AVAILABLE=1' - end s.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => preprocessor_definitions, diff --git a/FirebasePerformance/Sources/Instrumentation/FPRSelectorInstrumentor.m b/FirebasePerformance/Sources/Instrumentation/FPRSelectorInstrumentor.m index 0f474e5436a..d39df985888 100644 --- a/FirebasePerformance/Sources/Instrumentation/FPRSelectorInstrumentor.m +++ b/FirebasePerformance/Sources/Instrumentation/FPRSelectorInstrumentor.m @@ -17,9 +17,8 @@ #import "FirebasePerformance/Sources/Common/FPRDiagnostics.h" #import -#ifdef UNSWIZZLE_AVAILABLE -#import -#endif + +#import @implementation FPRSelectorInstrumentor { // The class this instrumentor operates on. @@ -76,11 +75,15 @@ - (void)swizzle { - (void)unswizzle { _swizzled = NO; -#ifdef UNSWIZZLE_AVAILABLE - [GULSwizzler unswizzleClass:_class selector:_selector isClassSelector:_isClassSelector]; -#else - NSAssert(NO, @"Unswizzling is disabled."); -#endif + // Unswizzling is provided by GoogleUtilities' GULSwizzlerTestHelpers category on GULSwizzler, + // which is only present at runtime when the hosting target links it (e.g. unit tests). + SEL unswizzleSelector = NSSelectorFromString(@"unswizzleClass:selector:isClassSelector:"); + if ([GULSwizzler respondsToSelector:unswizzleSelector]) { + ((void (*)(Class, SEL, Class, SEL, BOOL))objc_msgSend)([GULSwizzler class], unswizzleSelector, + _class, _selector, _isClassSelector); + } else { + NSAssert(NO, @"Unswizzling is disabled."); + } } - (IMP)currentIMP { diff --git a/FirebasePerformance/Tests/Unit/FPRClassInstrumentorTest.m b/FirebasePerformance/Tests/Unit/FPRClassInstrumentorTest.m index 5e07dd9a3ea..0b72ea5cd9a 100644 --- a/FirebasePerformance/Tests/Unit/FPRClassInstrumentorTest.m +++ b/FirebasePerformance/Tests/Unit/FPRClassInstrumentorTest.m @@ -57,8 +57,6 @@ - (void)testCannotInstrumentSameSelectorTwice { #pragma mark - Unswizzle based tests -#if !SWIFT_PACKAGE - /** Tests swizzling an instance selector. */ - (void)testSwizzleInstanceSelector { FPRClassInstrumentor *classInstrumentor = @@ -158,6 +156,4 @@ - (void)testVoidReturnClassSelector { [classInstrumentor unswizzle]; } -#endif // SWIFT_PACKAGE - @end diff --git a/FirebasePerformance/Tests/Unit/FPRInstrumentTest.m b/FirebasePerformance/Tests/Unit/FPRInstrumentTest.m index 245d0ac0d47..82a2af6f64b 100644 --- a/FirebasePerformance/Tests/Unit/FPRInstrumentTest.m +++ b/FirebasePerformance/Tests/Unit/FPRInstrumentTest.m @@ -65,8 +65,6 @@ - (void)testRegisterAlreadyRegisteredClassInstrumentor { #pragma mark - Unswizzle based tests -#if !SWIFT_PACKAGE - - (void)testDeregisterInstrumentors { FPRInstrument *instrument = [[FPRInstrument alloc] init]; FPRClassInstrumentor *classInstrumentor = @@ -86,6 +84,4 @@ - (void)testDeregisterInstrumentors { [classInstrumentor unswizzle]; } -#endif // SWIFT_PACKAGE - @end diff --git a/FirebasePerformance/Tests/Unit/FPRInstrumentationTest.m b/FirebasePerformance/Tests/Unit/FPRInstrumentationTest.m index 9e69f86ccd4..5adab5b0055 100644 --- a/FirebasePerformance/Tests/Unit/FPRInstrumentationTest.m +++ b/FirebasePerformance/Tests/Unit/FPRInstrumentationTest.m @@ -37,8 +37,6 @@ - (void)testInit { #pragma mark - Unswizzle based tests -#ifndef SWIFT_PACKAGE - - (void)testRegisterInstrumentGroup { FPRInstrumentation *instrumentation = [[FPRInstrumentation alloc] init]; NSUInteger numberOfInstrumentsInGroup = @@ -53,6 +51,4 @@ - (void)testDeregisterInstrumentGroup { XCTAssertTrue([instrumentation deregisterInstrumentGroup:kFPRInstrumentationGroupNetworkKey]); } -#endif // SWIFT_PACKAGE - @end diff --git a/FirebasePerformance/Tests/Unit/FPRSelectorInstrumentorTest.m b/FirebasePerformance/Tests/Unit/FPRSelectorInstrumentorTest.m index 7e8214e61c4..47c1882da03 100644 --- a/FirebasePerformance/Tests/Unit/FPRSelectorInstrumentorTest.m +++ b/FirebasePerformance/Tests/Unit/FPRSelectorInstrumentorTest.m @@ -57,8 +57,6 @@ - (void)testInitWithSelector { #pragma mark - Unswizzle based tests -#ifndef SWIFT_PACKAGE - - (void)testInstanceMethodSwizzle { NSString *expectedDescription = @"Not the description you expected!"; FPRSelectorInstrumentor *instrumentor = @@ -329,8 +327,6 @@ typedef void (^DataTaskCompletionHandler)(NSData *_Nullable, NSURLResponse *_Nul } } -#endif // SWIFT_PACKAGE - /** Tests attempting to swizzle non-existent/unimplemented method (like @dynamic) returns nil. */ - (void)testNonexistentMethodReturnsNil { FPRSelectorInstrumentor *instrumentor = diff --git a/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLConnectionInstrumentTest.m b/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLConnectionInstrumentTest.m index bb3e3520f39..c6c9f65bf2b 100644 --- a/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLConnectionInstrumentTest.m +++ b/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLConnectionInstrumentTest.m @@ -14,8 +14,6 @@ #pragma mark - Unswizzle based tests -#if !SWIFT_PACKAGE - #import "FirebasePerformance/Tests/Unit/Instruments/FPRNSURLConnectionInstrumentTestDelegates.h" #import @@ -519,5 +517,3 @@ - (void)testDownloadDelegateCompletionAPIGetsCalledEvenIfDataDelegateIsImplement } @end - -#endif // SWIFT_PACKAGE diff --git a/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m b/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m index cd1d9906ffe..1842a24c937 100644 --- a/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m +++ b/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m @@ -14,8 +14,6 @@ #pragma mark - Unswizzle based tests -#ifndef SWIFT_PACKAGE - #import "FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTestDelegates.h" #import @@ -1143,5 +1141,3 @@ - (void)testMutableRequestURLs { } @end - -#endif // SWIFT_PACKAGE diff --git a/FirebasePerformance/Tests/Unit/Instruments/FPRUIViewControllerInstrumentTest.m b/FirebasePerformance/Tests/Unit/Instruments/FPRUIViewControllerInstrumentTest.m index 005818f63a0..44bad282815 100644 --- a/FirebasePerformance/Tests/Unit/Instruments/FPRUIViewControllerInstrumentTest.m +++ b/FirebasePerformance/Tests/Unit/Instruments/FPRUIViewControllerInstrumentTest.m @@ -14,8 +14,6 @@ #pragma mark - Unswizzle based tests -#if !SWIFT_PACKAGE - #import "FirebasePerformance/Sources/Instrumentation/UIKit/FPRUIViewControllerInstrument.h" #import @@ -59,7 +57,16 @@ - (void)setUp { */ - (void)testViewDidAppearInvokesViewControllerDidAppearOnScreenTraceTracker { UIViewController *testViewController = [[UIViewController alloc] init]; - [[UIApplication sharedApplication].keyWindow addSubview:[testViewController view]]; + + // The instrument only reports view controllers whose view is in the key window of a running + // application. Neither a shared application nor a key window exists in an unhosted test + // runner, so both are stubbed. + id applicationMock = OCMClassMock([UIApplication class]); + OCMStub(ClassMethod([applicationMock sharedApplication])).andReturn(applicationMock); + UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + id windowMock = OCMPartialMock(window); + OCMStub([(UIWindow *)windowMock isKeyWindow]).andReturn(YES); + [window addSubview:[testViewController view]]; FPRUIViewControllerInstrument *instrument = [[FPRUIViewControllerInstrument alloc] init]; [instrument registerInstrumentors]; @@ -75,6 +82,8 @@ - (void)testViewDidAppearInvokesViewControllerDidAppearOnScreenTraceTracker { [instrument deregisterInstrumentors]; [[testViewController view] removeFromSuperview]; + [windowMock stopMocking]; + [applicationMock stopMocking]; } /** Tests that the viewControllerDidAppear: of the FPRScreenTraceTracker sharedInstance is invoked @@ -172,5 +181,3 @@ - (void)testViewDidAppearInvokesPreviousViewDidDisappear { } @end - -#endif // SWIFT_PACKAGE diff --git a/FirebasePerformance/Tests/Unit/Timer/FPRCounterListTest.m b/FirebasePerformance/Tests/Unit/Timer/FPRCounterListTest.m index 00b2c78d38f..d31cd61c6e1 100644 --- a/FirebasePerformance/Tests/Unit/Timer/FPRCounterListTest.m +++ b/FirebasePerformance/Tests/Unit/Timer/FPRCounterListTest.m @@ -29,18 +29,14 @@ + (void)setUp { [super setUp]; FIRPerformance *performance = [FIRPerformance sharedInstance]; [performance setDataCollectionEnabled:YES]; -#ifdef UNSWIZZLE_AVAILABLE [[FPRClient sharedInstance] disableInstrumentation]; -#endif // UNSWIZZLE_AVAILABLE } + (void)tearDown { [super tearDown]; FIRPerformance *performance = [FIRPerformance sharedInstance]; [performance setDataCollectionEnabled:NO]; -#ifdef UNSWIZZLE_AVAILABLE [[FPRClient sharedInstance] disableInstrumentation]; -#endif // UNSWIZZLE_AVAILABLE } /** Validates counterlist object creation. */ diff --git a/FirebasePerformance/generate_project.sh b/FirebasePerformance/generate_project.sh index cc5c175507a..0e2c87ac1e4 100755 --- a/FirebasePerformance/generate_project.sh +++ b/FirebasePerformance/generate_project.sh @@ -50,9 +50,6 @@ fi readonly DIR="$(git rev-parse --show-toplevel)" -# Enable Unswizzling in the development time (This is for unit test purposes). -export FPR_UNSWIZZLE_AVAILABLE="1" - # Enable AUTOPUSH to enable test App sending data to Autopush. # To enable sending data to Prod, remove the following line and regenerate the project. if [ "autopush" == "$env" ] diff --git a/Package.swift b/Package.swift index ac3a3f633a6..58ca7db221d 100644 --- a/Package.swift +++ b/Package.swift @@ -925,6 +925,8 @@ let package = Package( "FirebasePerformanceTarget", "SharedTestUtilities", "GCDWebServer", + .product(name: "GULMethodSwizzler", package: "GoogleUtilities"), + .product(name: "GULSwizzlerTestHelpers", package: "GoogleUtilities"), .product(name: "OCMock", package: "ocmock"), ], path: "FirebasePerformance/Tests/Unit", diff --git a/scripts/build.sh b/scripts/build.sh index 659ad732b7c..4bb8dc0dca1 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -723,8 +723,7 @@ case "$product-$platform-$method" in ;; Performance-*-unit) - # Run unit tests on prod environment with unswizzle capabilities. - export FPR_UNSWIZZLE_AVAILABLE="1" + # Run unit tests on prod environment. export FPR_AUTOPUSH_ENV="0" pod_gen FirebasePerformance.podspec --platforms="${gen_platform}" RunXcodebuild \ @@ -737,7 +736,6 @@ case "$product-$platform-$method" in Performance-*-proddev) # Build the prod dev test app. - export FPR_UNSWIZZLE_AVAILABLE="0" export FPR_AUTOPUSH_ENV="0" pod_gen FirebasePerformance.podspec --platforms="${gen_platform}" RunXcodebuild \ @@ -749,7 +747,6 @@ case "$product-$platform-$method" in Performance-*-integration) # Generate the workspace for the SDK to generate Protobuf files. - export FPR_UNSWIZZLE_AVAILABLE="0" pod_gen FirebasePerformance.podspec --platforms=ios --clean # Perform "pod install" to install the relevant dependencies From 5fa92f29275fab4f7ecfcb7473fa291f8d30ce0d Mon Sep 17 00:00:00 2001 From: Jesus Rojas Date: Fri, 12 Jun 2026 17:00:35 -0600 Subject: [PATCH 2/9] Fix update_firebase_spm_dependency.sh for branch names containing '#' --- scripts/update_firebase_spm_dependency.sh | 28 +++++++++++++++-------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/scripts/update_firebase_spm_dependency.sh b/scripts/update_firebase_spm_dependency.sh index 27d54e4658d..7d1523e21f9 100755 --- a/scripts/update_firebase_spm_dependency.sh +++ b/scripts/update_firebase_spm_dependency.sh @@ -57,12 +57,13 @@ REQUIREMENT_REGEX='({'\ '(\s*};'\ '\s*};)' -# Define the replacement regex based on the selected mode. +# Define the replacement requirement based on the selected mode. The new +# requirement is a kind (branch or revision) plus its value. case "$MODE" in --version) if [[ $# -lt 1 ]]; then echo "Error: Missing version for --version"; exit 1; fi - VERSION="$1" - REPLACEMENT_REGEX="\1kind = branch;\n\t\t\t\tbranch = \"$VERSION\";\2" + SPM_KIND="branch" + SPM_VALUE="$1" ;; --prerelease) COMMIT_HASH=$(git ls-remote https://github.com/firebase/firebase-ios-sdk.git refs/heads/main | cut -f1) @@ -70,17 +71,18 @@ case "$MODE" in echo "Error: Failed to get remote revision for main branch." exit 1 fi - REPLACEMENT_REGEX="\1kind = revision;\n\t\t\t\trevision = \"$COMMIT_HASH\";\2" + SPM_KIND="revision" + SPM_VALUE="$COMMIT_HASH" ;; --revision) if [[ $# -lt 1 ]]; then echo "Error: Missing revision for --revision"; exit 1; fi - REVISION="$1" - REPLACEMENT_REGEX="\1kind = revision;\n\t\t\t\trevision = \"$REVISION\";\2" + SPM_KIND="revision" + SPM_VALUE="$1" ;; --branch) if [[ $# -lt 1 ]]; then echo "Error: Missing branch name for --branch"; exit 1; fi - BRANCH_NAME="$1" - REPLACEMENT_REGEX="\1kind = branch;\n\t\t\t\tbranch = \"$BRANCH_NAME\";\2" + SPM_KIND="branch" + SPM_VALUE="$1" ;; *) echo "Invalid mode: $MODE" @@ -88,6 +90,14 @@ case "$MODE" in ;; esac +# The kind and value reach Perl through the environment rather than being +# interpolated into the expression's source text, so characters that are +# special to Perl (such as the `#` delimiter, which can appear in branch +# names) cannot break the substitution. Single quotes keep `$ENV{...}` and +# `${1}` for Perl rather than the shell. +export SPM_KIND SPM_VALUE +REPLACEMENT='${1}kind = $ENV{SPM_KIND};\n\t\t\t\t$ENV{SPM_KIND} = "$ENV{SPM_VALUE}";${2}' + # Make a temporary backup of the original file. # This will be used to check if any changes were made. TEMP_FILE=$(mktemp) @@ -99,7 +109,7 @@ cp "$PBXPROJ_PATH" "$TEMP_FILE" # -p Causes Perl to loop through the input line by line. # -i Edits the file in place. # -e Provides the expression to execute. -perl -0777 -i -pe "s#$REQUIREMENT_REGEX#$REPLACEMENT_REGEX#g" "$PBXPROJ_PATH" || { +perl -0777 -i -pe "s#$REQUIREMENT_REGEX#$REPLACEMENT#g" "$PBXPROJ_PATH" || { echo "Failed to update the Xcode project's SPM dependency." exit 1 } From 2710b35e0bbd850ab21cdffd3d0c21e9947ac16f Mon Sep 17 00:00:00 2001 From: Jesus Rojas Date: Mon, 22 Jun 2026 16:27:28 -0600 Subject: [PATCH 3/9] [Perf] Stabilize SPM unswizzle tests and pin quickstart to PR head SHA --- .../FPRNSURLSessionInstrumentTest.m | 30 ++++++++++++++++++- scripts/quickstart_build_spm.sh | 12 +++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m b/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m index acb64f1fde5..a46bbeca2ba 100644 --- a/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m +++ b/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m @@ -165,6 +165,20 @@ - (void)waitAndRunBlockAfterResponse:(void (^)(id self, } } +/** Waits for a condition to become true while spinning the current runloop. + * + * @param condition A block returning YES once the desired state is reached. + * @param timeoutSeconds Maximum time to wait in seconds. + * @return YES if the condition was met before timing out, NO otherwise. + */ +- (BOOL)waitForCondition:(BOOL (^)(void))condition timeout:(NSTimeInterval)timeoutSeconds { + NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSeconds]; + while (!condition() && [timeoutDate timeIntervalSinceNow] > 0) { + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.05]]; + } + return condition(); +} + #pragma mark - Testing infrastructure and subclass instrumenting /** Tests initing of FPRNSURLSessionInstrument also inits NSURLSessionDelegate instrument. */ @@ -525,6 +539,9 @@ - (void)testDelegateURLSessionDownloadTaskDidFinishDownloadingToURL { XCTAssertNotNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); [self waitAndRunBlockAfterResponse:^(id self, GCDWebServerRequest *_Nonnull request, GCDWebServerResponse *_Nonnull response) { + XCTAssertTrue([self waitForCondition:^BOOL { + return delegate.URLSessionDownloadTaskDidFinishDownloadingToURLCalled; + } timeout:3.0]); XCTAssertTrue(delegate.URLSessionDownloadTaskDidFinishDownloadingToURLCalled); XCTAssertNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); }]; @@ -575,7 +592,9 @@ - (void)testDelegateURLSessionDownloadTaskDidResumeAtOffsetExpectedTotalBytes { NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testBigDownload"]; NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:URL]; [downloadTask resume]; - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]]; + XCTAssertTrue([self waitForCondition:^BOOL { + return delegate.URLSessionDownloadTaskDidResumeAtOffsetExpectedTotalBytesCalled; + } timeout:5.0]); XCTAssertNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); XCTAssertTrue(delegate.URLSessionDownloadTaskDidResumeAtOffsetExpectedTotalBytesCalled); [instrument deregisterInstrumentors]; @@ -598,6 +617,9 @@ - (void)testDelegateURLSessionDownloadTaskDidWriteDataTotalBytesWrittenTotalByte XCTAssertNotNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); [self waitAndRunBlockAfterResponse:^(id self, GCDWebServerRequest *_Nonnull request, GCDWebServerResponse *_Nonnull response) { + XCTAssertTrue([self waitForCondition:^BOOL { + return delegate.URLSessionDownloadTaskDidWriteDataTotalBytesWrittenTotalBytesCalled; + } timeout:3.0]); XCTAssertTrue(delegate.URLSessionDownloadTaskDidWriteDataTotalBytesWrittenTotalBytesCalled); [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; XCTAssertNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); @@ -802,6 +824,9 @@ - (void)testProxyDelegateURLSessionDownloadTaskDidFinishDownloadingToURL { XCTAssertNotNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); [self waitAndRunBlockAfterResponse:^(id self, GCDWebServerRequest *_Nonnull request, GCDWebServerResponse *_Nonnull response) { + XCTAssertTrue([self waitForCondition:^BOOL { + return delegate.URLSessionDownloadTaskDidFinishDownloadingToURLCalled; + } timeout:3.0]); XCTAssertTrue(delegate.URLSessionDownloadTaskDidFinishDownloadingToURLCalled); XCTAssertNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); }]; @@ -857,6 +882,9 @@ - (void)testProxyDelegateURLSessionDownloadDidReceiveResponseCompletionHandler { XCTAssertNotNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); [self waitAndRunBlockAfterResponse:^(id self, GCDWebServerRequest *_Nonnull request, GCDWebServerResponse *_Nonnull response) { + XCTAssertTrue([self waitForCondition:^BOOL { + return delegate.URLSessionDownloadTaskDidWriteDataTotalBytesWrittenTotalBytesCalled; + } timeout:3.0]); XCTAssertTrue(delegate.URLSessionDownloadTaskDidWriteDataTotalBytesWrittenTotalBytesCalled); [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; XCTAssertNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); diff --git a/scripts/quickstart_build_spm.sh b/scripts/quickstart_build_spm.sh index 2337c8dae43..eff31da5b96 100755 --- a/scripts/quickstart_build_spm.sh +++ b/scripts/quickstart_build_spm.sh @@ -34,7 +34,17 @@ git clone https://github.com/firebase/quickstart-ios.git cd quickstart-ios -source "$scripts_dir/update_firebase_spm_dependency.sh" "$SAMPLE_XCODEPROJ" --branch "$BRANCH_NAME" +firebase_sdk_branch="${BRANCH_NAME:-main}" +if [[ "${GITHUB_EVENT_NAME:-}" == "pull_request" ]] && \ + [[ -n "${GITHUB_EVENT_PATH:-}" ]] && \ + command -v jq >/dev/null 2>&1; then + firebase_sdk_revision=$(jq -er .pull_request.head.sha "${GITHUB_EVENT_PATH}") + source "$scripts_dir/update_firebase_spm_dependency.sh" \ + "$SAMPLE_XCODEPROJ" --revision "$firebase_sdk_revision" +else + source "$scripts_dir/update_firebase_spm_dependency.sh" \ + "$SAMPLE_XCODEPROJ" --branch "$firebase_sdk_branch" +fi # Placeholder GoogleService-Info.plist good enough for build only testing. cp ./mock-GoogleService-Info.plist ./firebaseai/GoogleService-Info.plist From d6f1c647b5a66e2c7e9db177c30a0c3cc89fb3c6 Mon Sep 17 00:00:00 2001 From: Jesus Rojas Date: Mon, 22 Jun 2026 17:16:09 -0600 Subject: [PATCH 4/9] [Perf] Apply clang-format to NSURLSession instrument tests --- .../FPRNSURLSessionInstrumentTest.m | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m b/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m index a46bbeca2ba..1457b0b90e5 100644 --- a/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m +++ b/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m @@ -539,9 +539,11 @@ - (void)testDelegateURLSessionDownloadTaskDidFinishDownloadingToURL { XCTAssertNotNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); [self waitAndRunBlockAfterResponse:^(id self, GCDWebServerRequest *_Nonnull request, GCDWebServerResponse *_Nonnull response) { - XCTAssertTrue([self waitForCondition:^BOOL { - return delegate.URLSessionDownloadTaskDidFinishDownloadingToURLCalled; - } timeout:3.0]); + XCTAssertTrue([self + waitForCondition:^BOOL { + return delegate.URLSessionDownloadTaskDidFinishDownloadingToURLCalled; + } + timeout:3.0]); XCTAssertTrue(delegate.URLSessionDownloadTaskDidFinishDownloadingToURLCalled); XCTAssertNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); }]; @@ -592,9 +594,11 @@ - (void)testDelegateURLSessionDownloadTaskDidResumeAtOffsetExpectedTotalBytes { NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testBigDownload"]; NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:URL]; [downloadTask resume]; - XCTAssertTrue([self waitForCondition:^BOOL { - return delegate.URLSessionDownloadTaskDidResumeAtOffsetExpectedTotalBytesCalled; - } timeout:5.0]); + XCTAssertTrue([self + waitForCondition:^BOOL { + return delegate.URLSessionDownloadTaskDidResumeAtOffsetExpectedTotalBytesCalled; + } + timeout:5.0]); XCTAssertNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); XCTAssertTrue(delegate.URLSessionDownloadTaskDidResumeAtOffsetExpectedTotalBytesCalled); [instrument deregisterInstrumentors]; @@ -617,9 +621,11 @@ - (void)testDelegateURLSessionDownloadTaskDidWriteDataTotalBytesWrittenTotalByte XCTAssertNotNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); [self waitAndRunBlockAfterResponse:^(id self, GCDWebServerRequest *_Nonnull request, GCDWebServerResponse *_Nonnull response) { - XCTAssertTrue([self waitForCondition:^BOOL { - return delegate.URLSessionDownloadTaskDidWriteDataTotalBytesWrittenTotalBytesCalled; - } timeout:3.0]); + XCTAssertTrue([self + waitForCondition:^BOOL { + return delegate.URLSessionDownloadTaskDidWriteDataTotalBytesWrittenTotalBytesCalled; + } + timeout:3.0]); XCTAssertTrue(delegate.URLSessionDownloadTaskDidWriteDataTotalBytesWrittenTotalBytesCalled); [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; XCTAssertNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); @@ -824,9 +830,11 @@ - (void)testProxyDelegateURLSessionDownloadTaskDidFinishDownloadingToURL { XCTAssertNotNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); [self waitAndRunBlockAfterResponse:^(id self, GCDWebServerRequest *_Nonnull request, GCDWebServerResponse *_Nonnull response) { - XCTAssertTrue([self waitForCondition:^BOOL { - return delegate.URLSessionDownloadTaskDidFinishDownloadingToURLCalled; - } timeout:3.0]); + XCTAssertTrue([self + waitForCondition:^BOOL { + return delegate.URLSessionDownloadTaskDidFinishDownloadingToURLCalled; + } + timeout:3.0]); XCTAssertTrue(delegate.URLSessionDownloadTaskDidFinishDownloadingToURLCalled); XCTAssertNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); }]; @@ -882,9 +890,11 @@ - (void)testProxyDelegateURLSessionDownloadDidReceiveResponseCompletionHandler { XCTAssertNotNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); [self waitAndRunBlockAfterResponse:^(id self, GCDWebServerRequest *_Nonnull request, GCDWebServerResponse *_Nonnull response) { - XCTAssertTrue([self waitForCondition:^BOOL { - return delegate.URLSessionDownloadTaskDidWriteDataTotalBytesWrittenTotalBytesCalled; - } timeout:3.0]); + XCTAssertTrue([self + waitForCondition:^BOOL { + return delegate.URLSessionDownloadTaskDidWriteDataTotalBytesWrittenTotalBytesCalled; + } + timeout:3.0]); XCTAssertTrue(delegate.URLSessionDownloadTaskDidWriteDataTotalBytesWrittenTotalBytesCalled); [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; XCTAssertNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); From 0c598967434be5faef9bc7d3a1fedbdd922562ed Mon Sep 17 00:00:00 2001 From: Jesus Rojas Date: Tue, 23 Jun 2026 13:15:34 -0600 Subject: [PATCH 5/9] [Perf] Stabilize NSURLSession instrument tests --- .../FPRNSURLSessionInstrumentTest.m | 180 +++++++++++------- 1 file changed, 109 insertions(+), 71 deletions(-) diff --git a/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m b/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m index 1457b0b90e5..f312c6db129 100644 --- a/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m +++ b/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m @@ -120,6 +120,13 @@ @interface FPRNSURLSessionInstrumentTest : FPRTestCase @implementation FPRNSURLSessionInstrumentTest +// Use the main queue so delegate callbacks are delivered while the test spins the +// current run loop. The default nil queue is a private serial queue and is flaky +// when tests wait on the main run loop under full-suite CI load. +static dispatch_queue_t FPRTestNSURLSessionDelegateQueue(void) { + return dispatch_get_main_queue(); +} + - (void)setUp { [super setUp]; FIRPerformance *performance = [FIRPerformance sharedInstance]; @@ -131,11 +138,14 @@ - (void)setUp { } - (void)tearDown { - [super tearDown]; + self.testServer.responseCompletedBlock = nil; + if (self.testServer.isRunning) { + [self.testServer stop]; + } FIRPerformance *performance = [FIRPerformance sharedInstance]; [performance setDataCollectionEnabled:NO]; - [self.testServer stop]; [FPRConfigurations reset]; + [super tearDown]; } /** Waits for the server connection to finish by giving a block to run just before a response is @@ -342,15 +352,17 @@ - (void)testProxyWrappedSessionWithConfigurationDelegateDelegateQueue { }); method_setImplementation(method, swizzledImp); NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; - XCTAssertEqual([[NSURLSession sessionWithConfiguration:config delegate:nil - delegateQueue:nil] class], + XCTAssertEqual([[NSURLSession sessionWithConfiguration:config + delegate:nil + delegateQueue:FPRTestNSURLSessionDelegateQueue()] class], [FPRNSURLSessionProxy class]); FPRNSURLSessionInstrument *instrument = [[FPRNSURLSessionInstrument alloc] init]; [instrument registerInstrumentors]; NSURLSession *session; - XCTAssertNoThrow(session = [NSURLSession sessionWithConfiguration:config - delegate:nil - delegateQueue:nil]); + XCTAssertNoThrow(session = + [NSURLSession sessionWithConfiguration:config + delegate:nil + delegateQueue:FPRTestNSURLSessionDelegateQueue()]); NSURL *url = self.testServer.serverURL; XCTestExpectation *expectation = [self expectationWithDescription:@"completionHandler"]; NSURLSessionDownloadTask *task = @@ -374,9 +386,10 @@ - (void)testSessionWithConfigurationDelegateDelegateQueueWithNilDelegate { [instrument registerInstrumentors]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration - delegate:nil - delegateQueue:nil]; + NSURLSession *session = + [NSURLSession sessionWithConfiguration:configuration + delegate:nil + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; NSURLRequest *request = [NSURLRequest requestWithURL:self.testServer.serverURL]; NSURLSessionTask *task; @autoreleasepool { @@ -401,8 +414,12 @@ - (void)testDelegateClassOnlyRegisteredOnce { [[FPRNSURLSessionCompleteTestDelegate alloc] init]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; - [NSURLSession sessionWithConfiguration:configuration delegate:delegate delegateQueue:nil]; - [NSURLSession sessionWithConfiguration:configuration delegate:delegate delegateQueue:nil]; + [NSURLSession sessionWithConfiguration:configuration + delegate:delegate + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; + [NSURLSession sessionWithConfiguration:configuration + delegate:delegate + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; XCTAssertEqual(instrument.delegateInstrument.classInstrumentors.count, 1); XCTAssertEqual(instrument.delegateInstrument.instrumentedClasses.count, 1); [instrument deregisterInstrumentors]; @@ -419,9 +436,10 @@ - (void)testDelegateURLSessionTaskDidCompleteWithError { NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://nonurl"]]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration - delegate:delegate - delegateQueue:nil]; + NSURLSession *session = + [NSURLSession sessionWithConfiguration:configuration + delegate:delegate + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; NSURLSessionTask *task; @autoreleasepool { task = [session dataTaskWithRequest:request]; @@ -443,9 +461,10 @@ - (void)testDelegateURLSessionTaskDidSendBodyDataTotalBytesSentTotalBytesExpecte [[FPRNSURLSessionCompleteTestDelegate alloc] init]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration - delegate:delegate - delegateQueue:nil]; + NSURLSession *session = + [NSURLSession sessionWithConfiguration:configuration + delegate:delegate + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testUpload"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; request.HTTPMethod = @"POST"; @@ -481,9 +500,10 @@ - (void)testDelegateURLSessionTaskWillPerformHTTPRedirectionNewRequestCompletion [NSURLSessionConfiguration defaultSessionConfiguration]; NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testRedirect"]; NSURLRequest *request = [NSURLRequest requestWithURL:URL]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration - delegate:delegate - delegateQueue:nil]; + NSURLSession *session = + [NSURLSession sessionWithConfiguration:configuration + delegate:delegate + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; NSURLSessionTask *task = [session dataTaskWithRequest:request]; [task resume]; XCTAssertNotNil([FPRNetworkTrace networkTraceFromObject:task]); @@ -506,9 +526,10 @@ - (void)testDelegateURLSessionDataTaskDidReceiveData { [[FPRNSURLSessionCompleteTestDelegate alloc] init]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration - delegate:delegate - delegateQueue:nil]; + NSURLSession *session = + [NSURLSession sessionWithConfiguration:configuration + delegate:delegate + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testBigDownload"]; dataTask = [session dataTaskWithURL:URL]; [dataTask resume]; @@ -530,9 +551,10 @@ - (void)testDelegateURLSessionDownloadTaskDidFinishDownloadingToURL { [[FPRNSURLSessionCompleteTestDelegate alloc] init]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration - delegate:delegate - delegateQueue:nil]; + NSURLSession *session = + [NSURLSession sessionWithConfiguration:configuration + delegate:delegate + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testDownload"]; NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:URL]; [downloadTask resume]; @@ -543,7 +565,7 @@ - (void)testDelegateURLSessionDownloadTaskDidFinishDownloadingToURL { waitForCondition:^BOOL { return delegate.URLSessionDownloadTaskDidFinishDownloadingToURLCalled; } - timeout:3.0]); + timeout:10.0]); XCTAssertTrue(delegate.URLSessionDownloadTaskDidFinishDownloadingToURLCalled); XCTAssertNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); }]; @@ -561,9 +583,10 @@ - (void)testDelegateURLSessionDownloadDidReceiveResponseCompletionHandler { [[FPRNSURLSessionCompleteTestDelegate alloc] init]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration - delegate:delegate - delegateQueue:nil]; + NSURLSession *session = + [NSURLSession sessionWithConfiguration:configuration + delegate:delegate + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testBigDownload"]; dataTask = [session dataTaskWithURL:URL]; [dataTask resume]; @@ -588,9 +611,10 @@ - (void)testDelegateURLSessionDownloadTaskDidResumeAtOffsetExpectedTotalBytes { [[FPRNSURLSessionTestDownloadDelegate alloc] init]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration - delegate:delegate - delegateQueue:nil]; + NSURLSession *session = + [NSURLSession sessionWithConfiguration:configuration + delegate:delegate + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testBigDownload"]; NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:URL]; [downloadTask resume]; @@ -598,7 +622,7 @@ - (void)testDelegateURLSessionDownloadTaskDidResumeAtOffsetExpectedTotalBytes { waitForCondition:^BOOL { return delegate.URLSessionDownloadTaskDidResumeAtOffsetExpectedTotalBytesCalled; } - timeout:5.0]); + timeout:10.0]); XCTAssertNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); XCTAssertTrue(delegate.URLSessionDownloadTaskDidResumeAtOffsetExpectedTotalBytesCalled); [instrument deregisterInstrumentors]; @@ -612,9 +636,10 @@ - (void)testDelegateURLSessionDownloadTaskDidWriteDataTotalBytesWrittenTotalByte [[FPRNSURLSessionCompleteTestDelegate alloc] init]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration - delegate:delegate - delegateQueue:nil]; + NSURLSession *session = + [NSURLSession sessionWithConfiguration:configuration + delegate:delegate + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testBigDownload"]; NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:URL]; [downloadTask resume]; @@ -625,7 +650,7 @@ - (void)testDelegateURLSessionDownloadTaskDidWriteDataTotalBytesWrittenTotalByte waitForCondition:^BOOL { return delegate.URLSessionDownloadTaskDidWriteDataTotalBytesWrittenTotalBytesCalled; } - timeout:3.0]); + timeout:10.0]); XCTAssertTrue(delegate.URLSessionDownloadTaskDidWriteDataTotalBytesWrittenTotalBytesCalled); [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; XCTAssertNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); @@ -641,9 +666,10 @@ - (void)testDelegateUnimplementedURLSessionTaskDidCompleteWithError { NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; XCTAssertFalse([delegate respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]); - NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration - delegate:delegate - delegateQueue:nil]; + NSURLSession *session = + [NSURLSession sessionWithConfiguration:configuration + delegate:delegate + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; XCTAssertTrue([delegate respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]); NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:self.testServer.serverURL]; [downloadTask resume]; @@ -667,8 +693,12 @@ - (void)testProxyDelegateSwizzlesDelegateOnce { [[FPRNSURLSessionDelegateProxy alloc] initWithDelegate:delegate]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; - [NSURLSession sessionWithConfiguration:configuration delegate:proxyDelegate delegateQueue:nil]; - [NSURLSession sessionWithConfiguration:configuration delegate:proxyDelegate delegateQueue:nil]; + [NSURLSession sessionWithConfiguration:configuration + delegate:proxyDelegate + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; + [NSURLSession sessionWithConfiguration:configuration + delegate:proxyDelegate + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; XCTAssertEqual(instrument.delegateInstrument.classInstrumentors.count, 1); XCTAssertEqual(instrument.delegateInstrument.instrumentedClasses.count, 1); XCTAssertTrue( @@ -688,7 +718,7 @@ - (void)testProxyDelegateUsesRuntimeClassWhenProxyReportsWrappedDelegateClass { XCTAssertFalse([delegate respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]); XCTAssertNoThrow([NSURLSession sessionWithConfiguration:configuration delegate:proxyDelegate - delegateQueue:nil]); + delegateQueue:FPRTestNSURLSessionDelegateQueue()]); XCTAssertTrue([delegate respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]); [instrument deregisterInstrumentors]; } @@ -705,9 +735,10 @@ - (void)testProxyDelegateURLSessionTaskDidCompleteWithError { NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://nonurl"]]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration - delegate:proxyDelegate - delegateQueue:nil]; + NSURLSession *session = + [NSURLSession sessionWithConfiguration:configuration + delegate:proxyDelegate + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; NSURLSessionTask *task; @autoreleasepool { task = [session dataTaskWithRequest:request]; @@ -731,9 +762,10 @@ - (void)testProxyDelegateURLSessionTaskDidSendBodyDataTotalBytesSentTotalBytesEx [[FPRNSURLSessionDelegateProxy alloc] initWithDelegate:delegate]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration - delegate:proxyDelegate - delegateQueue:nil]; + NSURLSession *session = + [NSURLSession sessionWithConfiguration:configuration + delegate:proxyDelegate + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testUpload"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; request.HTTPMethod = @"POST"; @@ -768,9 +800,10 @@ - (void)testProxyDelegateURLSessionTaskWillPerformHTTPRedirectionNewRequestCompl [NSURLSessionConfiguration defaultSessionConfiguration]; NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testRedirect"]; NSURLRequest *request = [NSURLRequest requestWithURL:URL]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration - delegate:proxyDelegate - delegateQueue:nil]; + NSURLSession *session = + [NSURLSession sessionWithConfiguration:configuration + delegate:proxyDelegate + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; NSURLSessionTask *task = [session dataTaskWithRequest:request]; [task resume]; XCTAssertNotNil([FPRNetworkTrace networkTraceFromObject:task]); @@ -795,9 +828,10 @@ - (void)testProxyDelegateURLSessionDataTaskDidReceiveData { [[FPRNSURLSessionDelegateProxy alloc] initWithDelegate:delegate]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration - delegate:proxyDelegate - delegateQueue:nil]; + NSURLSession *session = + [NSURLSession sessionWithConfiguration:configuration + delegate:proxyDelegate + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testBigDownload"]; dataTask = [session dataTaskWithURL:URL]; [dataTask resume]; @@ -821,9 +855,10 @@ - (void)testProxyDelegateURLSessionDownloadTaskDidFinishDownloadingToURL { [[FPRNSURLSessionDelegateProxy alloc] initWithDelegate:delegate]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration - delegate:proxyDelegate - delegateQueue:nil]; + NSURLSession *session = + [NSURLSession sessionWithConfiguration:configuration + delegate:proxyDelegate + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testDownload"]; NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:URL]; [downloadTask resume]; @@ -834,7 +869,7 @@ - (void)testProxyDelegateURLSessionDownloadTaskDidFinishDownloadingToURL { waitForCondition:^BOOL { return delegate.URLSessionDownloadTaskDidFinishDownloadingToURLCalled; } - timeout:3.0]); + timeout:10.0]); XCTAssertTrue(delegate.URLSessionDownloadTaskDidFinishDownloadingToURLCalled); XCTAssertNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); }]; @@ -854,9 +889,10 @@ - (void)testProxyDelegateURLSessionDownloadDidReceiveResponseCompletionHandler { [[FPRNSURLSessionDelegateProxy alloc] initWithDelegate:delegate]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration - delegate:proxyDelegate - delegateQueue:nil]; + NSURLSession *session = + [NSURLSession sessionWithConfiguration:configuration + delegate:proxyDelegate + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testBigDownload"]; dataTask = [session dataTaskWithURL:URL]; [dataTask resume]; @@ -881,9 +917,10 @@ - (void)testProxyDelegateURLSessionDownloadDidReceiveResponseCompletionHandler { [[FPRNSURLSessionDelegateProxy alloc] initWithDelegate:delegate]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration - delegate:proxyDelegate - delegateQueue:nil]; + NSURLSession *session = + [NSURLSession sessionWithConfiguration:configuration + delegate:proxyDelegate + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; NSURL *URL = [self.testServer.serverURL URLByAppendingPathComponent:@"testBigDownload"]; NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:URL]; [downloadTask resume]; @@ -894,7 +931,7 @@ - (void)testProxyDelegateURLSessionDownloadDidReceiveResponseCompletionHandler { waitForCondition:^BOOL { return delegate.URLSessionDownloadTaskDidWriteDataTotalBytesWrittenTotalBytesCalled; } - timeout:3.0]); + timeout:10.0]); XCTAssertTrue(delegate.URLSessionDownloadTaskDidWriteDataTotalBytesWrittenTotalBytesCalled); [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; XCTAssertNil([FPRNetworkTrace networkTraceFromObject:downloadTask]); @@ -912,9 +949,10 @@ - (void)testProxyDelegateUnimplementedURLSessionTaskDidCompleteWithError { NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; XCTAssertFalse([delegate respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]); - NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration - delegate:proxyDelegate - delegateQueue:nil]; + NSURLSession *session = + [NSURLSession sessionWithConfiguration:configuration + delegate:proxyDelegate + delegateQueue:FPRTestNSURLSessionDelegateQueue()]; XCTAssertTrue([delegate respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]); NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:self.testServer.serverURL]; [downloadTask resume]; From be7b3be80a0bbea52a2256f02f3bb0933329ad6a Mon Sep 17 00:00:00 2001 From: Jesus Rojas Date: Tue, 23 Jun 2026 15:29:02 -0600 Subject: [PATCH 6/9] [Perf] Use NSOperationQueue mainQueue in NSURLSession instrument tests --- .../Unit/Instruments/FPRNSURLSessionInstrumentTest.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m b/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m index f312c6db129..14d5bd41c7c 100644 --- a/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m +++ b/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m @@ -120,11 +120,11 @@ @interface FPRNSURLSessionInstrumentTest : FPRTestCase @implementation FPRNSURLSessionInstrumentTest -// Use the main queue so delegate callbacks are delivered while the test spins the -// current run loop. The default nil queue is a private serial queue and is flaky -// when tests wait on the main run loop under full-suite CI load. -static dispatch_queue_t FPRTestNSURLSessionDelegateQueue(void) { - return dispatch_get_main_queue(); +// Use the main operation queue so delegate callbacks are delivered while the test +// spins the current run loop. The default nil queue is a private serial queue and +// is flaky when tests wait on the main run loop under full-suite CI load. +static NSOperationQueue *FPRTestNSURLSessionDelegateQueue(void) { + return [NSOperationQueue mainQueue]; } - (void)setUp { From 5e3515ed935b2cba959ba2018f3e51c1b6f8e795 Mon Sep 17 00:00:00 2001 From: Jesus Rojas Date: Thu, 25 Jun 2026 14:06:16 -0600 Subject: [PATCH 7/9] [Perf] Recreate temp dir before NSURLSession download tests --- .../Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m b/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m index 14d5bd41c7c..96508910e94 100644 --- a/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m +++ b/FirebasePerformance/Tests/Unit/Instruments/FPRNSURLSessionInstrumentTest.m @@ -131,6 +131,12 @@ - (void)setUp { [super setUp]; FIRPerformance *performance = [FIRPerformance sharedInstance]; [performance setDataCollectionEnabled:YES]; + NSError *error; + [[NSFileManager defaultManager] createDirectoryAtPath:NSTemporaryDirectory() + withIntermediateDirectories:YES + attributes:nil + error:&error]; + XCTAssertNil(error); XCTAssertFalse(self.testServer.isRunning); self.testServer = [[FPRHermeticTestServer alloc] init]; [self.testServer registerTestPaths]; From 8f4bef80a2def6c67d863cc3094c786de4ff0ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Rojas?= Date: Thu, 25 Jun 2026 16:35:12 -0600 Subject: [PATCH 8/9] Update FPRUIViewControllerInstrumentTest.m Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../Tests/Unit/Instruments/FPRUIViewControllerInstrumentTest.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebasePerformance/Tests/Unit/Instruments/FPRUIViewControllerInstrumentTest.m b/FirebasePerformance/Tests/Unit/Instruments/FPRUIViewControllerInstrumentTest.m index 44bad282815..d8287b5966e 100644 --- a/FirebasePerformance/Tests/Unit/Instruments/FPRUIViewControllerInstrumentTest.m +++ b/FirebasePerformance/Tests/Unit/Instruments/FPRUIViewControllerInstrumentTest.m @@ -63,7 +63,7 @@ - (void)testViewDidAppearInvokesViewControllerDidAppearOnScreenTraceTracker { // runner, so both are stubbed. id applicationMock = OCMClassMock([UIApplication class]); OCMStub(ClassMethod([applicationMock sharedApplication])).andReturn(applicationMock); - UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 320, 480)]; id windowMock = OCMPartialMock(window); OCMStub([(UIWindow *)windowMock isKeyWindow]).andReturn(YES); [window addSubview:[testViewController view]]; From f687eee94049e423561f725557688c99961847e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Rojas?= Date: Thu, 25 Jun 2026 16:35:47 -0600 Subject: [PATCH 9/9] Update quickstart_build_spm.sh Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- scripts/quickstart_build_spm.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/quickstart_build_spm.sh b/scripts/quickstart_build_spm.sh index eff31da5b96..da8a70f3f2b 100755 --- a/scripts/quickstart_build_spm.sh +++ b/scripts/quickstart_build_spm.sh @@ -38,9 +38,12 @@ firebase_sdk_branch="${BRANCH_NAME:-main}" if [[ "${GITHUB_EVENT_NAME:-}" == "pull_request" ]] && \ [[ -n "${GITHUB_EVENT_PATH:-}" ]] && \ command -v jq >/dev/null 2>&1; then - firebase_sdk_revision=$(jq -er .pull_request.head.sha "${GITHUB_EVENT_PATH}") - source "$scripts_dir/update_firebase_spm_dependency.sh" \ - "$SAMPLE_XCODEPROJ" --revision "$firebase_sdk_revision" + firebase_sdk_revision=$(jq -er .pull_request.head.sha "${GITHUB_EVENT_PATH}" 2>/dev/null || echo "") + if [[ -n "$firebase_sdk_revision" ]]; then + source "$scripts_dir/update_firebase_spm_dependency.sh" "$SAMPLE_XCODEPROJ" --revision "$firebase_sdk_revision" + else + source "$scripts_dir/update_firebase_spm_dependency.sh" "$SAMPLE_XCODEPROJ" --branch "$firebase_sdk_branch" + fi else source "$scripts_dir/update_firebase_spm_dependency.sh" \ "$SAMPLE_XCODEPROJ" --branch "$firebase_sdk_branch"