From ce40fbaf518c60766207de928e0b3237f91d97fc Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Fri, 27 Mar 2020 16:21:45 -0700 Subject: [PATCH] Include metadata in GitHub crash template (#53118) --- packages/flutter_tools/lib/runner.dart | 9 +- .../lib/src/commands/create.dart | 107 +++++------------- .../lib/src/flutter_project_metadata.dart | 99 ++++++++++++++++ packages/flutter_tools/lib/src/globals.dart | 2 + packages/flutter_tools/lib/src/project.dart | 8 +- .../lib/src/reporting/github_template.dart | 38 ++++--- .../flutter_project_metadata_test.dart | 75 ++++++++++++ .../general.shard/github_template_test.dart | 79 +++++++++---- 8 files changed, 299 insertions(+), 118 deletions(-) create mode 100644 packages/flutter_tools/lib/src/flutter_project_metadata.dart create mode 100644 packages/flutter_tools/test/general.shard/flutter_project_metadata_test.dart diff --git a/packages/flutter_tools/lib/runner.dart b/packages/flutter_tools/lib/runner.dart index 4ab1ff6dd64..5342f24d021 100644 --- a/packages/flutter_tools/lib/runner.dart +++ b/packages/flutter_tools/lib/runner.dart @@ -14,6 +14,7 @@ import 'src/base/context.dart'; import 'src/base/file_system.dart'; import 'src/base/io.dart'; import 'src/base/logger.dart'; +import 'src/base/net.dart'; import 'src/base/process.dart'; import 'src/context_runner.dart'; import 'src/doctor.dart'; @@ -157,7 +158,13 @@ Future _informUserOfCrash(List args, dynamic error, StackTrace sta globals.printError('A crash report has been written to ${file.path}.'); globals.printStatus('This crash may already be reported. Check GitHub for similar crashes.', emphasis: true); - final GitHubTemplateCreator gitHubTemplateCreator = context.get() ?? GitHubTemplateCreator(); + final HttpClientFactory clientFactory = context.get(); + final GitHubTemplateCreator gitHubTemplateCreator = context.get() ?? GitHubTemplateCreator( + fileSystem: globals.fs, + logger: globals.logger, + flutterProjectFactory: globals.projectFactory, + client: clientFactory != null ? clientFactory() : HttpClient(), + ); final String similarIssuesURL = await gitHubTemplateCreator.toolCrashSimilarIssuesGitHubURL(errorString); globals.printStatus('$similarIssuesURL\n', wrap: false); globals.printStatus('To report your crash to the Flutter team, first read the guide to filing a bug.', emphasis: true); diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart index 81e117a4e2b..2c73de40529 100644 --- a/packages/flutter_tools/lib/src/commands/create.dart +++ b/packages/flutter_tools/lib/src/commands/create.dart @@ -5,7 +5,6 @@ import 'dart:async'; import 'package:meta/meta.dart'; -import 'package:yaml/yaml.dart' as yaml; import '../android/android.dart' as android; import '../android/android_sdk.dart' as android_sdk; @@ -21,38 +20,13 @@ import '../convert.dart'; import '../dart/pub.dart'; import '../doctor.dart'; import '../features.dart'; +import '../flutter_project_metadata.dart'; import '../globals.dart' as globals; import '../project.dart'; import '../reporting/reporting.dart'; import '../runner/flutter_command.dart'; import '../template.dart'; -enum _ProjectType { - /// This is the default project with the user-managed host code. - /// It is different than the "module" template in that it exposes and doesn't - /// manage the platform code. - app, - /// The is a project that has managed platform host code. It is an application with - /// ephemeral .ios and .android directories that can be updated automatically. - module, - /// This is a Flutter Dart package project. It doesn't have any native - /// components, only Dart. - package, - /// This is a native plugin project. - plugin, -} - -_ProjectType _stringToProjectType(String value) { - _ProjectType result; - for (final _ProjectType type in _ProjectType.values) { - if (value == getEnumName(type)) { - result = type; - break; - } - } - return result; -} - class CreateCommand extends FlutterCommand { CreateCommand() { argParser.addFlag('pub', @@ -74,17 +48,17 @@ class CreateCommand extends FlutterCommand { argParser.addOption( 'template', abbr: 't', - allowed: _ProjectType.values.map((_ProjectType type) => getEnumName(type)), + allowed: FlutterProjectType.values.map((FlutterProjectType type) => type.name), help: 'Specify the type of project to create.', valueHelp: 'type', allowedHelp: { - getEnumName(_ProjectType.app): '(default) Generate a Flutter application.', - getEnumName(_ProjectType.package): 'Generate a shareable Flutter project containing modular ' + FlutterProjectType.app.name: '(default) Generate a Flutter application.', + FlutterProjectType.package.name: 'Generate a shareable Flutter project containing modular ' 'Dart code.', - getEnumName(_ProjectType.plugin): 'Generate a shareable Flutter project containing an API ' + FlutterProjectType.plugin.name: 'Generate a shareable Flutter project containing an API ' 'in Dart code with a platform-specific implementation for Android, for iOS code, or ' 'for both.', - getEnumName(_ProjectType.module): 'Generate a project to add a Flutter module to an ' + FlutterProjectType.module.name: 'Generate a project to add a Flutter module to an ' 'existing Android or iOS application.', }, defaultsTo: null, @@ -180,47 +154,24 @@ class CreateCommand extends FlutterCommand { // If it has an ios dir and an ios/Flutter dir, it's a legacy app // Otherwise, we don't presume to know what type of project it could be, since // many of the files could be missing, and we can't really tell definitively. - _ProjectType _determineTemplateType(Directory projectDir) { - yaml.YamlMap loadMetadata(Directory projectDir) { - if (!projectDir.existsSync()) { - return null; - } - final File metadataFile = globals.fs.file(globals.fs.path.join(projectDir.absolute.path, '.metadata')); - if (!metadataFile.existsSync()) { - return null; - } - final dynamic metadataYaml = yaml.loadYaml(metadataFile.readAsStringSync()); - if (metadataYaml is yaml.YamlMap) { - return metadataYaml; - } else { - throwToolExit('pubspec.yaml is malformed.'); - return null; - } + FlutterProjectType _determineTemplateType(Directory projectDir) { + final File metadataFile = globals.fs.file(globals.fs.path.join(projectDir.absolute.path, '.metadata')); + final FlutterProjectMetadata projectMetadata = FlutterProjectMetadata(metadataFile, globals.logger); + if (projectMetadata.projectType != null) { + return projectMetadata.projectType; } bool exists(List path) { return globals.fs.directory(globals.fs.path.joinAll([projectDir.absolute.path, ...path])).existsSync(); } - // If it exists, the project type in the metadata is definitive. - final yaml.YamlMap metadata = loadMetadata(projectDir); - if (metadata != null && metadata['project_type'] != null) { - final dynamic projectType = metadata['project_type']; - if (projectType is String) { - return _stringToProjectType(projectType); - } else { - throwToolExit('.metadata is malformed.'); - return null; - } - } - // There either wasn't any metadata, or it didn't contain the project type, // so try and figure out what type of project it is from the existing // directory structure. if (exists(['android', 'app']) || exists(['ios', 'Runner']) || exists(['ios', 'Flutter'])) { - return _ProjectType.app; + return FlutterProjectType.app; } // Since we can't really be definitive on nearly-empty directories, err on // the side of prudence and just say we don't know. @@ -277,12 +228,12 @@ class CreateCommand extends FlutterCommand { } } - _ProjectType _getProjectType(Directory projectDir) { - _ProjectType template; - _ProjectType detectedProjectType; + FlutterProjectType _getProjectType(Directory projectDir) { + FlutterProjectType template; + FlutterProjectType detectedProjectType; final bool metadataExists = projectDir.absolute.childFile('.metadata').existsSync(); if (argResults['template'] != null) { - template = _stringToProjectType(stringArg('template')); + template = stringToProjectType(stringArg('template')); } else { // If the project directory exists and isn't empty, then try to determine the template // type from the project directory. @@ -297,12 +248,12 @@ class CreateCommand extends FlutterCommand { } } } - template ??= detectedProjectType ?? _ProjectType.app; + template ??= detectedProjectType ?? FlutterProjectType.app; if (detectedProjectType != null && template != detectedProjectType && metadataExists) { // We can only be definitive that this is the wrong type if the .metadata file // exists and contains a type that doesn't match. - throwToolExit("The requested template type '${getEnumName(template)}' doesn't match the " - "existing template type of '${getEnumName(detectedProjectType)}'."); + throwToolExit("The requested template type '${template.name}' doesn't match the " + "existing template type of '${detectedProjectType.name}'."); } return template; } @@ -356,18 +307,18 @@ class CreateCommand extends FlutterCommand { String sampleCode; if (argResults['sample'] != null) { if (argResults['template'] != null && - _stringToProjectType(stringArg('template') ?? 'app') != _ProjectType.app) { + stringToProjectType(stringArg('template') ?? 'app') != FlutterProjectType.app) { throwToolExit('Cannot specify --sample with a project type other than ' - '"${getEnumName(_ProjectType.app)}"'); + '"${FlutterProjectType.app.name}"'); } // Fetch the sample from the server. sampleCode = await _fetchSampleFromServer(stringArg('sample')); } - final _ProjectType template = _getProjectType(projectDir); - final bool generateModule = template == _ProjectType.module; - final bool generatePlugin = template == _ProjectType.plugin; - final bool generatePackage = template == _ProjectType.package; + final FlutterProjectType template = _getProjectType(projectDir); + final bool generateModule = template == FlutterProjectType.module; + final bool generatePlugin = template == FlutterProjectType.plugin; + final bool generatePackage = template == FlutterProjectType.package; String organization = stringArg('org'); if (!argResults.wasParsed('org')) { @@ -424,16 +375,16 @@ class CreateCommand extends FlutterCommand { final Directory relativeDir = globals.fs.directory(projectDirPath); int generatedFileCount = 0; switch (template) { - case _ProjectType.app: + case FlutterProjectType.app: generatedFileCount += await _generateApp(relativeDir, templateContext, overwrite: overwrite); break; - case _ProjectType.module: + case FlutterProjectType.module: generatedFileCount += await _generateModule(relativeDir, templateContext, overwrite: overwrite); break; - case _ProjectType.package: + case FlutterProjectType.package: generatedFileCount += await _generatePackage(relativeDir, templateContext, overwrite: overwrite); break; - case _ProjectType.plugin: + case FlutterProjectType.plugin: generatedFileCount += await _generatePlugin(relativeDir, templateContext, overwrite: overwrite); break; } diff --git a/packages/flutter_tools/lib/src/flutter_project_metadata.dart b/packages/flutter_tools/lib/src/flutter_project_metadata.dart new file mode 100644 index 00000000000..a6d94143649 --- /dev/null +++ b/packages/flutter_tools/lib/src/flutter_project_metadata.dart @@ -0,0 +1,99 @@ +// 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:yaml/yaml.dart'; + +import 'base/file_system.dart'; +import 'base/logger.dart'; +import 'base/utils.dart'; + +enum FlutterProjectType { + /// This is the default project with the user-managed host code. + /// It is different than the "module" template in that it exposes and doesn't + /// manage the platform code. + app, + /// The is a project that has managed platform host code. It is an application with + /// ephemeral .ios and .android directories that can be updated automatically. + module, + /// This is a Flutter Dart package project. It doesn't have any native + /// components, only Dart. + package, + /// This is a native plugin project. + plugin, +} + +extension FlutterProjectTypeExtension on FlutterProjectType { + String get name => getEnumName(this); +} + +FlutterProjectType stringToProjectType(String value) { + FlutterProjectType result; + for (final FlutterProjectType type in FlutterProjectType.values) { + if (value == type.name) { + result = type; + break; + } + } + return result; +} + +/// A wrapper around the `.metadata` file. +class FlutterProjectMetadata { + FlutterProjectMetadata( + File metadataFile, + Logger logger, + ) : _metadataFile = metadataFile, + _logger = logger; + + final File _metadataFile; + final Logger _logger; + + String get versionChannel => _versionValue('channel'); + String get versionRevision => _versionValue('revision'); + + FlutterProjectType get projectType { + final dynamic projectTypeYaml = _metadataValue('project_type'); + if (projectTypeYaml is String) { + return stringToProjectType(projectTypeYaml); + } else { + _logger.printTrace('.metadata project_type version is malformed.'); + return null; + } + } + + YamlMap _versionYaml; + String _versionValue(String key) { + if (_versionYaml == null) { + final dynamic versionYaml = _metadataValue('version'); + if (versionYaml is YamlMap) { + _versionYaml = versionYaml; + } else { + _logger.printTrace('.metadata version is malformed.'); + return null; + } + } + if (_versionYaml != null && _versionYaml.containsKey(key) && _versionYaml[key] is String) { + return _versionYaml[key] as String; + } + return null; + } + + YamlMap _metadataYaml; + dynamic _metadataValue(String key) { + if (_metadataYaml == null) { + if (!_metadataFile.existsSync()) { + return null; + } + final dynamic metadataYaml = loadYaml(_metadataFile.readAsStringSync()); + if (metadataYaml is YamlMap) { + _metadataYaml = metadataYaml; + } else { + _logger.printTrace('.metadata is malformed.'); + return null; + } + } + + return _metadataYaml[key]; + } +} diff --git a/packages/flutter_tools/lib/src/globals.dart b/packages/flutter_tools/lib/src/globals.dart index 4d7779302b2..69c83113a4b 100644 --- a/packages/flutter_tools/lib/src/globals.dart +++ b/packages/flutter_tools/lib/src/globals.dart @@ -30,6 +30,7 @@ import 'ios/xcodeproj.dart'; import 'macos/cocoapods.dart'; import 'macos/xcode.dart'; import 'persistent_tool_state.dart'; +import 'project.dart'; import 'reporting/reporting.dart'; import 'version.dart'; import 'web/chrome.dart'; @@ -42,6 +43,7 @@ Logger get logger => context.get(); OperatingSystemUtils get os => context.get(); PersistentToolState get persistentToolState => PersistentToolState.instance; Usage get flutterUsage => context.get(); +FlutterProjectFactory get projectFactory => context.get() ?? FlutterProjectFactory(); const FileSystem _kLocalFs = LocalFileSystem(); diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index 2926a182c6f..4efd7c57b09 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart @@ -11,7 +11,6 @@ import 'package:yaml/yaml.dart'; import 'android/gradle_utils.dart' as gradle; import 'artifacts.dart'; import 'base/common.dart'; -import 'base/context.dart'; import 'base/file_system.dart'; import 'build_info.dart'; import 'bundle.dart' as bundle; @@ -24,8 +23,6 @@ import 'platform_plugins.dart'; import 'plugins.dart'; import 'template.dart'; -FlutterProjectFactory get projectFactory => context.get() ?? FlutterProjectFactory(); - class FlutterProjectFactory { FlutterProjectFactory(); @@ -69,7 +66,7 @@ class FlutterProject { /// Returns a [FlutterProject] view of the given directory or a ToolExit error, /// if `pubspec.yaml` or `example/pubspec.yaml` is invalid. - static FlutterProject fromDirectory(Directory directory) => projectFactory.fromDirectory(directory); + static FlutterProject fromDirectory(Directory directory) => globals.projectFactory.fromDirectory(directory); /// Returns a [FlutterProject] view of the current directory or a ToolExit error, /// if `pubspec.yaml` or `example/pubspec.yaml` is invalid. @@ -145,6 +142,9 @@ class FlutterProject { /// The `.packages` file of this project. File get packagesFile => directory.childFile('.packages'); + /// The `.metadata` file of this project. + File get metadataFile => directory.childFile('.metadata'); + /// The `.flutter-plugins` file of this project. File get flutterPluginsFile => directory.childFile('.flutter-plugins'); diff --git a/packages/flutter_tools/lib/src/reporting/github_template.dart b/packages/flutter_tools/lib/src/reporting/github_template.dart index 5d958b765db..76a1bd3c00b 100644 --- a/packages/flutter_tools/lib/src/reporting/github_template.dart +++ b/packages/flutter_tools/lib/src/reporting/github_template.dart @@ -5,23 +5,31 @@ import 'dart:async'; import 'package:file/file.dart'; +import 'package:meta/meta.dart'; -import '../base/context.dart'; import '../base/file_system.dart'; import '../base/io.dart'; -import '../base/net.dart'; +import '../base/logger.dart'; import '../convert.dart'; import '../flutter_manifest.dart'; -import '../globals.dart' as globals; +import '../flutter_project_metadata.dart'; import '../project.dart'; /// Provide suggested GitHub issue templates to user when Flutter encounters an error. class GitHubTemplateCreator { - GitHubTemplateCreator() : - _client = (context.get() == null) - ? HttpClient() - : context.get()(); + GitHubTemplateCreator({ + @required FileSystem fileSystem, + @required Logger logger, + @required FlutterProjectFactory flutterProjectFactory, + @required HttpClient client, + }) : _fileSystem = fileSystem, + _logger = logger, + _flutterProjectFactory = flutterProjectFactory, + _client = client; + final FileSystem _fileSystem; + final Logger _logger; + final FlutterProjectFactory _flutterProjectFactory; final HttpClient _client; Future toolCrashSimilarIssuesGitHubURL(String errorString) async { @@ -76,7 +84,7 @@ ${_projectMetadataInformation()} String _projectMetadataInformation() { FlutterProject project; try { - project = FlutterProject.current(); + project = _flutterProjectFactory.fromDirectory(_fileSystem.currentDirectory); } on Exception catch (exception) { // pubspec may be malformed. return exception.toString(); @@ -86,14 +94,18 @@ ${_projectMetadataInformation()} if (project == null || manifest == null || manifest.isEmpty) { return 'No pubspec in working directory.'; } + final FlutterProjectMetadata metadata = FlutterProjectMetadata(project.metadataFile, _logger); final StringBuffer description = StringBuffer() + ..writeln('**Type**: ${metadata.projectType?.name}') ..writeln('**Version**: ${manifest.appVersion}') ..writeln('**Material**: ${manifest.usesMaterialDesign}') ..writeln('**Android X**: ${manifest.usesAndroidX}') ..writeln('**Module**: ${manifest.isModule}') ..writeln('**Plugin**: ${manifest.isPlugin}') ..writeln('**Android package**: ${manifest.androidPackage}') - ..writeln('**iOS bundle identifier**: ${manifest.iosBundleIdentifier}'); + ..writeln('**iOS bundle identifier**: ${manifest.iosBundleIdentifier}') + ..writeln('**Creation channel**: ${metadata.versionChannel}') + ..writeln('**Creation framework version**: ${metadata.versionRevision}'); final File file = project.flutterPluginsFile; if (file.existsSync()) { @@ -107,7 +119,7 @@ ${_projectMetadataInformation()} } // Write the last part of the path, which includes the plugin name and version. // Example: camera-0.5.7+2 - final List pathParts = globals.fs.path.split(pluginParts[1]); + final List pathParts = _fileSystem.path.split(pluginParts[1]); description.writeln(pathParts.isEmpty ? pluginParts.first : pathParts.last); } } @@ -124,7 +136,7 @@ ${_projectMetadataInformation()} Future _shortURL(String fullURL) async { String url; try { - globals.printTrace('Attempting git.io shortener: $fullURL'); + _logger.printTrace('Attempting git.io shortener: $fullURL'); final List bodyBytes = utf8.encode('url=${Uri.encodeQueryComponent(fullURL)}'); final HttpClientRequest request = await _client.postUrl(Uri.parse('https://git.io')); request.headers.set(HttpHeaders.contentLengthHeader, bodyBytes.length.toString()); @@ -134,10 +146,10 @@ ${_projectMetadataInformation()} if (response.statusCode == 201) { url = response.headers[HttpHeaders.locationHeader]?.first; } else { - globals.printTrace('Failed to shorten GitHub template URL. Server responded with HTTP status code ${response.statusCode}'); + _logger.printTrace('Failed to shorten GitHub template URL. Server responded with HTTP status code ${response.statusCode}'); } } on Exception catch (sendError) { - globals.printTrace('Failed to shorten GitHub template URL: $sendError'); + _logger.printTrace('Failed to shorten GitHub template URL: $sendError'); } return url ?? fullURL; diff --git a/packages/flutter_tools/test/general.shard/flutter_project_metadata_test.dart b/packages/flutter_tools/test/general.shard/flutter_project_metadata_test.dart new file mode 100644 index 00000000000..e08e93c8499 --- /dev/null +++ b/packages/flutter_tools/test/general.shard/flutter_project_metadata_test.dart @@ -0,0 +1,75 @@ +// 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:flutter_tools/src/flutter_project_metadata.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:file/memory.dart'; + +import '../src/common.dart'; + +void main() { + FileSystem fileSystem; + BufferLogger logger; + File metadataFile; + + setUp(() { + fileSystem = MemoryFileSystem.test(); + logger = BufferLogger.test(); + metadataFile = fileSystem.file('.metadata'); + }); + + testWithoutContext('project metadata fields are empty when file does not exist', () { + final FlutterProjectMetadata projectMetadata = FlutterProjectMetadata(metadataFile, logger); + expect(projectMetadata.projectType, isNull); + expect(projectMetadata.versionChannel, isNull); + expect(projectMetadata.versionRevision, isNull); + + expect(logger.traceText, contains('.metadata project_type version is malformed.')); + expect(logger.traceText, contains('.metadata version is malformed.')); + }); + + testWithoutContext('project metadata fields are empty when file is empty', () { + metadataFile.createSync(); + final FlutterProjectMetadata projectMetadata = FlutterProjectMetadata(metadataFile, logger); + expect(projectMetadata.projectType, isNull); + expect(projectMetadata.versionChannel, isNull); + expect(projectMetadata.versionRevision, isNull); + + expect(logger.traceText, contains('.metadata project_type version is malformed.')); + expect(logger.traceText, contains('.metadata version is malformed.')); + }); + + testWithoutContext('projectType is populated when version is malformed', () { + metadataFile + ..createSync() + ..writeAsStringSync(''' +version: STRING INSTEAD OF MAP +project_type: plugin + '''); + final FlutterProjectMetadata projectMetadata = FlutterProjectMetadata(metadataFile, logger); + expect(projectMetadata.projectType, FlutterProjectType.plugin); + expect(projectMetadata.versionChannel, isNull); + expect(projectMetadata.versionRevision, isNull); + + expect(logger.traceText, contains('.metadata version is malformed.')); + }); + + testWithoutContext('version is populated when projectType is malformed', () { + metadataFile + ..createSync() + ..writeAsStringSync(''' +version: + revision: b59b226a49391949247e3d6122e34bb001049ae4 + channel: stable +project_type: {} + '''); + final FlutterProjectMetadata projectMetadata = FlutterProjectMetadata(metadataFile, logger); + expect(projectMetadata.projectType, isNull); + expect(projectMetadata.versionChannel, 'stable'); + expect(projectMetadata.versionRevision, 'b59b226a49391949247e3d6122e34bb001049ae4'); + + expect(logger.traceText, contains('.metadata project_type version is malformed.')); + }); +} \ No newline at end of file diff --git a/packages/flutter_tools/test/general.shard/github_template_test.dart b/packages/flutter_tools/test/general.shard/github_template_test.dart index f41d8dd014c..01732bd97dc 100644 --- a/packages/flutter_tools/test/general.shard/github_template_test.dart +++ b/packages/flutter_tools/test/general.shard/github_template_test.dart @@ -5,7 +5,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/net.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/reporting/github_template.dart'; import '../src/common.dart'; @@ -15,29 +16,39 @@ import '../src/testbed.dart'; const String _kShortURL = 'https://www.example.com/short'; void main() { + BufferLogger logger; + FileSystem fs; + setUp(() { + logger = BufferLogger.test(); + fs = MemoryFileSystem(); + }); + group('GitHub template creator', () { - testUsingContext('similar issues URL', () async { - final GitHubTemplateCreator creator = GitHubTemplateCreator(); + testWithoutContext('similar issues URL', () async { + final GitHubTemplateCreator creator = GitHubTemplateCreator( + fileSystem: fs, + logger: logger, + client: SuccessShortenURLFakeHttpClient(), + flutterProjectFactory: FlutterProjectFactory(), + ); expect( await creator.toolCrashSimilarIssuesGitHubURL('this is a 100% error'), _kShortURL ); - }, overrides: { - HttpClientFactory: () => () => SuccessShortenURLFakeHttpClient(), - FileSystem: () => MemoryFileSystem(), - ProcessManager: () => FakeProcessManager.any(), }); - testUsingContext('similar issues URL with network failure', () async { - final GitHubTemplateCreator creator = GitHubTemplateCreator(); + testWithoutContext('similar issues URL with network failure', () async { + final GitHubTemplateCreator creator = GitHubTemplateCreator( + fileSystem: fs, + logger: logger, + client: FakeHttpClient(), + flutterProjectFactory: FlutterProjectFactory(), + ); expect( await creator.toolCrashSimilarIssuesGitHubURL('this is a 100% error'), 'https://github.com/flutter/flutter/issues?q=is%3Aissue+this+is+a+100%25+error' ); - }, overrides: { - HttpClientFactory: () => () => FakeHttpClient(), - FileSystem: () => MemoryFileSystem(), - ProcessManager: () => FakeProcessManager.any(), + expect(logger.traceText, contains('Failed to shorten GitHub template URL')); }); group('new issue template URL', () { @@ -46,27 +57,34 @@ void main() { const String errorString = 'this is a 100% error'; const String exception = 'failing to succeed!!!'; const String doctorText = ' [✓] Flutter (Channel report'; - FileSystem fs; setUp(() async { stackTrace = StackTrace.fromString('trace'); - fs = MemoryFileSystem(); }); testUsingContext('shortened', () async { - final GitHubTemplateCreator creator = GitHubTemplateCreator(); + final GitHubTemplateCreator creator = GitHubTemplateCreator( + fileSystem: fs, + logger: logger, + client: SuccessShortenURLFakeHttpClient(), + flutterProjectFactory: FlutterProjectFactory(), + ); expect( await creator.toolCrashIssueTemplateGitHubURL(command, errorString, exception, stackTrace, doctorText), _kShortURL ); }, overrides: { - HttpClientFactory: () => () => SuccessShortenURLFakeHttpClient(), FileSystem: () => MemoryFileSystem(), ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('with network failure', () async { - final GitHubTemplateCreator creator = GitHubTemplateCreator(); + final GitHubTemplateCreator creator = GitHubTemplateCreator( + fileSystem: fs, + logger: logger, + client: FakeHttpClient(), + flutterProjectFactory: FlutterProjectFactory(), + ); expect( await creator.toolCrashIssueTemplateGitHubURL(command, errorString, exception, stackTrace, doctorText), 'https://github.com/flutter/flutter/issues/new?title=%5Btool_crash%5D+this+is+a+100%25+error&body=%23%' @@ -75,16 +93,20 @@ void main() { '%60%60%60%0A%60%60%60%0A+%5B%E2%9C%93%5D+Flutter+%28Channel+report%0A%60%60%60%0A%0A%23%23' '+Flutter+Application+Metadata%0ANo+pubspec+in+working+directory.%0A&labels=tool%2Csevere%3A+crash' ); + expect(logger.traceText, contains('Failed to shorten GitHub template URL')); }, overrides: { - HttpClientFactory: () => () => FakeHttpClient(), FileSystem: () => MemoryFileSystem(), ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('app metadata', () async { - final GitHubTemplateCreator creator = GitHubTemplateCreator(); + final GitHubTemplateCreator creator = GitHubTemplateCreator( + fileSystem: fs, + logger: logger, + client: FakeHttpClient(), + flutterProjectFactory: FlutterProjectFactory(), + ); final Directory projectDirectory = fs.currentDirectory; - final File pluginsFile = projectDirectory.childFile('.flutter-plugins'); projectDirectory .childFile('pubspec.yaml') @@ -99,12 +121,23 @@ flutter: 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, errorString, exception, stackTrace, doctorText); final String actualBody = Uri.parse(actualURL).queryParameters['body']; const String expectedBody = ''' @@ -128,6 +161,7 @@ trace ``` ## Flutter Application Metadata +**Type**: app **Version**: 2.0.1+100 **Material**: true **Android X**: true @@ -135,6 +169,8 @@ trace **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 @@ -143,7 +179,6 @@ device_info-0.4.1+4 expect(actualBody, expectedBody); }, overrides: { - HttpClientFactory: () => () => FakeHttpClient(), FileSystem: () => fs, ProcessManager: () => FakeProcessManager.any(), });