Unverified Commit ce40fbaf authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Include metadata in GitHub crash template (#53118)

parent adbc16b6
......@@ -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<void> _informUserOfCrash(List<String> 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>() ?? GitHubTemplateCreator();
final HttpClientFactory clientFactory = context.get<HttpClientFactory>();
final GitHubTemplateCreator gitHubTemplateCreator = context.get<GitHubTemplateCreator>() ?? 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);
......
......@@ -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<String>((_ProjectType type) => getEnumName(type)),
allowed: FlutterProjectType.values.map<String>((FlutterProjectType type) => type.name),
help: 'Specify the type of project to create.',
valueHelp: 'type',
allowedHelp: <String, String>{
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<String> path) {
return globals.fs.directory(globals.fs.path.joinAll(<String>[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(<String>['android', 'app'])
|| exists(<String>['ios', 'Runner'])
|| exists(<String>['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;
}
......
// 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];
}
}
......@@ -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<Logger>();
OperatingSystemUtils get os => context.get<OperatingSystemUtils>();
PersistentToolState get persistentToolState => PersistentToolState.instance;
Usage get flutterUsage => context.get<Usage>();
FlutterProjectFactory get projectFactory => context.get<FlutterProjectFactory>() ?? FlutterProjectFactory();
const FileSystem _kLocalFs = LocalFileSystem();
......
......@@ -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>() ?? 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');
......
......@@ -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<HttpClientFactory>() == null)
? HttpClient()
: context.get<HttpClientFactory>()();
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<String> 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<String> pathParts = globals.fs.path.split(pluginParts[1]);
final List<String> pathParts = _fileSystem.path.split(pluginParts[1]);
description.writeln(pathParts.isEmpty ? pluginParts.first : pathParts.last);
}
}
......@@ -124,7 +136,7 @@ ${_projectMetadataInformation()}
Future<String> _shortURL(String fullURL) async {
String url;
try {
globals.printTrace('Attempting git.io shortener: $fullURL');
_logger.printTrace('Attempting git.io shortener: $fullURL');
final List<int> 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;
......
// 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
......@@ -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: <Type, Generator>{
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: <Type, Generator>{
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: <Type, Generator>{
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: <Type, Generator>{
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: <Type, Generator>{
HttpClientFactory: () => () => FakeHttpClient(),
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
......
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