Skip to content

Commit b93859c

Browse files
Add Crashlytics record error userInfo binding
1 parent 5321a57 commit b93859c

3 files changed

Lines changed: 99 additions & 1 deletion

File tree

source/Firebase/Crashlytics/ApiDefinition.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ interface Crashlytics
3939
[Export ("recordError:")]
4040
void RecordError (NSError error);
4141

42+
// -(void)recordError:(NSError * _Nonnull)error userInfo:(NSDictionary<NSString *,id> * _Nullable)userInfo __attribute__((swift_name("record(error:userInfo:)")));
43+
[Export ("recordError:userInfo:")]
44+
void RecordError (NSError error, [NullAllowed] NSDictionary<NSString, NSObject> userInfo);
45+
4246
// -(void)recordExceptionModel:(FIRExceptionModel * _Nonnull)exceptionModel __attribute__((swift_name("record(exceptionModel:)")));
4347
[Export ("recordExceptionModel:")]
4448
void RecordExceptionModel (ExceptionModel exceptionModel);

tests/E2E/Firebase.Foundation/FirebaseFoundationE2E/FirebaseRuntimeDriftCases.cs

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,9 @@
9797
using ObjCRuntime;
9898
#endif
9999

100-
#if ENABLE_RUNTIME_DRIFT_CASE_CRASHLYTICS_STACKFRAMEWITHADDRESS
100+
#if ENABLE_RUNTIME_DRIFT_CASE_CRASHLYTICS_STACKFRAMEWITHADDRESS || ENABLE_RUNTIME_DRIFT_CASE_CRASHLYTICS_RECORD_ERROR_USER_INFO
101101
using Firebase.Crashlytics;
102+
using Foundation;
102103
using ObjCRuntime;
103104
#endif
104105

@@ -2527,6 +2528,88 @@ void OnMarshalObjectiveCException(object? sender, MarshalObjectiveCExceptionEven
25272528
}
25282529
#endif
25292530

2531+
#if ENABLE_RUNTIME_DRIFT_CASE_CRASHLYTICS_RECORD_ERROR_USER_INFO
2532+
static Task<string> VerifyCrashlyticsRecordErrorUserInfoAsync()
2533+
{
2534+
const string selector = "recordError:userInfo:";
2535+
2536+
var signature = typeof(Crashlytics).GetMethod(
2537+
nameof(Crashlytics.RecordError),
2538+
BindingFlags.Instance | BindingFlags.Public,
2539+
binder: null,
2540+
types: new[] { typeof(NSError), typeof(NSDictionary<NSString, NSObject>) },
2541+
modifiers: null);
2542+
if (signature is null)
2543+
{
2544+
throw new InvalidOperationException(
2545+
$"Expected managed API '{nameof(Crashlytics.RecordError)}({typeof(NSError).FullName}, {typeof(NSDictionary<NSString, NSObject>).FullName})' was not found.");
2546+
}
2547+
2548+
var crashlytics = Crashlytics.SharedInstance;
2549+
if (crashlytics is null)
2550+
{
2551+
throw new InvalidOperationException("Firebase.Crashlytics.Crashlytics.SharedInstance returned null after App.Configure().");
2552+
}
2553+
2554+
if (!crashlytics.RespondsToSelector(new Selector(selector)))
2555+
{
2556+
throw new InvalidOperationException($"Native FIRCrashlytics does not respond to expected selector '{selector}'.");
2557+
}
2558+
2559+
using var domain = new NSString("codex.crashlytics.e2e");
2560+
using var userInfoKey = new NSString("codex_context");
2561+
using var userInfoValue = new NSString("record-error-user-info");
2562+
using var error = new NSError(domain, -130, null);
2563+
using var userInfo = NSDictionary<NSString, NSObject>.FromObjectsAndKeys(
2564+
new NSObject[] { userInfoValue },
2565+
new[] { userInfoKey },
2566+
1);
2567+
2568+
NSException? marshaledException = null;
2569+
MarshalObjectiveCExceptionMode? marshaledExceptionMode = null;
2570+
2571+
void OnMarshalObjectiveCException(object? sender, MarshalObjectiveCExceptionEventArgs args)
2572+
{
2573+
marshaledException ??= args.Exception;
2574+
marshaledExceptionMode ??= args.ExceptionMode;
2575+
}
2576+
2577+
Runtime.MarshalObjectiveCException += OnMarshalObjectiveCException;
2578+
try
2579+
{
2580+
try
2581+
{
2582+
crashlytics.RecordError(error, userInfo);
2583+
}
2584+
catch (ObjCException ex)
2585+
{
2586+
throw new InvalidOperationException(
2587+
$"Selector '{selector}' should not throw after the missing binding is added, but observed {ex.GetType().FullName}. " +
2588+
$"NSError domain: {error.Domain}. UserInfo type: {userInfo.GetType().FullName}. " +
2589+
$"NSException.Name: {FormatDetail(marshaledException?.Name?.ToString())}. " +
2590+
$"NSException.Reason: {FormatDetail(marshaledException?.Reason)}. " +
2591+
$"Marshal mode: {FormatDetail(marshaledExceptionMode?.ToString())}.",
2592+
ex);
2593+
}
2594+
2595+
if (marshaledException is not null)
2596+
{
2597+
throw new InvalidOperationException(
2598+
$"Selector '{selector}' completed, but Runtime.MarshalObjectiveCException captured unexpected NSException.Name '{marshaledException.Name}'. " +
2599+
$"Reason: {FormatDetail(marshaledException.Reason)}. Marshal mode: {FormatDetail(marshaledExceptionMode?.ToString())}.");
2600+
}
2601+
2602+
return Task.FromResult(
2603+
$"Selector '{selector}' crossed the native boundary without ObjC exception. " +
2604+
$"NSError domain: {error.Domain}. UserInfo count: {userInfo.Count}.");
2605+
}
2606+
finally
2607+
{
2608+
Runtime.MarshalObjectiveCException -= OnMarshalObjectiveCException;
2609+
}
2610+
}
2611+
#endif
2612+
25302613
static string FormatNSError(Foundation.NSError error)
25312614
{
25322615
return $"{error.Domain} ({error.Code}): {error.LocalizedDescription}";

tests/E2E/Firebase.Foundation/runtime-drift-cases.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,17 @@
180180
"version": "12.6.0"
181181
}
182182
]
183+
},
184+
{
185+
"id": "crashlytics-record-error-user-info",
186+
"method": "VerifyCrashlyticsRecordErrorUserInfoAsync",
187+
"bindingPackage": "AdamE.Firebase.iOS.Crashlytics",
188+
"packages": [
189+
{
190+
"id": "AdamE.Firebase.iOS.Crashlytics",
191+
"version": "12.6.0"
192+
}
193+
]
183194
}
184195
]
185196
}

0 commit comments

Comments
 (0)