Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 4.0.0-alpha.3 (Next)

- Development.
- add loadContextsIntegration tests

## 4.0.0-alpha.2

Expand Down
20 changes: 16 additions & 4 deletions flutter/lib/src/sentry_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@ mixin SentryFlutter {
OptionsConfiguration optionsConfiguration,
Function callback, {
PackageLoader packageLoader = _loadPackageInfo,
iOSPlatformChecker iOSPlatformChecker = _iOSPlatformChecker,
}) async {
await Sentry.init((options) async {
await _initDefaultValues(options, callback, packageLoader);
await _initDefaultValues(
options,
callback,
packageLoader,
iOSPlatformChecker,
);

await optionsConfiguration(options);
});
Expand All @@ -30,6 +36,7 @@ mixin SentryFlutter {
SentryOptions options,
Function callback,
PackageLoader packageLoader,
iOSPlatformChecker iOSPlatformChecker,
) async {
// it is necessary to initialize Flutter method channels so that
// our plugin can call into the native code.
Expand Down Expand Up @@ -58,7 +65,7 @@ mixin SentryFlutter {

// first step is to install the native integration and set default values,
// so we are able to capture future errors.
_addDefaultIntegrations(options, callback);
_addDefaultIntegrations(options, callback, iOSPlatformChecker);

await _setReleaseAndDist(options, packageLoader);

Expand Down Expand Up @@ -103,6 +110,7 @@ mixin SentryFlutter {
static void _addDefaultIntegrations(
SentryOptions options,
Function callback,
iOSPlatformChecker isIOS,
) {
// the ordering here matters, as we'd like to first start the native integration
// that allow us to send events to the network and then the Flutter integrations.
Expand All @@ -121,8 +129,7 @@ mixin SentryFlutter {
options.addIntegration(isolateErrorIntegration);
}

// TODO: make it testable/mockable
if (Platform.isIOS) {
if (isIOS()) {
options.addIntegration(loadContextsIntegration(options, _channel));
}
// finally the runZonedGuarded, catch any errors in Dart code running
Expand All @@ -145,7 +152,12 @@ mixin SentryFlutter {

typedef PackageLoader = Future<PackageInfo> Function();

typedef iOSPlatformChecker = bool Function();

/// Package info loader.
Future<PackageInfo> _loadPackageInfo() async {
return await PackageInfo.fromPlatform();
}

/// verify if the platform is iOS
bool _iOSPlatformChecker() => Platform.isIOS;
15 changes: 1 addition & 14 deletions flutter/test/default_integrations_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ void main() {
fixture.options.sdk.integrations.contains('nativeSdkIntegration'));
});

test('loadContextsIntegration adds integration on ios', () async {
test('loadContextsIntegration adds integration', () async {
_channel.setMockMethodCallHandler((MethodCall methodCall) async {});

final integration = loadContextsIntegration(fixture.options, _channel);
Expand All @@ -186,19 +186,6 @@ void main() {
expect(true,
fixture.options.sdk.integrations.contains('loadContextsIntegration'));
});

test('loadContextsIntegration do not throw', () async {
_channel.setMockMethodCallHandler((MethodCall methodCall) async {
throw null;
});

final integration = loadContextsIntegration(fixture.options, _channel);

await integration(fixture.hub, fixture.options);

expect(true,
fixture.options.sdk.integrations.contains('loadContextsIntegration'));
});
}

class Fixture {
Expand Down
142 changes: 142 additions & 0 deletions flutter/test/load_contexts_integrations_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sentry_flutter/sentry_flutter.dart';

import 'mocks.dart';

void main() {
const MethodChannel _channel = MethodChannel('sentry_flutter');

TestWidgetsFlutterBinding.ensureInitialized();

bool called = false;

setUp(() {
_channel.setMockMethodCallHandler((MethodCall methodCall) async {
called = true;
return {
'integrations': ['NativeIntegration'],
'package': {'sdk_name': 'native-package', 'version': '1.0'},
'contexts': {
'device': {'name': 'Device1'},
'app': {'app_name': 'test-app'},
'os': {'name': 'os1'},
'gpu': {'name': 'gpu1'},
'browser': {'name': 'browser1'},
'runtime': {'name': 'RT1'},
'theme': 'material',
}
};
});
});

tearDown(() {
_channel.setMockMethodCallHandler(null);
});

test('should apply the loadContextsIntegration eventProcessor', () async {
final options = SentryOptions()..dsn = fakeDsn;
final hub = Hub(options);

loadContextsIntegration(options, _channel)(hub, options);

expect(options.eventProcessors.length, 1);

final e = SentryEvent();
final event = await options.eventProcessors.first(e, null);

expect(called, true);
expect(event.contexts.device.name, 'Device1');
expect(event.contexts.app.name, 'test-app');
expect(event.contexts.operatingSystem.name, 'os1');
expect(event.contexts.gpu.name, 'gpu1');
expect(event.contexts.browser.name, 'browser1');
expect(
event.contexts.runtimes.any((element) => element.name == 'RT1'), true);
expect(event.contexts['theme'], 'material');
expect(
event.sdk.packages.any((element) => element.name == 'native-package'),
true,
);
expect(event.sdk.integrations.contains('NativeIntegration'), true);
});

test(
'should not override event contexts with the loadContextsIntegration infos',
() async {
final options = SentryOptions()..dsn = fakeDsn;
final hub = Hub(options);

loadContextsIntegration(options, _channel)(hub, options);

expect(options.eventProcessors.length, 1);

final eventContexts = Contexts(
device: const Device(name: 'eDevice'),
app: const App(name: 'eApp'),
operatingSystem: const OperatingSystem(name: 'eOS'),
gpu: const Gpu(name: 'eGpu'),
browser: const Browser(name: 'eBrowser'),
runtimes: [const SentryRuntime(name: 'eRT')])
..['theme'] = 'cuppertino';
final e = SentryEvent(contexts: eventContexts);
final event = await options.eventProcessors.first(e, null);

expect(called, true);
expect(event.contexts.device.name, 'eDevice');
expect(event.contexts.app.name, 'eApp');
expect(event.contexts.operatingSystem.name, 'eOS');
expect(event.contexts.gpu.name, 'eGpu');
expect(event.contexts.browser.name, 'eBrowser');
expect(
event.contexts.runtimes.any((element) => element.name == 'RT1'), true);
expect(
event.contexts.runtimes.any((element) => element.name == 'eRT'), true);
expect(event.contexts['theme'], 'cuppertino');
});

test(
'should merge event and loadContextsIntegration sdk packages and integration',
() async {
final options = SentryOptions()..dsn = fakeDsn;
final hub = Hub(options);

loadContextsIntegration(options, _channel)(hub, options);

final eventSdk = SdkVersion(
name: 'sdk1',
version: '1.0',
integrations: ['EventIntegration'],
packages: [const SentryPackage('event-package', '2.0')],
);
final e = SentryEvent(sdk: eventSdk);
final event = await options.eventProcessors.first(e, null);

expect(
event.sdk.packages.any((element) => element.name == 'native-package'),
true,
);
expect(
event.sdk.packages.any((element) => element.name == 'event-package'),
true,
);
expect(event.sdk.integrations.contains('NativeIntegration'), true);
expect(event.sdk.integrations.contains('EventIntegration'), true);
},
);

test('should not throw on loadContextsIntegration exception', () async {
_channel.setMockMethodCallHandler((MethodCall methodCall) async {
throw null;
});
final options = SentryOptions()..dsn = fakeDsn;
final hub = Hub(options);

loadContextsIntegration(options, _channel)(hub, options);

final e = SentryEvent();
final event = await options.eventProcessors.first(e, null);

expect(event, isNotNull);
});
}
2 changes: 2 additions & 0 deletions flutter/test/mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ import 'package:sentry/sentry.dart';

class MockHub extends Mock implements Hub {}

class MockTransport extends Mock implements Transport {}

const fakeDsn = 'https://[email protected]/1234567';
70 changes: 69 additions & 1 deletion flutter/test/sentry_flutter_test.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:package_info/package_info.dart';
import 'package:sentry/sentry.dart';
import 'package:sentry_flutter/sentry_flutter.dart';

import 'mocks.dart';
import 'sentry_flutter_util.dart';

void main() {
Expand All @@ -22,11 +24,77 @@ void main() {

test('Flutter init for mobile will run default configurations', () async {
await SentryFlutter.init(
configurationTester,
getConfigurationTester(),
callback,
packageLoader: loadTestPackage,
iOSPlatformChecker: () => false,
);
});

test('Flutter init for mobile will run default configurations on ios',
() async {
await SentryFlutter.init(
getConfigurationTester(isIOS: true),
callback,
packageLoader: loadTestPackage,
iOSPlatformChecker: () => true,
);
});

group('platform based loadContextsIntegration', () {
final transport = MockTransport();

setUp(() {
_channel.setMockMethodCallHandler(
(MethodCall methodCall) async => <String, dynamic>{},
);
when(transport.send(any))
.thenAnswer((realInvocation) => Future.value(SentryId.newId()));
});

tearDown(() {
_channel.setMockMethodCallHandler(null);
Sentry.close();
});

test('should add loadContextsIntegration on ios', () async {
await SentryFlutter.init(
(options) => options
..dsn = fakeDsn
..transport = transport,
callback,
packageLoader: loadTestPackage,
iOSPlatformChecker: () => true,
);

await Sentry.captureMessage('a message');

final event =
verify(transport.send(captureAny)).captured.first as SentryEvent;

expect(event.sdk.integrations.length, 5);
expect(event.sdk.integrations.contains('loadContextsIntegration'), true);
});

test('should not add loadContextsIntegration if not ios', () async {
await SentryFlutter.init(
(options) => options
..dsn = fakeDsn
..transport = transport,
callback,
packageLoader: loadTestPackage,
iOSPlatformChecker: () => false,
);

await Sentry.captureMessage('a message');

final event =
verify(transport.send(captureAny)).captured.first as SentryEvent;

expect(event.sdk.integrations.length, 4);
expect(event.sdk.integrations.contains('loadContextsIntegration'), false);
});
});
}

void callback() {}
Expand Down
Loading