Skip to content

Commit 0589692

Browse files
antonisclaude
andauthored
fix(profiling): iOS UI profiling on v8 (#6012)
* fix(profiling): iOS UI profiling on v8 `RNSentryStart.createOptionsWithDictionary` (the live iOS init path since v8.0.0) did not read `_experiments.profilingOptions`, silently dropping `profileSessionSampleRate`, `lifecycle`, and `startOnAppStart`. The handling existed in `SentrySDKWrapper`, but that surface hasn't been called from `initNativeSdk` since #5582#5611 added the block to the wrong file. Port the handling to `RNSentryStart` and add XCTest coverage for the `startWithOptions` entry point that `initNativeSdk` uses, so this regression can't slip through again. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * review: house-style CHANGELOG and verify profiling fields land on probe - Rewrite CHANGELOG entry to match the descriptive + PR-link format. - Strengthen the two positive profiling tests to invoke the installed callback on a fresh SentryProfileOptions probe and assert that sessionSampleRate, lifecycle, and profileAppStarts land correctly, so a future regression that installs an incomplete callback is caught. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * changelog: simplify profiling fix wording --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4b87b12 commit 0589692

3 files changed

Lines changed: 109 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
### Fixes
2020

21+
- Fix iOS UI profiling options being silently ignored ([#6012](https://github.com/getsentry/sentry-react-native/pull/6012))
2122
- Check `captureReplay` return value in iOS bridge to avoid linking error events to uncaptured replays ([#6008](https://github.com/getsentry/sentry-react-native/pull/6008))
2223
- Report the expected properties file path and any missing keys when using `flavorAware` on Android, instead of failing with an opaque `Illegal null value provided in this collection` error ([#6031](https://github.com/getsentry/sentry-react-native/pull/6031))
2324

packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#import <OCMock/OCMock.h>
77
#import <RNSentry/RNSentry.h>
88
#import <Sentry/PrivateSentrySDKOnly.h>
9+
#import <Sentry/SentryProfilingConditionals.h>
910
#import <UIKit/UIKit.h>
1011
#import <XCTest/XCTest.h>
1112
@import Sentry;
@@ -1373,6 +1374,103 @@ - (void)testStartBeforeBreadcrumbsCallbackDoesNotFiltersOutNonDevServerOrDsnRequ
13731374
XCTAssertEqual(breadcrumb, result);
13741375
}
13751376

1377+
#if SENTRY_TARGET_PROFILING_SUPPORTED
1378+
// Regression test for the v8.0.0 bug where the init path (RNSentryStart) did not
1379+
// handle `_experiments.profilingOptions`, silently dropping iOS UI profiling config.
1380+
// This pins the full entry point used by `initNativeSdk` in RNSentry.mm.
1381+
- (void)testStartWithDictionaryInstallsConfigureProfilingFromExperimentsProfilingOptions
1382+
{
1383+
NSError *error = nil;
1384+
1385+
NSDictionary *_Nonnull mockedReactNativeDictionary = @{
1386+
@"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456",
1387+
@"_experiments" : @ {
1388+
@"profilingOptions" : @ {
1389+
@"profileSessionSampleRate" : @1.0,
1390+
@"lifecycle" : @"trace",
1391+
@"startOnAppStart" : @YES,
1392+
},
1393+
},
1394+
};
1395+
[RNSentryStart startWithOptions:mockedReactNativeDictionary error:&error];
1396+
SentryOptions *actualOptions = PrivateSentrySDKOnly.options;
1397+
1398+
XCTAssertNotNil(actualOptions, @"Did not create sentry options");
1399+
XCTAssertNil(error, @"Should not pass no error");
1400+
XCTAssertNotNil(actualOptions.configureProfiling,
1401+
@"configureProfiling must be installed after startWithOptions when profilingOptions is "
1402+
@"present");
1403+
1404+
SentryProfileOptions *probe = [[SentryProfileOptions alloc] init];
1405+
actualOptions.configureProfiling(probe);
1406+
XCTAssertEqual(probe.sessionSampleRate, 1.0f);
1407+
XCTAssertEqual(probe.lifecycle, SentryProfileLifecycleTrace);
1408+
XCTAssertTrue(probe.profileAppStarts);
1409+
}
1410+
1411+
- (void)testStartCreateOptionsWithDictionaryProfilingOptionsInstallsConfigureProfiling
1412+
{
1413+
NSError *error = nil;
1414+
1415+
NSDictionary *_Nonnull mockedReactNativeDictionary = @{
1416+
@"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456",
1417+
@"_experiments" : @ {
1418+
@"profilingOptions" : @ {
1419+
@"profileSessionSampleRate" : @1.0,
1420+
@"lifecycle" : @"trace",
1421+
@"startOnAppStart" : @YES,
1422+
},
1423+
},
1424+
};
1425+
SentryOptions *actualOptions =
1426+
[RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error];
1427+
1428+
XCTAssertNotNil(actualOptions, @"Did not create sentry options");
1429+
XCTAssertNil(error, @"Should not pass no error");
1430+
XCTAssertNotNil(actualOptions.configureProfiling,
1431+
@"configureProfiling callback should be installed when profilingOptions is present");
1432+
1433+
SentryProfileOptions *probe = [[SentryProfileOptions alloc] init];
1434+
actualOptions.configureProfiling(probe);
1435+
XCTAssertEqual(probe.sessionSampleRate, 1.0f);
1436+
XCTAssertEqual(probe.lifecycle, SentryProfileLifecycleTrace);
1437+
XCTAssertTrue(probe.profileAppStarts);
1438+
}
1439+
1440+
- (void)testStartCreateOptionsWithDictionaryProfilingOptionsMissingDoesNotInstallConfigureProfiling
1441+
{
1442+
NSError *error = nil;
1443+
1444+
NSDictionary *_Nonnull mockedReactNativeDictionary = @{
1445+
@"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456",
1446+
};
1447+
SentryOptions *actualOptions =
1448+
[RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error];
1449+
1450+
XCTAssertNotNil(actualOptions, @"Did not create sentry options");
1451+
XCTAssertNil(error, @"Should not pass no error");
1452+
XCTAssertNil(actualOptions.configureProfiling,
1453+
@"configureProfiling callback should not be installed without profilingOptions");
1454+
}
1455+
1456+
- (void)testStartCreateOptionsWithDictionaryEmptyExperimentsDoesNotInstallConfigureProfiling
1457+
{
1458+
NSError *error = nil;
1459+
1460+
NSDictionary *_Nonnull mockedReactNativeDictionary = @{
1461+
@"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456",
1462+
@"_experiments" : @ { },
1463+
};
1464+
SentryOptions *actualOptions =
1465+
[RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error];
1466+
1467+
XCTAssertNotNil(actualOptions, @"Did not create sentry options");
1468+
XCTAssertNil(error, @"Should not pass no error");
1469+
XCTAssertNil(actualOptions.configureProfiling,
1470+
@"configureProfiling callback should not be installed when profilingOptions is absent");
1471+
}
1472+
#endif
1473+
13761474
- (void)testStartEventFromSentryCocoaReactNativeHasOriginAndEnvironmentTags
13771475
{
13781476
SentryEvent *testEvent = [[SentryEvent alloc] init];

packages/core/ios/RNSentryStart.m

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,16 @@ + (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull)
140140
}
141141
}
142142

143+
// Configure iOS UI Profiling from _experiments.profilingOptions
144+
NSDictionary *experiments = mutableOptions[@"_experiments"];
145+
if (experiments != nil && [experiments isKindOfClass:[NSDictionary class]]) {
146+
NSDictionary *profilingOptions = experiments[@"profilingOptions"];
147+
if (profilingOptions != nil && [profilingOptions isKindOfClass:[NSDictionary class]]) {
148+
[RNSentryExperimentalOptions configureProfilingWithOptions:profilingOptions
149+
sentryOptions:sentryOptions];
150+
}
151+
}
152+
143153
// Set strict trace continuation options
144154
if ([mutableOptions valueForKey:@"strictTraceContinuation"] != nil) {
145155
sentryOptions.strictTraceContinuation =

0 commit comments

Comments
 (0)