blob: 279eb9c9661b0f7c237fe7dc5a3ee7d267c9f656 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/github_template.dart';
import '../src/common.dart';
import '../src/context.dart';
import '../src/fake_http_client.dart';
const String _kShortURL = 'https://www.example.com/short';
void main() {
BufferLogger logger;
FileSystem fs;
setUp(() {
logger = BufferLogger.test();
fs = MemoryFileSystem.test();
});
group('GitHub template creator', () {
testWithoutContext('similar issues URL', () {
expect(
GitHubTemplateCreator.toolCrashSimilarIssuesURL('this is a 100% error'),
'https://github.com/flutter/flutter/issues?q=is%3Aissue+this+is+a+100%25+error',
);
});
group('sanitized error message', () {
testWithoutContext('ProcessException', () {
expect(
GitHubTemplateCreator.sanitizedCrashException(
const ProcessException('cd', <String>['path/to/something'])
),
'ProcessException: Command: cd, OS error code: 0',
);
expect(
GitHubTemplateCreator.sanitizedCrashException(
const ProcessException('cd', <String>['path/to/something'], 'message')
),
'ProcessException: message Command: cd, OS error code: 0',
);
expect(
GitHubTemplateCreator.sanitizedCrashException(
const ProcessException('cd', <String>['path/to/something'], 'message', -19)
),
'ProcessException: message Command: cd, OS error code: -19',
);
});
testWithoutContext('FileSystemException', () {
expect(
GitHubTemplateCreator.sanitizedCrashException(
const FileSystemException('delete failed', 'path/to/something')
),
'FileSystemException: delete failed, null',
);
expect(
GitHubTemplateCreator.sanitizedCrashException(
const FileSystemException('delete failed', 'path/to/something', OSError('message', -19))
),
'FileSystemException: delete failed, OS Error: message, errno = -19',
);
});
testWithoutContext('SocketException', () {
expect(
GitHubTemplateCreator.sanitizedCrashException(
SocketException(
'message',
osError: const OSError('message', -19),
address: InternetAddress.anyIPv6,
port: 2000
)
),
'SocketException: message, OS Error: message, errno = -19',
);
});
testWithoutContext('DevFSException', () {
final StackTrace stackTrace = StackTrace.fromString('''
#0 _File.open.<anonymous closure> (dart:io/file_impl.dart:366:9)
#1 _rootRunUnary (dart:async/zone.dart:1141:38)''');
expect(
GitHubTemplateCreator.sanitizedCrashException(
DevFSException('message', ArgumentError('argument error message'), stackTrace)
),
'DevFSException: message',
);
});
testWithoutContext('ArgumentError', () {
expect(
GitHubTemplateCreator.sanitizedCrashException(
ArgumentError('argument error message')
),
'ArgumentError: Invalid argument(s): argument error message',
);
});
testWithoutContext('Error', () {
expect(
GitHubTemplateCreator.sanitizedCrashException(
FakeError()
),
'FakeError: (#0 _File.open.<anonymous closure> (dart:io/file_impl.dart:366:9))',
);
});
testWithoutContext('String', () {
expect(
GitHubTemplateCreator.sanitizedCrashException(
'May have non-tool-internal info, very long string, 0b8abb4724aa590dd0f429683339b' // ignore: missing_whitespace_between_adjacent_strings
'24aa590dd0f429683339b1e045a1594d0b8abb4724aa590dd0f429683339b1e045a1594d0b8abb'
'24aa590dd0f429683339b1e045a1594d0b8abb4724aa590dd0f429683339b1e045a1594d0b8abb'
'24aa590dd0f429683339b1e045a1594d0b8abb4724aa590dd0f429683339b1e045a1594d0b8abb'
'24aa590dd0f429683339b1e045a1594d0b8abb4724aa590dd0f429683339b1e045a1594d0b8abb'
'24aa590dd0f429683339b1e045a1594d0b8abb4724aa590dd0f429683339b1e045a1594d0b8abb'
'24aa590dd0f429683339b1e045a1594d0b8abb4724aa590dd0f429683339b1e045a1594d0b8abb'
'24aa590dd0f429683339b1e045a1594d0b8abb4724aa590dd0f429683339b1e045a1594d0b8abb'
'24aa590dd0f429683339b1e045a1594d0b8abb4724aa590dd0f429683339b1e045a1594d0b8abb'
'24aa590dd0f429683339b1e045a1594d0b8abb4724aa590dd0f429683339b1e045a1594d0b8abb'
'24aa590dd0f429683339b1e045a1594d0b8abb4724aa590dd0f429683339b1e045a1594d0b8abb'
'24aa590dd0f429683339b1e045a1594d0b8abb4724aa590dd0f429683339b1e045a1594d0b8abb'
'24aa590dd0f429683339b1e045a1594d0b8abb4724aa590dd0f429683339b1e045a1594d0b8abb'
),
'String: <1,016 characters>',
);
});
testWithoutContext('Exception', () {
expect(
GitHubTemplateCreator.sanitizedCrashException(
Exception('May have non-tool-internal info')
),
'_Exception',
);
});
});
group('new issue template URL', () {
StackTrace stackTrace;
Error error;
const String command = 'flutter test';
const String doctorText = ' [✓] Flutter (Channel report';
setUp(() async {
stackTrace = StackTrace.fromString('trace');
error = ArgumentError('argument error message');
});
testUsingContext('shortened', () async {
final GitHubTemplateCreator creator = GitHubTemplateCreator(
fileSystem: fs,
logger: logger,
client: FakeHttpClient.list(<FakeRequest>[
FakeRequest(Uri.parse('https://git.io'), method: HttpMethod.post, response: const FakeResponse(
statusCode: 201,
headers: <String, List<String>>{
HttpHeaders.locationHeader: <String>[_kShortURL],
}
))
]),
flutterProjectFactory: FlutterProjectFactory(
fileSystem: fs,
logger: logger,
),
);
expect(
await creator.toolCrashIssueTemplateGitHubURL(command, error, stackTrace, doctorText),
_kShortURL
);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('with network failure', () async {
final GitHubTemplateCreator creator = GitHubTemplateCreator(
fileSystem: fs,
logger: logger,
client: FakeHttpClient.list(<FakeRequest>[
FakeRequest(Uri.parse('https://git.io'), method: HttpMethod.post, response: const FakeResponse(
statusCode: 500,
))
]),
flutterProjectFactory: FlutterProjectFactory(
fileSystem: fs,
logger: logger,
),
);
expect(
await creator.toolCrashIssueTemplateGitHubURL(command, error, stackTrace, doctorText),
'https://github.com/flutter/flutter/issues/new?title=%5Btool_crash%5D+ArgumentError%3A+'
'Invalid+argument%28s%29%3A+argument+error+message&body=%23%23+Command%0A%60%60%60%0A'
'flutter+test%0A%60%60%60%0A%0A%23%23+Steps+to+Reproduce%0A1.+...%0A2.+...%0A3.+...%0'
'A%0A%23%23+Logs%0AArgumentError%3A+Invalid+argument%28s%29%3A+argument+error+message'
'%0A%60%60%60%0Atrace%0A%60%60%60%0A%60%60%60%0A+%5B%E2%9C%93%5D+Flutter+%28Channel+r'
'eport%0A%60%60%60%0A%0A%23%23+Flutter+Application+Metadata%0ANo+pubspec+in+working+d'
'irectory.%0A&labels=tool%2Csevere%3A+crash'
);
expect(logger.traceText, contains('Failed to shorten GitHub template URL'));
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('app metadata', () async {
final GitHubTemplateCreator creator = GitHubTemplateCreator(
fileSystem: fs,
logger: logger,
client: FakeHttpClient.any(),
flutterProjectFactory: FlutterProjectFactory(
fileSystem: fs,
logger: logger,
),
);
final Directory projectDirectory = fs.currentDirectory;
projectDirectory
.childFile('pubspec.yaml')
.writeAsStringSync('''
name: failing_app
version: 2.0.1+100
flutter:
uses-material-design: true
module:
androidX: true
androidPackage: com.example.failing.android
iosBundleIdentifier: com.example.failing.ios
''');
final File pluginsFile = projectDirectory.childFile('.flutter-plugins');
pluginsFile
.writeAsStringSync('''
camera=/fake/pub.dartlang.org/camera-0.5.7+2/
device_info=/fake/pub.dartlang.org/pub.dartlang.org/device_info-0.4.1+4/
''');
final File metadataFile = projectDirectory.childFile('.metadata');
metadataFile
.writeAsStringSync('''
version:
revision: 0b8abb4724aa590dd0f429683339b1e045a1594d
channel: stable
project_type: app
''');
final String actualURL = await creator.toolCrashIssueTemplateGitHubURL(command, error, stackTrace, doctorText);
final String actualBody = Uri.parse(actualURL).queryParameters['body'];
const String expectedBody = '''
## Command
```
flutter test
```
## Steps to Reproduce
1. ...
2. ...
3. ...
## Logs
ArgumentError: Invalid argument(s): argument error message
```
trace
```
```
[✓] Flutter (Channel report
```
## Flutter Application Metadata
**Type**: app
**Version**: 2.0.1+100
**Material**: true
**Android X**: true
**Module**: true
**Plugin**: false
**Android package**: com.example.failing.android
**iOS bundle identifier**: com.example.failing.ios
**Creation channel**: stable
**Creation framework version**: 0b8abb4724aa590dd0f429683339b1e045a1594d
### Plugins
camera-0.5.7+2
device_info-0.4.1+4
''';
expect(actualBody, expectedBody);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
});
});
}
class FakeError extends Error {
@override
StackTrace get stackTrace => StackTrace.fromString('''
#0 _File.open.<anonymous closure> (dart:io/file_impl.dart:366:9)
#1 _rootRunUnary (dart:async/zone.dart:1141:38)''');
}