| // 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. |
| |
| 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'; |
| |
| void main() { |
| late BufferLogger logger; |
| late 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', () { |
| late StackTrace stackTrace; |
| late 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('shows GitHub issue URL', () async { |
| final GitHubTemplateCreator creator = GitHubTemplateCreator( |
| fileSystem: fs, |
| logger: logger, |
| 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' |
| ); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| |
| testUsingContext('app metadata', () async { |
| final GitHubTemplateCreator creator = GitHubTemplateCreator( |
| fileSystem: fs, |
| logger: logger, |
| 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)'''); |
| |
| @override |
| String toString() => 'PII to ignore'; |
| } |