Unverified Commit 6987c9fb authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Migrate crash_reporting and github_template (#91516)

parent 98e66fdb
...@@ -8,11 +8,9 @@ import 'base/context.dart'; ...@@ -8,11 +8,9 @@ import 'base/context.dart';
import 'doctor.dart'; import 'doctor.dart';
import 'ios/simulators.dart'; import 'ios/simulators.dart';
import 'macos/xcdevice.dart'; import 'macos/xcdevice.dart';
import 'reporting/crash_reporting.dart';
export 'globals_null_migrated.dart'; export 'globals_null_migrated.dart';
CrashReporter get crashReporter => context.get<CrashReporter>();
Doctor get doctor => context.get<Doctor>(); Doctor get doctor => context.get<Doctor>();
IOSSimulatorUtils get iosSimulatorUtils => context.get<IOSSimulatorUtils>(); IOSSimulatorUtils get iosSimulatorUtils => context.get<IOSSimulatorUtils>();
......
...@@ -37,6 +37,7 @@ import 'macos/cocoapods_validator.dart'; ...@@ -37,6 +37,7 @@ import 'macos/cocoapods_validator.dart';
import 'macos/xcode.dart'; import 'macos/xcode.dart';
import 'persistent_tool_state.dart'; import 'persistent_tool_state.dart';
import 'project.dart'; import 'project.dart';
import 'reporting/crash_reporting.dart';
import 'reporting/reporting.dart'; import 'reporting/reporting.dart';
import 'runner/local_engine.dart'; import 'runner/local_engine.dart';
import 'version.dart'; import 'version.dart';
...@@ -49,6 +50,7 @@ BuildSystem? get buildSystem => context.get<BuildSystem>(); ...@@ -49,6 +50,7 @@ BuildSystem? get buildSystem => context.get<BuildSystem>();
Cache get cache => context.get<Cache>()!; Cache get cache => context.get<Cache>()!;
CocoaPodsValidator? get cocoapodsValidator => context.get<CocoaPodsValidator>(); CocoaPodsValidator? get cocoapodsValidator => context.get<CocoaPodsValidator>();
Config get config => context.get<Config>()!; Config get config => context.get<Config>()!;
CrashReporter? get crashReporter => context.get<CrashReporter>();
DeviceManager? get deviceManager => context.get<DeviceManager>(); DeviceManager? get deviceManager => context.get<DeviceManager>();
HttpClientFactory? get httpClientFactory => context.get<HttpClientFactory>(); HttpClientFactory? get httpClientFactory => context.get<HttpClientFactory>();
Logger get logger => context.get<Logger>()!; Logger get logger => context.get<Logger>()!;
......
...@@ -2,13 +2,10 @@ ...@@ -2,13 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// @dart = 2.8
import 'dart:async'; import 'dart:async';
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:meta/meta.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
...@@ -43,14 +40,14 @@ const String _kStackTraceFilename = 'stacktrace_file'; ...@@ -43,14 +40,14 @@ const String _kStackTraceFilename = 'stacktrace_file';
class CrashDetails { class CrashDetails {
CrashDetails({ CrashDetails({
@required this.command, required this.command,
@required this.error, required this.error,
@required this.stackTrace, required this.stackTrace,
@required this.doctorText, required this.doctorText,
}); });
final String command; final String command;
final dynamic error; final Object error;
final StackTrace stackTrace; final StackTrace stackTrace;
final String doctorText; final String doctorText;
} }
...@@ -58,10 +55,10 @@ class CrashDetails { ...@@ -58,10 +55,10 @@ class CrashDetails {
/// Reports information about the crash to the user. /// Reports information about the crash to the user.
class CrashReporter { class CrashReporter {
CrashReporter({ CrashReporter({
@required FileSystem fileSystem, required FileSystem fileSystem,
@required Logger logger, required Logger logger,
@required FlutterProjectFactory flutterProjectFactory, required FlutterProjectFactory flutterProjectFactory,
@required HttpClient client, required HttpClient client,
}) : _fileSystem = fileSystem, }) : _fileSystem = fileSystem,
_logger = logger, _logger = logger,
_flutterProjectFactory = flutterProjectFactory, _flutterProjectFactory = flutterProjectFactory,
...@@ -110,11 +107,11 @@ class CrashReporter { ...@@ -110,11 +107,11 @@ class CrashReporter {
/// wish to use your own server for collecting crash reports from Flutter Tools. /// wish to use your own server for collecting crash reports from Flutter Tools.
class CrashReportSender { class CrashReportSender {
CrashReportSender({ CrashReportSender({
http.Client client, http.Client? client,
@required Usage usage, required Usage usage,
@required Platform platform, required Platform platform,
@required Logger logger, required Logger logger,
@required OperatingSystemUtils operatingSystemUtils, required OperatingSystemUtils operatingSystemUtils,
}) : _client = client ?? http.Client(), }) : _client = client ?? http.Client(),
_usage = usage, _usage = usage,
_platform = platform, _platform = platform,
...@@ -130,7 +127,7 @@ class CrashReportSender { ...@@ -130,7 +127,7 @@ class CrashReportSender {
bool _crashReportSent = false; bool _crashReportSent = false;
Uri get _baseUrl { Uri get _baseUrl {
final String overrideUrl = _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);
...@@ -147,10 +144,10 @@ class CrashReportSender { ...@@ -147,10 +144,10 @@ class CrashReportSender {
/// ///
/// The report is populated from data in [error] and [stackTrace]. /// The report is populated from data in [error] and [stackTrace].
Future<void> sendReport({ Future<void> sendReport({
@required dynamic error, required Object error,
@required StackTrace stackTrace, required StackTrace stackTrace,
@required String Function() getFlutterVersion, required String Function() getFlutterVersion,
@required String command, required String command,
}) async { }) async {
// Only send one crash report per run. // Only send one crash report per run.
if (_crashReportSent) { if (_crashReportSent) {
......
...@@ -2,13 +2,10 @@ ...@@ -2,13 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// @dart = 2.8
import 'dart:async'; import 'dart:async';
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:meta/meta.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
...@@ -25,10 +22,10 @@ import '../version.dart'; ...@@ -25,10 +22,10 @@ import '../version.dart';
/// Provide suggested GitHub issue templates to user when Flutter encounters an error. /// Provide suggested GitHub issue templates to user when Flutter encounters an error.
class GitHubTemplateCreator { class GitHubTemplateCreator {
GitHubTemplateCreator({ GitHubTemplateCreator({
@required FileSystem fileSystem, required FileSystem fileSystem,
@required Logger logger, required Logger logger,
@required FlutterProjectFactory flutterProjectFactory, required FlutterProjectFactory flutterProjectFactory,
@required HttpClient client, required HttpClient client,
}) : _fileSystem = fileSystem, }) : _fileSystem = fileSystem,
_logger = logger, _logger = logger,
_flutterProjectFactory = flutterProjectFactory, _flutterProjectFactory = flutterProjectFactory,
...@@ -44,7 +41,7 @@ class GitHubTemplateCreator { ...@@ -44,7 +41,7 @@ class GitHubTemplateCreator {
} }
/// Restricts exception object strings to contain only information about tool internals. /// Restricts exception object strings to contain only information about tool internals.
static String sanitizedCrashException(dynamic error) { static String sanitizedCrashException(Object error) {
if (error is ProcessException) { if (error is ProcessException) {
// Suppress args. // Suppress args.
return 'ProcessException: ${error.message} Command: ${error.executable}, OS error code: ${error.errorCode}'; return 'ProcessException: ${error.message} Command: ${error.executable}, OS error code: ${error.errorCode}';
...@@ -83,7 +80,7 @@ class GitHubTemplateCreator { ...@@ -83,7 +80,7 @@ class GitHubTemplateCreator {
/// Shorten the URL, if possible. /// Shorten the URL, if possible.
Future<String> toolCrashIssueTemplateGitHubURL( Future<String> toolCrashIssueTemplateGitHubURL(
String command, String command,
dynamic error, Object error,
StackTrace stackTrace, StackTrace stackTrace,
String doctorText String doctorText
) async { ) async {
...@@ -131,13 +128,14 @@ ${_projectMetadataInformation()} ...@@ -131,13 +128,14 @@ ${_projectMetadataInformation()}
return exception.toString(); return exception.toString();
} }
try { try {
final FlutterManifest manifest = project?.manifest; final FlutterManifest manifest = project.manifest;
if (project == null || manifest == null || manifest.isEmpty) { if (project == null || manifest == null || manifest.isEmpty) {
return 'No pubspec in working directory.'; return 'No pubspec in working directory.';
} }
final FlutterProjectMetadata metadata = FlutterProjectMetadata(project.metadataFile, _logger); final FlutterProjectMetadata metadata = FlutterProjectMetadata(project.metadataFile, _logger);
final FlutterProjectType? projectType = metadata.projectType;
final StringBuffer description = StringBuffer() final StringBuffer description = StringBuffer()
..writeln('**Type**: ${flutterProjectTypeToString(metadata.projectType)}') ..writeln('**Type**: ${projectType == null ? 'malformed' : flutterProjectTypeToString(projectType)}')
..writeln('**Version**: ${manifest.appVersion}') ..writeln('**Version**: ${manifest.appVersion}')
..writeln('**Material**: ${manifest.usesMaterialDesign}') ..writeln('**Material**: ${manifest.usesMaterialDesign}')
..writeln('**Android X**: ${manifest.usesAndroidX}') ..writeln('**Android X**: ${manifest.usesAndroidX}')
...@@ -175,7 +173,7 @@ ${_projectMetadataInformation()} ...@@ -175,7 +173,7 @@ ${_projectMetadataInformation()}
/// ///
/// See https://github.blog/2011-11-10-git-io-github-url-shortener. /// See https://github.blog/2011-11-10-git-io-github-url-shortener.
Future<String> _shortURL(String fullURL) async { Future<String> _shortURL(String fullURL) async {
String url; String? url;
try { try {
_logger.printTrace('Attempting git.io shortener: $fullURL'); _logger.printTrace('Attempting git.io shortener: $fullURL');
final List<int> bodyBytes = utf8.encode('url=${Uri.encodeQueryComponent(fullURL)}'); final List<int> bodyBytes = utf8.encode('url=${Uri.encodeQueryComponent(fullURL)}');
......
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// @dart = 2.8
import 'dart:convert'; import 'dart:convert';
import 'package:file/file.dart'; import 'package:file/file.dart';
...@@ -12,8 +10,6 @@ import 'package:flutter_tools/src/base/io.dart'; ...@@ -12,8 +10,6 @@ import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/doctor_validator.dart';
import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/crash_reporting.dart'; import 'package:flutter_tools/src/reporting/crash_reporting.dart';
import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/reporting/reporting.dart';
...@@ -25,11 +21,12 @@ import '../src/fake_http_client.dart'; ...@@ -25,11 +21,12 @@ import '../src/fake_http_client.dart';
import '../src/fake_process_manager.dart'; import '../src/fake_process_manager.dart';
void main() { void main() {
BufferLogger logger; late BufferLogger logger;
FileSystem fs; late FileSystem fs;
TestUsage testUsage; late TestUsage testUsage;
Platform platform; late Platform platform;
OperatingSystemUtils operatingSystemUtils; late OperatingSystemUtils operatingSystemUtils;
late StackTrace stackTrace;
setUp(() async { setUp(() async {
logger = BufferLogger.test(); logger = BufferLogger.test();
...@@ -45,6 +42,9 @@ void main() { ...@@ -45,6 +42,9 @@ void main() {
); );
MockCrashReportSender.sendCalls = 0; MockCrashReportSender.sendCalls = 0;
stackTrace = StackTrace.fromString('''
#0 _File.open.<anonymous closure> (dart:io/file_impl.dart:366:9)
#1 _rootRunUnary (dart:async/zone.dart:1141:38)''');
}); });
Future<void> verifyCrashReportSent(RequestInfo crashInfo, { Future<void> verifyCrashReportSent(RequestInfo crashInfo, {
...@@ -62,15 +62,15 @@ void main() { ...@@ -62,15 +62,15 @@ void main() {
'version': 'test-version', 'version': 'test-version',
}, },
)); ));
expect(crashInfo.fields['uuid'], testUsage.clientId); expect(crashInfo.fields?['uuid'], testUsage.clientId);
expect(crashInfo.fields['product'], 'Flutter_Tools'); expect(crashInfo.fields?['product'], 'Flutter_Tools');
expect(crashInfo.fields['version'], 'test-version'); expect(crashInfo.fields?['version'], 'test-version');
expect(crashInfo.fields['osName'], 'linux'); expect(crashInfo.fields?['osName'], 'linux');
expect(crashInfo.fields['osVersion'], 'Linux'); expect(crashInfo.fields?['osVersion'], 'Linux');
expect(crashInfo.fields['type'], 'DartError'); expect(crashInfo.fields?['type'], 'DartError');
expect(crashInfo.fields['error_runtime_type'], 'StateError'); expect(crashInfo.fields?['error_runtime_type'], 'StateError');
expect(crashInfo.fields['error_message'], 'Bad state: Test bad state error'); expect(crashInfo.fields?['error_message'], 'Bad state: Test bad state error');
expect(crashInfo.fields['comments'], 'crash'); expect(crashInfo.fields?['comments'], 'crash');
expect(logger.traceText, contains('Sending crash report to Google.')); expect(logger.traceText, contains('Sending crash report to Google.'));
expect(logger.traceText, contains('Crash report sent (report ID: test-report-id)')); expect(logger.traceText, contains('Crash report sent (report ID: test-report-id)'));
...@@ -112,7 +112,7 @@ void main() { ...@@ -112,7 +112,7 @@ void main() {
await crashReportSender.sendReport( await crashReportSender.sendReport(
error: StateError('Test bad state error'), error: StateError('Test bad state error'),
stackTrace: null, stackTrace: stackTrace,
getFlutterVersion: () => 'test-version', getFlutterVersion: () => 'test-version',
command: 'crash', command: 'crash',
); );
...@@ -138,7 +138,7 @@ void main() { ...@@ -138,7 +138,7 @@ void main() {
await crashReportSender.sendReport( await crashReportSender.sendReport(
error: StateError('Test bad state error'), error: StateError('Test bad state error'),
stackTrace: null, stackTrace: stackTrace,
getFlutterVersion: () => 'test-version', getFlutterVersion: () => 'test-version',
command: 'crash', command: 'crash',
); );
...@@ -157,7 +157,7 @@ void main() { ...@@ -157,7 +157,7 @@ void main() {
await crashReportSender.sendReport( await crashReportSender.sendReport(
error: StateError('Test bad state error'), error: StateError('Test bad state error'),
stackTrace: null, stackTrace: stackTrace,
getFlutterVersion: () => 'test-version', getFlutterVersion: () => 'test-version',
command: 'crash', command: 'crash',
); );
...@@ -176,7 +176,7 @@ void main() { ...@@ -176,7 +176,7 @@ void main() {
await crashReportSender.sendReport( await crashReportSender.sendReport(
error: StateError('Test bad state error'), error: StateError('Test bad state error'),
stackTrace: null, stackTrace: stackTrace,
getFlutterVersion: () => 'test-version', getFlutterVersion: () => 'test-version',
command: 'crash', command: 'crash',
); );
...@@ -195,7 +195,7 @@ void main() { ...@@ -195,7 +195,7 @@ void main() {
await crashReportSender.sendReport( await crashReportSender.sendReport(
error: ClientException('Test bad state error'), error: ClientException('Test bad state error'),
stackTrace: null, stackTrace: stackTrace,
getFlutterVersion: () => 'test-version', getFlutterVersion: () => 'test-version',
command: 'crash', command: 'crash',
); );
...@@ -216,28 +216,28 @@ void main() { ...@@ -216,28 +216,28 @@ void main() {
await crashReportSender.sendReport( await crashReportSender.sendReport(
error: StateError('Test bad state error'), error: StateError('Test bad state error'),
stackTrace: null, stackTrace: stackTrace,
getFlutterVersion: () => 'test-version', getFlutterVersion: () => 'test-version',
command: 'crash', command: 'crash',
); );
await crashReportSender.sendReport( await crashReportSender.sendReport(
error: StateError('Test bad state error'), error: StateError('Test bad state error'),
stackTrace: null, stackTrace: stackTrace,
getFlutterVersion: () => 'test-version', getFlutterVersion: () => 'test-version',
command: 'crash', command: 'crash',
); );
await crashReportSender.sendReport( await crashReportSender.sendReport(
error: StateError('Test bad state error'), error: StateError('Test bad state error'),
stackTrace: null, stackTrace: stackTrace,
getFlutterVersion: () => 'test-version', getFlutterVersion: () => 'test-version',
command: 'crash', command: 'crash',
); );
await crashReportSender.sendReport( await crashReportSender.sendReport(
error: StateError('Test bad state error'), error: StateError('Test bad state error'),
stackTrace: null, stackTrace: stackTrace,
getFlutterVersion: () => 'test-version', getFlutterVersion: () => 'test-version',
command: 'crash', command: 'crash',
); );
...@@ -247,8 +247,8 @@ void main() { ...@@ -247,8 +247,8 @@ void main() {
}); });
testWithoutContext('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;
final MockClient mockClient = MockClient((Request request) async { final MockClient mockClient = MockClient((Request request) async {
method = request.method; method = request.method;
...@@ -270,7 +270,7 @@ void main() { ...@@ -270,7 +270,7 @@ void main() {
await crashReportSender.sendReport( await crashReportSender.sendReport(
error: StateError('Test bad state error'), error: StateError('Test bad state error'),
stackTrace: null, stackTrace: stackTrace,
getFlutterVersion: () => '[user-branch]/v1.2.3', getFlutterVersion: () => '[user-branch]/v1.2.3',
command: 'crash', command: 'crash',
); );
...@@ -283,7 +283,7 @@ void main() { ...@@ -283,7 +283,7 @@ void main() {
}); });
testWithoutContext('can override base URL', () async { testWithoutContext('can override base URL', () async {
Uri uri; Uri? uri;
final MockClient mockClient = 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);
...@@ -307,7 +307,7 @@ void main() { ...@@ -307,7 +307,7 @@ void main() {
await crashReportSender.sendReport( await crashReportSender.sendReport(
error: StateError('Test bad state error'), error: StateError('Test bad state error'),
stackTrace: null, stackTrace: stackTrace,
getFlutterVersion: () => 'test-version', getFlutterVersion: () => 'test-version',
command: 'crash', command: 'crash',
); );
...@@ -329,9 +329,9 @@ void main() { ...@@ -329,9 +329,9 @@ void main() {
} }
class RequestInfo { class RequestInfo {
String method; String? method;
Uri uri; Uri? uri;
Map<String, String> fields; Map<String, String>? fields;
} }
class MockCrashReportSender extends MockClient { class MockCrashReportSender extends MockClient {
...@@ -341,21 +341,20 @@ class MockCrashReportSender extends MockClient { ...@@ -341,21 +341,20 @@ class MockCrashReportSender extends MockClient {
crashInfo.uri = request.url; crashInfo.uri = request.url;
// A very ad-hoc multipart request parser. Good enough for this test. // A very ad-hoc multipart request parser. Good enough for this test.
String boundary = request.headers['Content-Type']; String? boundary = request.headers['Content-Type'];
boundary = boundary.substring(boundary.indexOf('boundary=') + 9); boundary = boundary?.substring(boundary.indexOf('boundary=') + 9);
crashInfo.fields = Map<String, String>.fromIterable( crashInfo.fields = Map<String, String>.fromIterable(
utf8.decode(request.bodyBytes) utf8.decode(request.bodyBytes)
.split('--$boundary') .split('--$boundary')
.map<List<String>>((String part) { .map<List<String>?>((String part) {
final Match nameMatch = RegExp(r'name="(.*)"').firstMatch(part); final Match? nameMatch = RegExp(r'name="(.*)"').firstMatch(part);
if (nameMatch == null) { if (nameMatch == null) {
return null; return null;
} }
final String name = nameMatch[1]; final String name = nameMatch[1]!;
final String value = part.split('\n').skip(2).join('\n').trim(); final String value = part.split('\n').skip(2).join('\n').trim();
return <String>[name, value]; return <String>[name, value];
}) }).whereType<List<String>>(),
.where((List<String> pair) => pair != null),
key: (dynamic key) { key: (dynamic key) {
final List<String> pair = key as List<String>; final List<String> pair = key as List<String>;
return pair[0]; return pair[0];
...@@ -380,13 +379,3 @@ class CrashingCrashReportSender extends MockClient { ...@@ -380,13 +379,3 @@ class CrashingCrashReportSender extends MockClient {
throw exception; throw exception;
}); });
} }
/// A DoctorValidatorsProvider that overrides the default validators without
/// overriding the doctor.
class FakeDoctorValidatorsProvider implements DoctorValidatorsProvider {
@override
List<DoctorValidator> get validators => <DoctorValidator>[];
@override
List<Workflow> get workflows => <Workflow>[];
}
...@@ -15,7 +15,7 @@ import 'package:flutter_tools/src/base/io.dart' as io; ...@@ -15,7 +15,7 @@ import 'package:flutter_tools/src/base/io.dart' as io;
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/user_messages.dart'; import 'package:flutter_tools/src/base/user_messages.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/globals_null_migrated.dart' as globals;
import 'package:flutter_tools/src/reporting/crash_reporting.dart'; import 'package:flutter_tools/src/reporting/crash_reporting.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/runner/flutter_command.dart';
......
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