Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/flutter/example/integration_test/all.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import 'integration_test.dart' as a;
import 'profiling_test.dart' as b;
import 'replay_test.dart' as c;
import 'platform_integrations_test.dart' as d;
import 'native_ffi_jni_utils_test.dart' as e;

void main() {
a.main();
b.main();
c.main();
d.main();
e.main();
}
47 changes: 43 additions & 4 deletions packages/flutter/example/integration_test/integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -769,11 +769,20 @@ void main() {
});

// 1. Add a breadcrumb via Dart
final customObject = CustomObject();
final testBreadcrumb = Breadcrumb(
message: 'test-breadcrumb-message',
category: 'test-category',
level: SentryLevel.info,
);
message: 'test-breadcrumb-message',
category: 'test-category',
level: SentryLevel.info,
data: {
'string': 'data',
'int': 12,
'bool': true,
'double': 12.34,
'map': {'nested': 'data', 'custom object': customObject},
'list': [1, customObject, 3],
'custom object': customObject
});
await Sentry.addBreadcrumb(testBreadcrumb);

// 2. Verify it appears in native via loadContexts
Expand All @@ -794,6 +803,17 @@ void main() {
expect(testCrumb, isNotNull,
reason: 'Test breadcrumb should exist in native breadcrumbs');
expect(testCrumb['category'], equals('test-category'));
expect(testCrumb['level'], equals('info'));
expect(testCrumb['data'], isNotNull);
expect(testCrumb['data']['map'], isNotNull);
expect(testCrumb['data']['map']['nested'], equals('data'));
expect(testCrumb['data']['map']['custom object'],
equals(customObject.toString()));
expect(testCrumb['data']['list'], isNotNull);
expect(testCrumb['data']['list'][0], equals(1));
expect(testCrumb['data']['list'][1], equals(customObject.toString()));
expect(testCrumb['data']['list'][2], equals(3));
expect(testCrumb['data']['custom object'], equals(customObject.toString()));

// 3. Clear breadcrumbs
await Sentry.configureScope((scope) async {
Expand All @@ -813,10 +833,20 @@ void main() {
});

// 1. Set a user via Dart
final customObject = CustomObject();
final testUser = SentryUser(
id: 'test-user-id',
email: '[email protected]',
username: 'test-username',
data: {
'string': 'data',
'int': 12,
'bool': true,
'double': 12.34,
'map': {'nested': 'data', 'custom object': customObject},
'list': [1, customObject, 3],
'custom object': customObject
},
);
await Sentry.configureScope((scope) async {
await scope.setUser(testUser);
Expand All @@ -831,6 +861,15 @@ void main() {
expect(user!['id'], equals('test-user-id'));
expect(user['email'], equals('[email protected]'));
expect(user['username'], equals('test-username'));
expect(user['data']['map'], isNotNull);
expect(user['data']['map']['nested'], equals('data'));
expect(
user['data']['map']['custom object'], equals(customObject.toString()));
expect(user['data']['list'], isNotNull);
expect(user['data']['list'][0], equals(1));
expect(user['data']['list'][1], equals(customObject.toString()));
expect(user['data']['list'][2], equals(3));
expect(user['data']['custom object'], equals(customObject.toString()));

// 3. Clear user (after clearing the id should remain)
await Sentry.configureScope((scope) async {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
// ignore_for_file: depend_on_referenced_packages

@TestOn('vm')

import 'dart:io';

import 'package:flutter_test/flutter_test.dart';
import 'package:jni/jni.dart';
import 'package:objective_c/objective_c.dart';
import 'package:sentry_flutter/src/native/cocoa/sentry_native_cocoa.dart';
import 'package:sentry_flutter/src/native/java/sentry_native_java.dart';

import 'utils.dart';

final _customObject = CustomObject();

final _nestedMap = {
'innerString': 'nested',
'innerList': [1, null, 2],
'innerNull': null,
};

final _testList = [
'value',
1,
1.1,
true,
_customObject,
['nestedList', 2],
_nestedMap,
null,
];

final _testMap = {
'key': 'value',
'key2': 1,
'key3': 1.1,
'key4': true,
'key5': _customObject,
'list': _testList,
'nestedMap': _nestedMap,
'nullEntry': null,
};

const _expectedListLength = 7;
const _expectedNestedListLength = 2;
const _expectedMapLength = 7;

void main() {
group('JNI (Android)', () {
test('dartToJObject converts primitives', () {
_expectJniString(dartToJObject('value'), 'value');
_expectJniInt(dartToJObject(1), 1);
_expectJniDouble(dartToJObject(1.1), 1.1);
_expectJniBool(dartToJObject(true), true);
_expectJniString(dartToJObject(_customObject), _customObject.toString());
});

test('dartToJObject converts list (drops nulls)', () {
final jList = dartToJObject(_testList).as(JList.type(JObject.type));
addTearDown(jList.release);
_verifyJniList(jList);
});

test('dartToJObject converts map (drops null values)', () {
final jMap =
dartToJObject(_testMap).as(JMap.type(JString.type, JObject.type));
addTearDown(jMap.release);
_verifyJniMap(jMap);
});

test('dartToJList', () {
final jList = dartToJList(_testList);
addTearDown(jList.release);
_verifyJniList(jList);
});

test('dartToJMap', () {
final jMap = dartToJMap(_testMap);
addTearDown(jMap.release);
_verifyJniMap(jMap);
});
}, skip: !Platform.isAndroid);

group('FFI (iOS/macOS)', () {
test('dartToNSObject converts primitives', () {
_expectNSString(dartToNSObject('value'), 'value');
_expectNSInt(dartToNSObject(1), 1);
_expectNSDouble(dartToNSObject(1.1), 1.1);
_expectNSBool(dartToNSObject(true), true);
_expectNSString(dartToNSObject(_customObject), _customObject.toString());
});

test('dartToNSObject converts list (drops nulls)', () {
final nsArray = NSArray.castFrom(dartToNSObject(_testList));
_verifyNSArray(nsArray);
});

test('dartToNSObject converts map (drops null values)', () {
final nsDict = NSDictionary.castFrom(dartToNSObject(_testMap));
_verifyNSDictionary(nsDict);
});

test('dartToNSArray', () {
_verifyNSArray(dartToNSArray(_testList));
});

test('dartToNSDictionary', () {
_verifyNSDictionary(dartToNSDictionary(_testMap));
});
}, skip: !(Platform.isIOS || Platform.isMacOS));
}

void _expectJniString(JObject obj, String expected) {
expect(obj, isA<JString>());
expect((obj as JString).toDartString(releaseOriginal: true), expected);
}

void _expectJniInt(JObject obj, int expected) {
expect(obj, isA<JLong>());
expect((obj as JLong).longValue(releaseOriginal: true), expected);
}

void _expectJniDouble(JObject obj, double expected) {
expect(obj, isA<JDouble>());
expect((obj as JDouble).doubleValue(releaseOriginal: true), expected);
}

void _expectJniBool(JObject obj, bool expected) {
expect(obj, isA<JBoolean>());
expect((obj as JBoolean).booleanValue(releaseOriginal: true), expected);
}

JObject? _jniGet(JMap<JString, JObject> map, String key) {
final jKey = key.toJString();
final value = map[jKey];
jKey.release();
return value;
}

bool _jniIsNull(JObject? obj) => obj == null || obj.toString() == 'null';

void _verifyJniList(JList<JObject> list) {
expect(list.length, _expectedListLength);

// Verify primitives
expect(list[0].as(JString.type).toDartString(), 'value');
expect(list[1].as(JLong.type).longValue(), 1);
expect(list[2].as(JDouble.type).doubleValue(), 1.1);
expect(list[3].as(JBoolean.type).booleanValue(), isTrue);
expect(list[4].as(JString.type).toDartString(), _customObject.toString());

// Verify nested list
final nestedList = list[5].as(JList.type(JObject.type));
expect(nestedList.length, 2);
expect(nestedList[0].as(JString.type).toDartString(), 'nestedList');
expect(nestedList[1].as(JLong.type).longValue(), 2);
nestedList.release();

// Verify nested map
final nestedMap = list[6].as(JMap.type(JString.type, JObject.type));
_verifyJniNestedMap(nestedMap);
nestedMap.release();
}

void _verifyJniMap(JMap<JString, JObject> map) {
expect(map.length, _expectedMapLength);

// Verify primitives
expect(_jniGet(map, 'key')!.as(JString.type).toDartString(), 'value');
expect(_jniGet(map, 'key2')!.as(JLong.type).longValue(), 1);
expect(_jniGet(map, 'key3')!.as(JDouble.type).doubleValue(), 1.1);
expect(_jniGet(map, 'key4')!.as(JBoolean.type).booleanValue(), isTrue);
expect(_jniGet(map, 'key5')!.as(JString.type).toDartString(),
_customObject.toString());

// Verify nested list
final nestedList = _jniGet(map, 'list')!.as(JList.type(JObject.type));
_verifyJniList(nestedList);
nestedList.release();

// Verify nested map
final nestedMap =
_jniGet(map, 'nestedMap')!.as(JMap.type(JString.type, JObject.type));
_verifyJniNestedMap(nestedMap);
nestedMap.release();

// Verify null was dropped
expect(_jniIsNull(_jniGet(map, 'nullEntry')), isTrue);
}

void _verifyJniNestedMap(JMap<JString, JObject> map) {
expect(
_jniGet(map, 'innerString')!.as(JString.type).toDartString(), 'nested');

final innerList = _jniGet(map, 'innerList')!.as(JList.type(JObject.type));
expect(innerList.length, _expectedNestedListLength);
expect(innerList[0].as(JLong.type).longValue(), 1);
expect(innerList[1].as(JLong.type).longValue(), 2);
innerList.release();

// Verify null was dropped
expect(_jniIsNull(_jniGet(map, 'innerNull')), isTrue);
}

void _expectNSString(ObjCObjectBase obj, String expected) {
expect(NSString.isInstance(obj), isTrue);
expect(NSString.castFrom(obj).toDartString(), expected);
}

void _expectNSInt(ObjCObjectBase obj, int expected) {
expect(NSNumber.isInstance(obj), isTrue);
expect(NSNumber.castFrom(obj).longLongValue, expected);
}

void _expectNSDouble(ObjCObjectBase obj, double expected) {
expect(NSNumber.isInstance(obj), isTrue);
expect(NSNumber.castFrom(obj).doubleValue, expected);
}

void _expectNSBool(ObjCObjectBase obj, bool expected) {
expect(NSNumber.isInstance(obj), isTrue);
expect(NSNumber.castFrom(obj).boolValue, expected);
}

ObjCObjectBase? _nsGet(NSDictionary dict, String key) =>
dict.objectForKey(key.toNSString());

void _verifyNSArray(NSArray array) {
expect(array.count, _expectedListLength);

// Verify primitives
expect(NSString.castFrom(array.objectAtIndex(0)).toDartString(), 'value');
expect(NSNumber.castFrom(array.objectAtIndex(1)).longLongValue, 1);
expect(NSNumber.castFrom(array.objectAtIndex(2)).doubleValue, 1.1);
expect(NSNumber.castFrom(array.objectAtIndex(3)).boolValue, isTrue);
expect(NSString.castFrom(array.objectAtIndex(4)).toDartString(),
_customObject.toString());

// Verify nested list
final nestedList = NSArray.castFrom(array.objectAtIndex(5));
expect(nestedList.count, 2);
expect(NSString.castFrom(nestedList.objectAtIndex(0)).toDartString(),
'nestedList');
expect(NSNumber.castFrom(nestedList.objectAtIndex(1)).longLongValue, 2);

// Verify nested map
_verifyNSNestedDict(NSDictionary.castFrom(array.objectAtIndex(6)));
}

void _verifyNSDictionary(NSDictionary dict) {
expect(dict.count, _expectedMapLength);

// Verify primitives
expect(NSString.castFrom(_nsGet(dict, 'key')!).toDartString(), 'value');
expect(NSNumber.castFrom(_nsGet(dict, 'key2')!).longLongValue, 1);
expect(NSNumber.castFrom(_nsGet(dict, 'key3')!).doubleValue, 1.1);
expect(NSNumber.castFrom(_nsGet(dict, 'key4')!).boolValue, isTrue);
expect(NSString.castFrom(_nsGet(dict, 'key5')!).toDartString(),
_customObject.toString());

// Verify nested list
_verifyNSArray(NSArray.castFrom(_nsGet(dict, 'list')!));

// Verify nested map
_verifyNSNestedDict(NSDictionary.castFrom(_nsGet(dict, 'nestedMap')!));

// Verify null was dropped
expect(_nsGet(dict, 'nullEntry'), isNull);
}

void _verifyNSNestedDict(NSDictionary dict) {
expect(
NSString.castFrom(_nsGet(dict, 'innerString')!).toDartString(), 'nested');

final innerList = NSArray.castFrom(_nsGet(dict, 'innerList')!);
expect(innerList.count, _expectedNestedListLength);
expect(NSNumber.castFrom(innerList.objectAtIndex(0)).longLongValue, 1);
expect(NSNumber.castFrom(innerList.objectAtIndex(1)).longLongValue, 2);

// Verify null was dropped
expect(_nsGet(dict, 'innerNull'), isNull);
}
3 changes: 3 additions & 0 deletions packages/flutter/example/integration_test/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ FutureOr<void> restoreFlutterOnErrorAfter(FutureOr<void> Function() fn) async {
}

const fakeDsn = 'https://[email protected]/1234567';

// Used to test for correct serialization of custom object in attributes / data.
class CustomObject {}
Loading
Loading