From b96d818c19af37a550bdc204199783665eeed295 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Tue, 3 Dec 2019 13:24:45 -0800 Subject: [PATCH] Remove record/replay/bug report functionality from the tool (#45999) --- .../lib/src/base/file_system.dart | 73 ----- .../flutter_tools/lib/src/base/platform.dart | 30 -- .../lib/src/base/process_manager.dart | 43 --- .../src/runner/flutter_command_runner.dart | 80 ----- packages/flutter_tools/lib/src/vmservice.dart | 29 -- .../lib/src/vmservice_record_replay.dart | 294 ------------------ .../test/general.shard/bug_report_test.dart | 35 --- 7 files changed, 584 deletions(-) delete mode 100644 packages/flutter_tools/lib/src/vmservice_record_replay.dart delete mode 100644 packages/flutter_tools/test/general.shard/bug_report_test.dart diff --git a/packages/flutter_tools/lib/src/base/file_system.dart b/packages/flutter_tools/lib/src/base/file_system.dart index 9ae5f777db..b8d7bc0f82 100644 --- a/packages/flutter_tools/lib/src/base/file_system.dart +++ b/packages/flutter_tools/lib/src/base/file_system.dart @@ -5,18 +5,15 @@ import 'package:file/file.dart'; import 'package:file/local.dart'; import 'package:file/memory.dart'; -import 'package:file/record_replay.dart'; import 'package:meta/meta.dart'; import 'common.dart' show throwToolExit; import 'context.dart'; import 'platform.dart'; -import 'process.dart'; export 'package:file/file.dart'; export 'package:file/local.dart'; -const String _kRecordingType = 'file'; const FileSystem _kLocalFs = LocalFileSystem(); /// Currently active implementation of the file system. @@ -25,33 +22,6 @@ const FileSystem _kLocalFs = LocalFileSystem(); /// with [MemoryFileSystem]. FileSystem get fs => context.get() ?? _kLocalFs; -/// Gets a [FileSystem] that will record file system activity to the specified -/// base recording [location]. -/// -/// Activity will be recorded in a subdirectory of [location] named `"file"`. -/// It is permissible for [location] to represent an existing non-empty -/// directory as long as there is no collision with the `"file"` subdirectory. -RecordingFileSystem getRecordingFileSystem(String location) { - final Directory dir = getRecordingSink(location, _kRecordingType); - final RecordingFileSystem fileSystem = RecordingFileSystem( - delegate: _kLocalFs, destination: dir); - addShutdownHook(() async { - await fileSystem.recording.flush(); - }, ShutdownStage.SERIALIZE_RECORDING); - return fileSystem; -} - -/// Gets a [FileSystem] that replays invocation activity from a previously -/// recorded set of invocations. -/// -/// [location] must represent a directory to which file system activity has -/// been recorded (i.e. the result of having been previously passed to -/// [getRecordingFileSystem]), or a [ToolExit] will be thrown. -ReplayFileSystem getReplayFileSystem(String location) { - final Directory dir = getReplaySource(location, _kRecordingType); - return ReplayFileSystem(recording: dir); -} - /// Create the ancestor directories of a file path if they do not already exist. void ensureDirectoryExists(String filePath) { final String dirPath = fs.path.dirname(filePath); @@ -105,49 +75,6 @@ void copyDirectorySync( } } -/// Gets a directory to act as a recording destination, creating the directory -/// as necessary. -/// -/// The directory will exist in the local file system, be named [basename], and -/// be a child of the directory identified by [dirname]. -/// -/// If the target directory already exists as a directory, the existing -/// directory must be empty, or a [ToolExit] will be thrown. If the target -/// directory exists as an entity other than a directory, a [ToolExit] will -/// also be thrown. -Directory getRecordingSink(String dirname, String basename) { - final String location = _kLocalFs.path.join(dirname, basename); - switch (_kLocalFs.typeSync(location, followLinks: false)) { - case FileSystemEntityType.file: - case FileSystemEntityType.link: - throwToolExit('Invalid record-to location: $dirname ("$basename" exists as non-directory)'); - break; - case FileSystemEntityType.directory: - if (_kLocalFs.directory(location).listSync(followLinks: false).isNotEmpty) { - throwToolExit('Invalid record-to location: $dirname ("$basename" is not empty)'); - } - break; - case FileSystemEntityType.notFound: - _kLocalFs.directory(location).createSync(recursive: true); - } - return _kLocalFs.directory(location); -} - -/// Gets a directory that holds a saved recording to be used for the purpose of -/// replay. -/// -/// The directory will exist in the local file system, be named [basename], and -/// be a child of the directory identified by [dirname]. -/// -/// If the target directory does not exist, a [ToolExit] will be thrown. -Directory getReplaySource(String dirname, String basename) { - final Directory dir = _kLocalFs.directory(_kLocalFs.path.join(dirname, basename)); - if (!dir.existsSync()) { - throwToolExit('Invalid replay-from location: $dirname ("$basename" does not exist)'); - } - return dir; -} - /// Canonicalizes [path]. /// /// This function implements the behavior of `canonicalize` from diff --git a/packages/flutter_tools/lib/src/base/platform.dart b/packages/flutter_tools/lib/src/base/platform.dart index aa2fb0f24f..9cea8c9020 100644 --- a/packages/flutter_tools/lib/src/base/platform.dart +++ b/packages/flutter_tools/lib/src/base/platform.dart @@ -5,39 +5,9 @@ import 'package:platform/platform.dart'; import 'context.dart'; -import 'file_system.dart'; export 'package:platform/platform.dart'; const Platform _kLocalPlatform = LocalPlatform(); -const String _kRecordingType = 'platform'; Platform get platform => context.get() ?? _kLocalPlatform; - -/// Serializes the current [platform] to the specified base recording -/// [location]. -/// -/// Platform metadata will be recorded in a subdirectory of [location] named -/// `"platform"`. It is permissible for [location] to represent an existing -/// non-empty directory as long as there is no collision with the `"platform"` -/// subdirectory. -/// -/// Returns the existing platform. -Platform getRecordingPlatform(String location) { - final Directory dir = getRecordingSink(location, _kRecordingType); - final File file = _getPlatformManifest(dir); - file.writeAsStringSync(platform.toJson(), flush: true); - return platform; -} - -FakePlatform getReplayPlatform(String location) { - final Directory dir = getReplaySource(location, _kRecordingType); - final File file = _getPlatformManifest(dir); - final String json = file.readAsStringSync(); - return FakePlatform.fromJson(json); -} - -File _getPlatformManifest(Directory dir) { - final String path = dir.fileSystem.path.join(dir.path, 'MANIFEST.txt'); - return dir.fileSystem.file(path); -} diff --git a/packages/flutter_tools/lib/src/base/process_manager.dart b/packages/flutter_tools/lib/src/base/process_manager.dart index cffd66e33d..4aa69a7017 100644 --- a/packages/flutter_tools/lib/src/base/process_manager.dart +++ b/packages/flutter_tools/lib/src/base/process_manager.dart @@ -2,54 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; - import 'package:process/process.dart'; -import 'package:process/record_replay.dart'; -import 'common.dart'; import 'context.dart'; -import 'file_system.dart'; -import 'process.dart'; -const String _kRecordingType = 'process'; const ProcessManager _kLocalProcessManager = LocalProcessManager(); /// The active process manager. ProcessManager get processManager => context.get() ?? _kLocalProcessManager; - -/// Gets a [ProcessManager] that will record process invocation activity to the -/// specified base recording [location]. -/// -/// Activity will be recorded in a subdirectory of [location] named `"process"`. -/// It is permissible for [location] to represent an existing non-empty -/// directory as long as there is no collision with the `"process"` -/// subdirectory. -RecordingProcessManager getRecordingProcessManager(String location) { - final Directory dir = getRecordingSink(location, _kRecordingType); - const ProcessManager delegate = LocalProcessManager(); - final RecordingProcessManager manager = RecordingProcessManager(delegate, dir); - addShutdownHook(() async { - await manager.flush(finishRunningProcesses: true); - }, ShutdownStage.SERIALIZE_RECORDING); - return manager; -} - -/// Gets a [ProcessManager] that replays process activity from a previously -/// recorded set of invocations. -/// -/// [location] must represent a directory to which process activity has been -/// recorded (i.e. the result of having been previously passed to -/// [getRecordingProcessManager]), or a [ToolExit] will be thrown. -Future getReplayProcessManager(String location) async { - final Directory dir = getReplaySource(location, _kRecordingType); - - ProcessManager manager; - try { - manager = await ReplayProcessManager.create(dir); - } on ArgumentError catch (error) { - throwToolExit('Invalid replay-from: $error'); - } - - return manager as ReplayProcessManager; -} diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart index a3839d7f35..1a2c0fa2f8 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart @@ -8,8 +8,6 @@ import 'package:args/args.dart'; import 'package:args/command_runner.dart'; import 'package:completion/completion.dart'; import 'package:file/file.dart'; -import 'package:platform/platform.dart'; -import 'package:process/process.dart'; import '../artifacts.dart'; import '../base/common.dart'; @@ -17,10 +15,7 @@ import '../base/context.dart'; import '../base/file_system.dart'; import '../base/io.dart' as io; import '../base/logger.dart'; -import '../base/os.dart'; import '../base/platform.dart'; -import '../base/process.dart'; -import '../base/process_manager.dart'; import '../base/terminal.dart'; import '../base/user_messages.dart'; import '../base/utils.dart'; @@ -32,7 +27,6 @@ import '../globals.dart'; import '../reporting/reporting.dart'; import '../tester/flutter_tester.dart'; import '../version.dart'; -import '../vmservice.dart'; const String kFlutterRootEnvironmentVariableName = 'FLUTTER_ROOT'; // should point to //flutter/ (root of flutter/flutter repo) const String kFlutterEngineEnvironmentVariableName = 'FLUTTER_ENGINE'; // should point to //engine/src/ (root of flutter/engine repo) @@ -96,10 +90,6 @@ class FlutterCommandRunner extends CommandRunner { argParser.addFlag('suppress-analytics', negatable: false, help: 'Suppress analytics reporting when this command runs.'); - argParser.addFlag('bug-report', - negatable: false, - help: 'Captures a bug report file to submit to the Flutter team.\n' - 'Contains local paths, device identifiers, and log snippets.'); String packagesHelp; bool showPackagesCommand; @@ -141,19 +131,6 @@ class FlutterCommandRunner extends CommandRunner { if (verboseHelp) { argParser.addSeparator('Options for testing the "flutter" tool itself:'); } - - argParser.addOption('record-to', - hide: !verboseHelp, - help: 'Enables recording of process invocations (including stdout and stderr of all such invocations), ' - 'and file system access (reads and writes).\n' - 'Serializes that recording to a directory with the path specified in this flag. If the ' - 'directory does not already exist, it will be created.'); - argParser.addOption('replay-from', - hide: !verboseHelp, - help: 'Enables mocking of process invocations by replaying their stdout, stderr, and exit code from ' - 'the specified recording (obtained via --record-to). The path specified in this flag must refer ' - 'to a directory that holds serialized process invocations structured according to the output of ' - '--record-to.'); argParser.addFlag('show-test-device', negatable: false, hide: !verboseHelp, @@ -291,63 +268,6 @@ class FlutterCommandRunner extends CommandRunner { FlutterTesterDevices.showFlutterTesterDevice = true; } - String recordTo = topLevelResults['record-to'] as String; - String replayFrom = topLevelResults['replay-from'] as String; - - if (topLevelResults['bug-report'] as bool) { - // --bug-report implies --record-to= - final Directory tempDir = const LocalFileSystem() - .systemTempDirectory - .createTempSync('flutter_tools_bug_report.'); - recordTo = tempDir.path; - - // Record the arguments that were used to invoke this runner. - final File manifest = tempDir.childFile('MANIFEST.txt'); - final StringBuffer buffer = StringBuffer() - ..writeln('# arguments') - ..writeln(topLevelResults.arguments) - ..writeln() - ..writeln('# rest') - ..writeln(topLevelResults.rest); - manifest.writeAsStringSync(buffer.toString(), flush: true); - - // ZIP the recording up once the recording has been serialized. - addShutdownHook(() { - final File zipFile = getUniqueFile(fs.currentDirectory, 'bugreport', 'zip'); - os.zip(tempDir, zipFile); - printStatus(userMessages.runnerBugReportFinished(zipFile.basename)); - }, ShutdownStage.POST_PROCESS_RECORDING); - addShutdownHook(() => tempDir.deleteSync(recursive: true), ShutdownStage.CLEANUP); - } - - assert(recordTo == null || replayFrom == null); - - if (recordTo != null) { - recordTo = recordTo.trim(); - if (recordTo.isEmpty) { - throwToolExit(userMessages.runnerNoRecordTo); - } - contextOverrides.addAll({ - ProcessManager: getRecordingProcessManager(recordTo), - FileSystem: getRecordingFileSystem(recordTo), - Platform: getRecordingPlatform(recordTo), - }); - VMService.enableRecordingConnection(recordTo); - } - - if (replayFrom != null) { - replayFrom = replayFrom.trim(); - if (replayFrom.isEmpty) { - throwToolExit(userMessages.runnerNoReplayFrom); - } - contextOverrides.addAll({ - ProcessManager: await getReplayProcessManager(replayFrom), - FileSystem: getReplayFileSystem(replayFrom), - Platform: getReplayPlatform(replayFrom), - }); - VMService.enableReplayConnection(replayFrom); - } - // We must set Cache.flutterRoot early because other features use it (e.g. // enginePath's initializer uses it). final String flutterRoot = topLevelResults['flutter-root'] as String ?? defaultFlutterRoot; diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index bd1880323d..8dca5ea3e7 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart @@ -5,7 +5,6 @@ import 'dart:async'; import 'dart:math' as math; -import 'package:file/file.dart'; import 'package:json_rpc_2/error_code.dart' as rpc_error_code; import 'package:json_rpc_2/json_rpc_2.dart' as rpc; import 'package:meta/meta.dart' show required; @@ -22,7 +21,6 @@ import 'convert.dart' show base64, utf8; import 'device.dart'; import 'globals.dart'; import 'version.dart'; -import 'vmservice_record_replay.dart'; /// Override `WebSocketConnector` in [context] to use a different constructor /// for [WebSocket]s (used by tests). @@ -62,8 +60,6 @@ typedef CompileExpression = Future Function( bool isStatic, ); -const String _kRecordingType = 'vmservice'; - Future> _defaultOpenChannel(Uri uri, {io.CompressionOptions compression = io.CompressionOptions.compressionDefault}) async { Duration delay = const Duration(milliseconds: 100); int attempts = 0; @@ -284,31 +280,6 @@ class VMService { } } - /// Enables recording of VMService JSON-rpc activity to the specified base - /// recording [location]. - /// - /// Activity will be recorded in a subdirectory of [location] named - /// `"vmservice"`. It is permissible for [location] to represent an existing - /// non-empty directory as long as there is no collision with the - /// `"vmservice"` subdirectory. - static void enableRecordingConnection(String location) { - final Directory dir = getRecordingSink(location, _kRecordingType); - _openChannel = (Uri uri, {io.CompressionOptions compression}) async { - final StreamChannel delegate = await _defaultOpenChannel(uri); - return RecordingVMServiceChannel(delegate, dir); - }; - } - - /// Enables VMService JSON-rpc replay mode. - /// - /// [location] must represent a directory to which VMService JSON-rpc - /// activity has been recorded (i.e. the result of having been previously - /// passed to [enableRecordingConnection]), or a [ToolExit] will be thrown. - static void enableReplayConnection(String location) { - final Directory dir = getReplaySource(location, _kRecordingType); - _openChannel = (Uri uri, {io.CompressionOptions compression}) async => ReplayVMServiceChannel(dir); - } - static void _unhandledError(dynamic error, dynamic stack) { logger.printTrace('Error in internal implementation of JSON RPC.\n$error\n$stack'); assert(false); diff --git a/packages/flutter_tools/lib/src/vmservice_record_replay.dart b/packages/flutter_tools/lib/src/vmservice_record_replay.dart deleted file mode 100644 index 7cb940a7d0..0000000000 --- a/packages/flutter_tools/lib/src/vmservice_record_replay.dart +++ /dev/null @@ -1,294 +0,0 @@ -// 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 'dart:async'; - -import 'package:file/file.dart'; -import 'package:stream_channel/stream_channel.dart'; - -import 'base/io.dart'; -import 'base/process.dart'; -import 'base/utils.dart'; -import 'convert.dart'; -import 'globals.dart'; - -const String _kManifest = 'MANIFEST.txt'; -const String _kRequest = 'request'; -const String _kResponse = 'response'; -const String _kId = 'id'; -const String _kType = 'type'; -const String _kData = 'data'; - -/// A [StreamChannel] that expects VM service (JSON-rpc) protocol messages and -/// serializes all such messages to the file system for later playback. -class RecordingVMServiceChannel extends DelegatingStreamChannel { - RecordingVMServiceChannel(StreamChannel delegate, Directory location) - : super(delegate) { - addShutdownHook(() { - // Sort the messages such that they are ordered - // `[request1, response1, request2, response2, ...]`. This serves no - // purpose other than to make the serialized format more human-readable. - _messages.sort(); - - final File file = _getManifest(location); - final String json = const JsonEncoder.withIndent(' ').convert(_messages); - file.writeAsStringSync(json, flush: true); - }, ShutdownStage.SERIALIZE_RECORDING); - } - - final List<_Message> _messages = <_Message>[]; - - _RecordingStream _streamRecorder; - _RecordingSink _sinkRecorder; - - @override - Stream get stream { - _streamRecorder ??= _RecordingStream(super.stream, _messages); - return _streamRecorder.stream; - } - - @override - StreamSink get sink => _sinkRecorder ??= _RecordingSink(super.sink, _messages); -} - -/// Base class for request and response JSON-rpc messages. -abstract class _Message implements Comparable<_Message> { - _Message(this.type, this.data); - - factory _Message.fromRecording(Map recordingData) { - return recordingData[_kType] == _kRequest - ? _Request(castStringKeyedMap(recordingData[_kData])) - : _Response(castStringKeyedMap(recordingData[_kData])); - } - - final String type; - final Map data; - - int get id => data[_kId] as int; - - /// Allows [JsonEncoder] to properly encode objects of this type. - Map toJson() { - return { - _kType: type, - _kData: data, - }; - } - - @override - int compareTo(_Message other) { - if (id == null) { - printError('Invalid VMService message data detected: $data'); - return -1; - } - final int result = id.compareTo(other.id); - if (result != 0) { - return result; - } else if (type == _kRequest) { - return -1; - } else { - return 1; - } - } -} - -/// A VM service JSON-rpc request (sent to the VM). -class _Request extends _Message { - _Request(Map data) : super(_kRequest, data); - _Request.fromString(String data) : this(castStringKeyedMap(json.decode(data))); -} - -/// A VM service JSON-rpc response (from the VM). -class _Response extends _Message { - _Response(Map data) : super(_kResponse, data); - _Response.fromString(String data) : this(castStringKeyedMap(json.decode(data))); -} - -/// A matching request/response pair. -/// -/// A request and response match by virtue of having matching -/// [IDs](_Message.id). -class _Transaction { - _Request request; - _Response response; -} - -/// A helper class that monitors a [Stream] of VM service JSON-rpc responses -/// and saves the responses to a recording. -class _RecordingStream { - _RecordingStream(Stream stream, this._recording) - : _delegate = stream, - _controller = stream.isBroadcast - ? StreamController.broadcast() - : StreamController() { - _controller.onListen = () { - assert(_subscription == null); - _subscription = _listenToStream(); - }; - _controller.onCancel = () async { - assert(_subscription != null); - await _subscription.cancel(); - _subscription = null; - }; - _controller.onPause = () { - assert(_subscription != null && !_subscription.isPaused); - _subscription.pause(); - }; - _controller.onResume = () { - assert(_subscription != null && _subscription.isPaused); - _subscription.resume(); - }; - } - - final Stream _delegate; - final StreamController _controller; - final List<_Message> _recording; - StreamSubscription _subscription; - - StreamSubscription _listenToStream() { - return _delegate.listen( - (String element) { - _recording.add(_Response.fromString(element)); - _controller.add(element); - }, - onError: _controller.addError, // We currently don't support recording of errors. - onDone: _controller.close, - ); - } - - /// The wrapped [Stream] to expose to callers. - Stream get stream => _controller.stream; -} - -/// A [StreamSink] that monitors VM service JSON-rpc requests and saves the -/// requests to a recording. -class _RecordingSink implements StreamSink { - _RecordingSink(this._delegate, this._recording); - - final StreamSink _delegate; - final List<_Message> _recording; - - @override - Future close() => _delegate.close(); - - @override - Future get done => _delegate.done; - - @override - void add(String data) { - _delegate.add(data); - _recording.add(_Request.fromString(data)); - } - - @override - void addError(dynamic errorEvent, [ StackTrace stackTrace ]) { - throw UnimplementedError('Add support for this if the need ever arises'); - } - - @override - Future addStream(Stream stream) { - throw UnimplementedError('Add support for this if the need ever arises'); - } -} - -/// A [StreamChannel] that expects VM service (JSON-rpc) requests to be written -/// to its [StreamChannel.sink], looks up those requests in a recording, and -/// replays the corresponding responses back from the recording. -class ReplayVMServiceChannel extends StreamChannelMixin { - ReplayVMServiceChannel(Directory location) - : _transactions = _loadTransactions(location); - - final Map _transactions; - final StreamController _controller = StreamController(); - _ReplaySink _replaySink; - - static Map _loadTransactions(Directory location) { - final File file = _getManifest(location); - final String jsonData = file.readAsStringSync(); - final Iterable<_Message> messages = (json.decode(jsonData) as List) - .cast>() - .map<_Message>(_toMessage); - final Map transactions = {}; - for (_Message message in messages) { - final _Transaction transaction = - transactions.putIfAbsent(message.id, () => _Transaction()); - if (message.type == _kRequest) { - assert(transaction.request == null); - transaction.request = message as _Request; - } else { - assert(transaction.response == null); - transaction.response = message as _Response; - } - } - return transactions; - } - - static _Message _toMessage(Map jsonData) { - return _Message.fromRecording(jsonData); - } - - void send(_Request request) { - if (!_transactions.containsKey(request.id)) { - throw ArgumentError('No matching invocation found'); - } - final _Transaction transaction = _transactions.remove(request.id); - // TODO(tvolkert): validate that `transaction.request` matches `request` - if (transaction.response == null) { - // This signals that when we were recording, the VM shut down before - // we received the response. This is typically due to the user quitting - // the app runner. We follow suit here and exit. - printStatus('Exiting due to dangling request'); - exit(0); - } else { - _controller.add(json.encoder.convert(transaction.response.data)); - if (_transactions.isEmpty) { - _controller.close(); - } - } - } - - @override - StreamSink get sink => _replaySink ??= _ReplaySink(this); - - @override - Stream get stream => _controller.stream; -} - -class _ReplaySink implements StreamSink { - _ReplaySink(this.channel); - - final ReplayVMServiceChannel channel; - final Completer _completer = Completer(); - - @override - Future close() { - _completer.complete(); - return _completer.future; - } - - @override - Future get done => _completer.future; - - @override - void add(String data) { - if (_completer.isCompleted) { - throw StateError('Sink already closed'); - } - channel.send(_Request.fromString(data)); - } - - @override - void addError(dynamic errorEvent, [ StackTrace stackTrace ]) { - throw UnimplementedError('Add support for this if the need ever arises'); - } - - @override - Future addStream(Stream stream) { - throw UnimplementedError('Add support for this if the need ever arises'); - } -} - -File _getManifest(Directory location) { - final String path = location.fileSystem.path.join(location.path, _kManifest); - return location.fileSystem.file(path); -} diff --git a/packages/flutter_tools/test/general.shard/bug_report_test.dart b/packages/flutter_tools/test/general.shard/bug_report_test.dart deleted file mode 100644 index 7205cba93c..0000000000 --- a/packages/flutter_tools/test/general.shard/bug_report_test.dart +++ /dev/null @@ -1,35 +0,0 @@ -// 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_testing/file_testing.dart'; -import 'package:mockito/mockito.dart'; - -import 'package:flutter_tools/executable.dart' as tools; -import 'package:flutter_tools/src/cache.dart'; -import 'package:flutter_tools/src/base/io.dart'; -import 'package:flutter_tools/src/base/os.dart'; - -import '../src/common.dart'; -import '../src/context.dart'; - -void main() { - Cache.disableLocking(); - - int exitCode; - setExitFunctionForTests((int code) { - exitCode = code; - }); - - group('--bug-report', () { - testUsingContext('generates valid zip file', () async { - await tools.main(['devices', '--bug-report']); - expect(exitCode, 0); - verify(os.zip(any, argThat(hasPath(matches(r'bugreport_01\.zip'))))); - }, overrides: { - OperatingSystemUtils: () => MockOperatingSystemUtils(), - }); - }); -} - -class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils { }