Unverified Commit 8109dcc2 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

CrashReportSender dependency injection (#54924)

parent 9202e547
...@@ -7,7 +7,7 @@ import 'dart:async'; ...@@ -7,7 +7,7 @@ import 'dart:async';
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
import 'package:intl/intl.dart' as intl; import 'package:intl/intl.dart' as intl;
import 'package:intl/intl_standalone.dart' as intl_standalone; import 'package:intl/intl_standalone.dart' as intl_standalone;
import 'package:meta/meta.dart'; import 'package:http/http.dart' as http;
import 'src/base/common.dart'; import 'src/base/common.dart';
import 'src/base/context.dart'; import 'src/base/context.dart';
...@@ -33,7 +33,7 @@ Future<int> run( ...@@ -33,7 +33,7 @@ Future<int> run(
bool reportCrashes, bool reportCrashes,
String flutterVersion, String flutterVersion,
Map<Type, Generator> overrides, Map<Type, Generator> overrides,
}) async { }) async {
if (muteCommandLogging) { if (muteCommandLogging) {
// Remove the verbose option; for help and doctor, users don't need to see // Remove the verbose option; for help and doctor, users don't need to see
// verbose logs. // verbose logs.
...@@ -121,7 +121,14 @@ Future<int> _handleToolError( ...@@ -121,7 +121,14 @@ Future<int> _handleToolError(
// Report to both [Usage] and [CrashReportSender]. // Report to both [Usage] and [CrashReportSender].
globals.flutterUsage.sendException(error); globals.flutterUsage.sendException(error);
await CrashReportSender.instance.sendReport( final CrashReportSender crashReportSender = CrashReportSender(
client: http.Client(),
usage: globals.flutterUsage,
platform: globals.platform,
logger: globals.logger,
operatingSystemUtils: globals.os,
);
await crashReportSender.sendReport(
error: error, error: error,
stackTrace: stackTrace, stackTrace: stackTrace,
getFlutterVersion: getFlutterVersion, getFlutterVersion: getFlutterVersion,
...@@ -184,18 +191,10 @@ String _crashCommand(List<String> args) => 'flutter ${args.join(' ')}'; ...@@ -184,18 +191,10 @@ String _crashCommand(List<String> args) => 'flutter ${args.join(' ')}';
String _crashException(dynamic error) => '${error.runtimeType}: $error'; String _crashException(dynamic error) => '${error.runtimeType}: $error';
/// File system used by the crash reporting logic.
///
/// We do not want to use the file system stored in the context because it may
/// be recording. Additionally, in the case of a crash we do not trust the
/// integrity of the [AppContext].
@visibleForTesting
FileSystem crashFileSystem = const LocalFileSystem();
/// Saves the crash report to a local file. /// Saves the crash report to a local file.
Future<File> _createLocalCrashReport(List<String> args, dynamic error, StackTrace stackTrace, String doctorText) async { Future<File> _createLocalCrashReport(List<String> args, dynamic error, StackTrace stackTrace, String doctorText) async {
File crashFile = globals.fsUtils.getUniqueFile( File crashFile = globals.fsUtils.getUniqueFile(
crashFileSystem.currentDirectory, globals.fs.currentDirectory,
'flutter', 'flutter',
'log', 'log',
); );
...@@ -219,7 +218,7 @@ Future<File> _createLocalCrashReport(List<String> args, dynamic error, StackTrac ...@@ -219,7 +218,7 @@ Future<File> _createLocalCrashReport(List<String> args, dynamic error, StackTrac
} on FileSystemException catch (_) { } on FileSystemException catch (_) {
// Fallback to the system temporary directory. // Fallback to the system temporary directory.
crashFile = globals.fsUtils.getUniqueFile( crashFile = globals.fsUtils.getUniqueFile(
crashFileSystem.systemTempDirectory, globals.fs.systemTempDirectory,
'flutter', 'flutter',
'log', 'log',
); );
......
...@@ -35,28 +35,29 @@ const String _kStackTraceFilename = 'stacktrace_file'; ...@@ -35,28 +35,29 @@ const String _kStackTraceFilename = 'stacktrace_file';
/// environment is behind a firewall and unable to send crash reports to /// environment is behind a firewall and unable to send crash reports to
/// Google, or when you wish to use your own server for collecting crash /// Google, or when you wish to use your own server for collecting crash
/// reports from Flutter Tools. /// reports from Flutter Tools.
/// * In tests call [initializeWith] and provide a mock implementation of
/// [http.Client].
class CrashReportSender { class CrashReportSender {
CrashReportSender._(this._client); CrashReportSender({
@required http.Client client,
@required Usage usage,
@required Platform platform,
@required Logger logger,
@required OperatingSystemUtils operatingSystemUtils,
}) : _client = client,
_usage = usage,
_platform = platform,
_logger = logger,
_operatingSystemUtils = operatingSystemUtils;
static CrashReportSender _instance; final http.Client _client;
final Usage _usage;
static CrashReportSender get instance => _instance ?? CrashReportSender._(http.Client()); final Platform _platform;
final Logger _logger;
final OperatingSystemUtils _operatingSystemUtils;
bool _crashReportSent = false; bool _crashReportSent = false;
/// Overrides the default [http.Client] with [client] for testing purposes.
@visibleForTesting
static void initializeWith(http.Client client) {
_instance = CrashReportSender._(client);
}
final http.Client _client;
final Usage _usage = globals.flutterUsage;
Uri get _baseUrl { Uri get _baseUrl {
final String overrideUrl = globals.platform.environment['FLUTTER_CRASH_SERVER_BASE_URL']; final String overrideUrl = _platform.environment['FLUTTER_CRASH_SERVER_BASE_URL'];
if (overrideUrl != null) { if (overrideUrl != null) {
return Uri.parse(overrideUrl); return Uri.parse(overrideUrl);
...@@ -90,7 +91,7 @@ class CrashReportSender { ...@@ -90,7 +91,7 @@ class CrashReportSender {
return; return;
} }
globals.printTrace('Sending crash report to Google.'); _logger.printTrace('Sending crash report to Google.');
final Uri uri = _baseUrl.replace( final Uri uri = _baseUrl.replace(
queryParameters: <String, String>{ queryParameters: <String, String>{
...@@ -103,8 +104,8 @@ class CrashReportSender { ...@@ -103,8 +104,8 @@ class CrashReportSender {
req.fields['uuid'] = _usage.clientId; req.fields['uuid'] = _usage.clientId;
req.fields['product'] = _kProductId; req.fields['product'] = _kProductId;
req.fields['version'] = flutterVersion; req.fields['version'] = flutterVersion;
req.fields['osName'] = globals.platform.operatingSystem; req.fields['osName'] = _platform.operatingSystem;
req.fields['osVersion'] = globals.os.name; // this actually includes version req.fields['osVersion'] = _operatingSystemUtils.name; // this actually includes version
req.fields['type'] = _kDartTypeId; req.fields['type'] = _kDartTypeId;
req.fields['error_runtime_type'] = '${error.runtimeType}'; req.fields['error_runtime_type'] = '${error.runtimeType}';
req.fields['error_message'] = '$error'; req.fields['error_message'] = '$error';
...@@ -121,19 +122,19 @@ class CrashReportSender { ...@@ -121,19 +122,19 @@ class CrashReportSender {
if (resp.statusCode == 200) { if (resp.statusCode == 200) {
final String reportId = await http.ByteStream(resp.stream) final String reportId = await http.ByteStream(resp.stream)
.bytesToString(); .bytesToString();
globals.printTrace('Crash report sent (report ID: $reportId)'); _logger.printTrace('Crash report sent (report ID: $reportId)');
_crashReportSent = true; _crashReportSent = true;
} else { } else {
globals.printError('Failed to send crash report. Server responded with HTTP status code ${resp.statusCode}'); _logger.printError('Failed to send crash report. Server responded with HTTP status code ${resp.statusCode}');
} }
// Catch all exceptions to print the message that makes clear that the // Catch all exceptions to print the message that makes clear that the
// crash logger crashed. // crash logger crashed.
} catch (sendError, sendStackTrace) { // ignore: avoid_catches_without_on_clauses } catch (sendError, sendStackTrace) { // ignore: avoid_catches_without_on_clauses
if (sendError is SocketException || sendError is HttpException) { if (sendError is SocketException || sendError is HttpException) {
globals.printError('Failed to send crash report due to a network error: $sendError'); _logger.printError('Failed to send crash report due to a network error: $sendError');
} else { } else {
// If the sender itself crashes, just print. We did our best. // If the sender itself crashes, just print. We did our best.
globals.printError('Crash report sender itself crashed: $sendError\n$sendStackTrace'); _logger.printError('Crash report sender itself crashed: $sendError\n$sendStackTrace');
} }
} }
} }
......
...@@ -10,11 +10,13 @@ import 'package:file/file.dart'; ...@@ -10,11 +10,13 @@ import 'package:file/file.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:platform/platform.dart';
import 'package:usage/usage_io.dart'; import 'package:usage/usage_io.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/os.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../base/time.dart'; import '../base/time.dart';
import '../build_system/exceptions.dart'; import '../build_system/exceptions.dart';
......
...@@ -5,160 +5,205 @@ ...@@ -5,160 +5,205 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:file/file.dart';
import 'package:file/local.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/runner.dart' as tools;
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:http/testing.dart'; import 'package:http/testing.dart';
import 'package:quiver/testing/async.dart'; import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart'; import 'package:platform/platform.dart';
import '../src/common.dart'; import '../src/common.dart';
import '../src/context.dart'; import '../src/context.dart';
void main() { void main() {
group('crash reporting', () { BufferLogger logger;
setUpAll(() { MockUsage mockUsage;
Cache.disableLocking(); Platform platform;
}); OperatingSystemUtils operatingSystemUtils;
setUp(() async { setUp(() async {
tools.crashFileSystem = MemoryFileSystem(); logger = BufferLogger.test();
setExitFunctionForTests((_) { });
mockUsage = MockUsage();
when(mockUsage.clientId).thenReturn('00000000-0000-4000-0000-000000000000');
platform = FakePlatform(environment: <String, String>{}, operatingSystem: 'linux');
operatingSystemUtils = OperatingSystemUtils(
fileSystem: MemoryFileSystem.test(),
logger: logger,
platform: platform,
processManager: FakeProcessManager.any(),
);
MockCrashReportSender.sendCalls = 0; MockCrashReportSender.sendCalls = 0;
}); });
tearDown(() { Future<void> verifyCrashReportSent(RequestInfo crashInfo, {
tools.crashFileSystem = const LocalFileSystem(); int crashes = 1,
restoreExitFunction(); }) async {
}); // Verify that we sent the crash report.
expect(crashInfo.method, 'POST');
expect(crashInfo.uri, Uri(
scheme: 'https',
host: 'clients2.google.com',
port: 443,
path: '/cr/report',
queryParameters: <String, String>{
'product': 'Flutter_Tools',
'version': 'test-version',
},
));
expect(crashInfo.fields['uuid'], '00000000-0000-4000-0000-000000000000');
expect(crashInfo.fields['product'], 'Flutter_Tools');
expect(crashInfo.fields['version'], 'test-version');
expect(crashInfo.fields['osName'], 'linux');
expect(crashInfo.fields['osVersion'], 'Linux');
expect(crashInfo.fields['type'], 'DartError');
expect(crashInfo.fields['error_runtime_type'], 'StateError');
expect(crashInfo.fields['error_message'], 'Bad state: Test bad state error');
expect(crashInfo.fields['comments'], 'crash');
testUsingContext('should send crash reports', () async { expect(logger.traceText, contains('Sending crash report to Google.'));
final RequestInfo requestInfo = RequestInfo(); expect(logger.traceText, contains('Crash report sent (report ID: test-report-id)'));
}
CrashReportSender.initializeWith(MockCrashReportSender(requestInfo)); testWithoutContext('suppress analytics', () async {
final int exitCode = await tools.run( when(mockUsage.suppressAnalytics).thenReturn(true);
<String>['crash'],
<FlutterCommand>[_CrashCommand()], final CrashReportSender crashReportSender = CrashReportSender(
reportCrashes: true, client: CrashingCrashReportSender(const SocketException('no internets')),
flutterVersion: 'test-version', usage: mockUsage,
platform: platform,
logger: logger,
operatingSystemUtils: operatingSystemUtils,
); );
expect(exitCode, 1);
await verifyCrashReportSent(requestInfo); await crashReportSender.sendReport(
}, overrides: <Type, Generator>{ error: StateError('Test bad state error'),
Stdio: () => _NoStderr(), stackTrace: null,
getFlutterVersion: () => 'test-version',
command: 'crash',
);
expect(logger.traceText, isEmpty);
}); });
testUsingContext('should print an explanatory message when there is a SocketException', () async { group('allow analytics', () {
final Completer<int> exitCodeCompleter = Completer<int>(); setUp(() async {
setExitFunctionForTests((int exitCode) { when(mockUsage.suppressAnalytics).thenReturn(false);
exitCodeCompleter.complete(exitCode);
}); });
CrashReportSender.initializeWith( testWithoutContext('should send crash reports', () async {
CrashingCrashReportSender(const SocketException('no internets'))); final RequestInfo requestInfo = RequestInfo();
unawaited(tools.run( final CrashReportSender crashReportSender = CrashReportSender(
<String>['crash'], client: MockCrashReportSender(requestInfo),
<FlutterCommand>[_CrashAsyncCommand()], usage: mockUsage,
reportCrashes: true, platform: platform,
flutterVersion: 'test-version', logger: logger,
)); operatingSystemUtils: operatingSystemUtils,
expect(await exitCodeCompleter.future, 1); );
expect(testLogger.errorText, contains('Failed to send crash report due to a network error'));
}, overrides: <Type, Generator>{ await crashReportSender.sendReport(
Stdio: () => _NoStderr(), error: StateError('Test bad state error'),
}); stackTrace: null,
getFlutterVersion: () => 'test-version',
command: 'crash',
);
testUsingContext('should print an explanatory message when there is an HttpException', () async { await verifyCrashReportSent(requestInfo);
final Completer<int> exitCodeCompleter = Completer<int>();
setExitFunctionForTests((int exitCode) {
exitCodeCompleter.complete(exitCode);
}); });
CrashReportSender.initializeWith( testWithoutContext('should print an explanatory message when there is a SocketException', () async {
CrashingCrashReportSender(const HttpException('no internets'))); final CrashReportSender crashReportSender = CrashReportSender(
client: CrashingCrashReportSender(const SocketException('no internets')),
usage: mockUsage,
platform: platform,
logger: logger,
operatingSystemUtils: operatingSystemUtils,
);
unawaited(tools.run( await crashReportSender.sendReport(
<String>['crash'], error: StateError('Test bad state error'),
<FlutterCommand>[_CrashAsyncCommand()], stackTrace: null,
reportCrashes: true, getFlutterVersion: () => 'test-version',
flutterVersion: 'test-version', command: 'crash',
)); );
expect(await exitCodeCompleter.future, 1);
expect(testLogger.errorText, contains('Failed to send crash report due to a network error')); expect(logger.errorText, contains('Failed to send crash report due to a network error'));
}, overrides: <Type, Generator>{
Stdio: () => _NoStderr(),
}); });
testUsingContext('should send crash reports when async throws', () async { testWithoutContext('should print an explanatory message when there is an HttpException', () async {
final Completer<int> exitCodeCompleter = Completer<int>(); final CrashReportSender crashReportSender = CrashReportSender(
setExitFunctionForTests((int exitCode) { client: CrashingCrashReportSender(const HttpException('no internets')),
exitCodeCompleter.complete(exitCode); usage: mockUsage,
platform: platform,
logger: logger,
operatingSystemUtils: operatingSystemUtils,
);
await crashReportSender.sendReport(
error: StateError('Test bad state error'),
stackTrace: null,
getFlutterVersion: () => 'test-version',
command: 'crash',
);
expect(logger.errorText, contains('Failed to send crash report due to a network error'));
}); });
testWithoutContext('should send only one crash report when sent many times', () async {
final RequestInfo requestInfo = RequestInfo(); final RequestInfo requestInfo = RequestInfo();
CrashReportSender.initializeWith(MockCrashReportSender(requestInfo)); final CrashReportSender crashReportSender = CrashReportSender(
client: MockCrashReportSender(requestInfo),
usage: mockUsage,
platform: platform,
logger: logger,
operatingSystemUtils: operatingSystemUtils,
);
unawaited(tools.run( await crashReportSender.sendReport(
<String>['crash'], error: StateError('Test bad state error'),
<FlutterCommand>[_CrashAsyncCommand()], stackTrace: null,
reportCrashes: true, getFlutterVersion: () => 'test-version',
flutterVersion: 'test-version', command: 'crash',
)); );
expect(await exitCodeCompleter.future, 1);
await verifyCrashReportSent(requestInfo);
}, overrides: <Type, Generator>{
Stdio: () => _NoStderr(),
});
testUsingContext('should send only one crash report when async throws many', () async { await crashReportSender.sendReport(
final Completer<int> exitCodeCompleter = Completer<int>(); error: StateError('Test bad state error'),
setExitFunctionForTests((int exitCode) { stackTrace: null,
if (!exitCodeCompleter.isCompleted) { getFlutterVersion: () => 'test-version',
exitCodeCompleter.complete(exitCode); command: 'crash',
} );
});
await crashReportSender.sendReport(
error: StateError('Test bad state error'),
stackTrace: null,
getFlutterVersion: () => 'test-version',
command: 'crash',
);
await crashReportSender.sendReport(
error: StateError('Test bad state error'),
stackTrace: null,
getFlutterVersion: () => 'test-version',
command: 'crash',
);
final RequestInfo requestInfo = RequestInfo();
final MockCrashReportSender sender = MockCrashReportSender(requestInfo);
CrashReportSender.initializeWith(sender);
FakeAsync().run((FakeAsync time) {
time.elapse(const Duration(seconds: 1));
unawaited(tools.run(
<String>['crash'],
<FlutterCommand>[_MultiCrashAsyncCommand(crashes: 4)],
reportCrashes: true,
flutterVersion: 'test-version',
));
time.elapse(const Duration(seconds: 1));
time.flushMicrotasks();
});
expect(await exitCodeCompleter.future, 1);
expect(MockCrashReportSender.sendCalls, 1); expect(MockCrashReportSender.sendCalls, 1);
await verifyCrashReportSent(requestInfo, crashes: 4); await verifyCrashReportSent(requestInfo, crashes: 4);
}, overrides: <Type, Generator>{
DoctorValidatorsProvider: () => FakeDoctorValidatorsProvider(),
Stdio: () => _NoStderr(),
}); });
testUsingContext('should not send a crash report if on a user-branch', () async { testWithoutContext('should not send a crash report if on a user-branch', () async {
String method; String method;
Uri uri; Uri uri;
CrashReportSender.initializeWith(MockClient((Request request) async { final MockClient mockClient = MockClient((Request request) async {
method = request.method; method = request.method;
uri = request.url; uri = request.url;
...@@ -166,41 +211,60 @@ void main() { ...@@ -166,41 +211,60 @@ void main() {
'test-report-id', 'test-report-id',
200, 200,
); );
})); });
final int exitCode = await tools.run( final CrashReportSender crashReportSender = CrashReportSender(
<String>['crash'], client: mockClient,
<FlutterCommand>[_CrashCommand()], usage: mockUsage,
reportCrashes: true, platform: platform,
flutterVersion: '[user-branch]/v1.2.3', logger: logger,
operatingSystemUtils: operatingSystemUtils,
); );
expect(exitCode, 1); await crashReportSender.sendReport(
error: StateError('Test bad state error'),
stackTrace: null,
getFlutterVersion: () => '[user-branch]/v1.2.3',
command: 'crash',
);
// Verify that the report wasn't sent // Verify that the report wasn't sent
expect(method, null); expect(method, null);
expect(uri, null); expect(uri, null);
expect(testLogger.traceText, isNot(contains('Crash report sent'))); expect(logger.traceText, isNot(contains('Crash report sent')));
}, overrides: <Type, Generator>{
Stdio: () => _NoStderr(),
}); });
testUsingContext('can override base URL', () async { testWithoutContext('can override base URL', () async {
Uri uri; Uri uri;
CrashReportSender.initializeWith(MockClient((Request request) async { final MockClient mockClient = MockClient((Request request) async {
uri = request.url; uri = request.url;
return Response('test-report-id', 200); return Response('test-report-id', 200);
})); });
final int exitCode = await tools.run( final Platform environmentPlatform = FakePlatform(
<String>['crash'], operatingSystem: 'linux',
<FlutterCommand>[_CrashCommand()], environment: <String, String>{
reportCrashes: true, 'HOME': '/',
flutterVersion: 'test-version', 'FLUTTER_CRASH_SERVER_BASE_URL': 'https://localhost:12345/fake_server',
},
script: Uri(scheme: 'data'),
); );
expect(exitCode, 1); final CrashReportSender crashReportSender = CrashReportSender(
client: mockClient,
usage: mockUsage,
platform: environmentPlatform,
logger: logger,
operatingSystemUtils: operatingSystemUtils,
);
await crashReportSender.sendReport(
error: StateError('Test bad state error'),
stackTrace: null,
getFlutterVersion: () => 'test-version',
command: 'crash',
);
// Verify that we sent the crash report. // Verify that we sent the crash report.
expect(uri, isNotNull); expect(uri, isNotNull);
...@@ -214,16 +278,6 @@ void main() { ...@@ -214,16 +278,6 @@ void main() {
'version': 'test-version', 'version': 'test-version',
}, },
)); ));
}, overrides: <Type, Generator>{
Platform: () => FakePlatform(
operatingSystem: 'linux',
environment: <String, String>{
'HOME': '/',
'FLUTTER_CRASH_SERVER_BASE_URL': 'https://localhost:12345/fake_server',
},
script: Uri(scheme: 'data'),
),
Stdio: () => _NoStderr(),
}); });
}); });
} }
...@@ -234,42 +288,6 @@ class RequestInfo { ...@@ -234,42 +288,6 @@ class RequestInfo {
Map<String, String> fields; Map<String, String> fields;
} }
Future<void> verifyCrashReportSent(RequestInfo crashInfo, {
int crashes = 1,
}) async {
// Verify that we sent the crash report.
expect(crashInfo.method, 'POST');
expect(crashInfo.uri, Uri(
scheme: 'https',
host: 'clients2.google.com',
port: 443,
path: '/cr/report',
queryParameters: <String, String>{
'product': 'Flutter_Tools',
'version': 'test-version',
},
));
expect(crashInfo.fields['uuid'], '00000000-0000-4000-0000-000000000000');
expect(crashInfo.fields['product'], 'Flutter_Tools');
expect(crashInfo.fields['version'], 'test-version');
expect(crashInfo.fields['osName'], globals.platform.operatingSystem);
expect(crashInfo.fields['osVersion'], 'fake OS name and version');
expect(crashInfo.fields['type'], 'DartError');
expect(crashInfo.fields['error_runtime_type'], 'StateError');
expect(crashInfo.fields['error_message'], 'Bad state: Test bad state error');
expect(crashInfo.fields['comments'], 'crash');
expect(testLogger.traceText, contains('Sending crash report to Google.'));
expect(testLogger.traceText, contains('Crash report sent (report ID: test-report-id)'));
// Verify that we've written the crash report to disk.
final List<String> writtenFiles =
(await tools.crashFileSystem.directory('/').list(recursive: true).toList())
.map((FileSystemEntity e) => e.path).toList();
expect(writtenFiles, hasLength(crashes));
expect(writtenFiles, contains('flutter_01.log'));
}
class MockCrashReportSender extends MockClient { class MockCrashReportSender extends MockClient {
MockCrashReportSender(RequestInfo crashInfo) : super((Request request) async { MockCrashReportSender(RequestInfo crashInfo) : super((Request request) async {
MockCrashReportSender.sendCalls++; MockCrashReportSender.sendCalls++;
...@@ -317,78 +335,6 @@ class CrashingCrashReportSender extends MockClient { ...@@ -317,78 +335,6 @@ class CrashingCrashReportSender extends MockClient {
}); });
} }
/// Throws a random error to simulate a CLI crash.
class _CrashCommand extends FlutterCommand {
@override
String get description => 'Simulates a crash';
@override
String get name => 'crash';
@override
Future<FlutterCommandResult> runCommand() async {
void fn1() {
throw StateError('Test bad state error');
}
void fn2() {
fn1();
}
void fn3() {
fn2();
}
fn3();
return FlutterCommandResult.success();
}
}
/// Throws StateError from async callback.
class _CrashAsyncCommand extends FlutterCommand {
@override
String get description => 'Simulates a crash';
@override
String get name => 'crash';
@override
Future<FlutterCommandResult> runCommand() async {
Timer.run(() {
throw StateError('Test bad state error');
});
return Completer<FlutterCommandResult>().future; // expect StateError
}
}
/// Generates multiple asynchronous unhandled exceptions.
class _MultiCrashAsyncCommand extends FlutterCommand {
_MultiCrashAsyncCommand({
int crashes = 1,
}) : _crashes = crashes;
final int _crashes;
@override
String get description => 'Simulates a crash';
@override
String get name => 'crash';
@override
Future<FlutterCommandResult> runCommand() async {
for (int i = 0; i < _crashes; i++) {
Timer.run(() {
throw StateError('Test bad state error');
});
}
return Completer<FlutterCommandResult>().future; // expect StateError
}
}
/// A DoctorValidatorsProvider that overrides the default validators without /// A DoctorValidatorsProvider that overrides the default validators without
/// overriding the doctor. /// overriding the doctor.
class FakeDoctorValidatorsProvider implements DoctorValidatorsProvider { class FakeDoctorValidatorsProvider implements DoctorValidatorsProvider {
...@@ -399,49 +345,4 @@ class FakeDoctorValidatorsProvider implements DoctorValidatorsProvider { ...@@ -399,49 +345,4 @@ class FakeDoctorValidatorsProvider implements DoctorValidatorsProvider {
List<Workflow> get workflows => <Workflow>[]; List<Workflow> get workflows => <Workflow>[];
} }
class _NoStderr extends Stdio { class MockUsage extends Mock implements Usage {}
_NoStderr();
@override
IOSink get stderr => const _NoopIOSink();
}
class _NoopIOSink implements IOSink {
const _NoopIOSink();
@override
Encoding get encoding => utf8;
@override
set encoding(_) => throw UnsupportedError('');
@override
void add(_) { }
@override
void write(_) { }
@override
void writeAll(_, [ __ = '' ]) { }
@override
void writeln([ _ = '' ]) { }
@override
void writeCharCode(_) { }
@override
void addError(_, [ __ ]) { }
@override
Future<dynamic> addStream(_) async { }
@override
Future<dynamic> flush() async { }
@override
Future<dynamic> close() async { }
@override
Future<dynamic> get done async { }
}
...@@ -24,7 +24,6 @@ void main() { ...@@ -24,7 +24,6 @@ void main() {
MockGitHubTemplateCreator mockGitHubTemplateCreator; MockGitHubTemplateCreator mockGitHubTemplateCreator;
setUp(() { setUp(() {
mockGitHubTemplateCreator = MockGitHubTemplateCreator(); mockGitHubTemplateCreator = MockGitHubTemplateCreator();
runner.crashFileSystem = MemoryFileSystem();
// Instead of exiting with dart:io exit(), this causes an exception to // Instead of exiting with dart:io exit(), this causes an exception to
// be thrown, which we catch with the onError callback in the zone below. // be thrown, which we catch with the onError callback in the zone below.
io.setExitFunctionForTests((int _) { throw 'test exit';}); io.setExitFunctionForTests((int _) { throw 'test exit';});
...@@ -32,7 +31,6 @@ void main() { ...@@ -32,7 +31,6 @@ void main() {
}); });
tearDown(() { tearDown(() {
runner.crashFileSystem = const LocalFileSystem();
io.restoreExitFunction(); io.restoreExitFunction();
Cache.enableLocking(); Cache.enableLocking();
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment