Only write the pid-file while listening to SIGUSR signals. (#74533)
This commit is contained in:
parent
1b44133322
commit
b1cc48748d
@ -2,19 +2,11 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter_driver/driver_extension.dart';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
String log = '';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
enableFlutterDriverExtension(handler: (String message) async {
|
print('called main');
|
||||||
log = 'log:';
|
|
||||||
await WidgetsBinding.instance.reassembleApplication();
|
|
||||||
return log;
|
|
||||||
});
|
|
||||||
runApp(const MaterialApp(home: Test()));
|
runApp(const MaterialApp(home: Test()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,9 +14,7 @@ class Test extends SingleChildRenderObjectWidget {
|
|||||||
const Test({ Key key }) : super(key: key);
|
const Test({ Key key }) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
RenderTest createRenderObject(BuildContext context) {
|
RenderTest createRenderObject(BuildContext context) => RenderTest();
|
||||||
return RenderTest();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class RenderTest extends RenderProxyBox {
|
class RenderTest extends RenderProxyBox {
|
||||||
@ -33,11 +23,17 @@ class RenderTest extends RenderProxyBox {
|
|||||||
@override
|
@override
|
||||||
void debugPaintSize(PaintingContext context, Offset offset) {
|
void debugPaintSize(PaintingContext context, Offset offset) {
|
||||||
super.debugPaintSize(context, offset);
|
super.debugPaintSize(context, offset);
|
||||||
log += ' debugPaintSize';
|
print('called debugPaintSize');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(PaintingContext context, Offset offset) {
|
void paint(PaintingContext context, Offset offset) {
|
||||||
log += ' paint';
|
print('called paint');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void reassemble() {
|
||||||
|
print('called reassemble');
|
||||||
|
super.reassemble();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
41
dev/integration_tests/ui/lib/overflow.dart
Normal file
41
dev/integration_tests/ui/lib/overflow.dart
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// 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/material.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
runApp(const MaterialApp(home: Test()));
|
||||||
|
}
|
||||||
|
|
||||||
|
class Test extends StatefulWidget {
|
||||||
|
const Test({ Key key }) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Test> createState() => _TestState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TestState extends State<Test> {
|
||||||
|
bool _triggered = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void reassemble() {
|
||||||
|
_triggered = true;
|
||||||
|
super.reassemble();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (!_triggered)
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
return Row(children: const <Widget>[
|
||||||
|
SizedBox(width: 10000.0),
|
||||||
|
SizedBox(width: 10000.0),
|
||||||
|
SizedBox(width: 10000.0),
|
||||||
|
SizedBox(width: 10000.0),
|
||||||
|
SizedBox(width: 10000.0),
|
||||||
|
SizedBox(width: 10000.0),
|
||||||
|
SizedBox(width: 10000.0),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -52,6 +52,7 @@ import 'package:meta/meta.dart';
|
|||||||
import '../globals.dart' as globals;
|
import '../globals.dart' as globals;
|
||||||
import 'async_guard.dart';
|
import 'async_guard.dart';
|
||||||
import 'context.dart';
|
import 'context.dart';
|
||||||
|
import 'file_system.dart';
|
||||||
import 'process.dart';
|
import 'process.dart';
|
||||||
|
|
||||||
export 'dart:io'
|
export 'dart:io'
|
||||||
@ -364,34 +365,60 @@ class Stdio {
|
|||||||
Future<void> addStderrStream(Stream<List<int>> stream) => stderr.addStream(stream);
|
Future<void> addStderrStream(Stream<List<int>> stream) => stderr.addStream(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(zra): Move pid and writePidFile into `ProcessInfo`.
|
|
||||||
void writePidFile(String pidFile) {
|
|
||||||
if (pidFile != null) {
|
|
||||||
// Write our pid to the file.
|
|
||||||
globals.fs.file(pidFile).writeAsStringSync(io.pid.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An overridable version of io.ProcessInfo.
|
/// An overridable version of io.ProcessInfo.
|
||||||
abstract class ProcessInfo {
|
abstract class ProcessInfo {
|
||||||
factory ProcessInfo() => _DefaultProcessInfo();
|
factory ProcessInfo() => _DefaultProcessInfo(globals.fs);
|
||||||
|
factory ProcessInfo.test(FileSystem fs) => _TestProcessInfo(fs);
|
||||||
|
|
||||||
static ProcessInfo get instance => context.get<ProcessInfo>();
|
static ProcessInfo get instance => context.get<ProcessInfo>();
|
||||||
|
|
||||||
int get currentRss;
|
int get currentRss;
|
||||||
|
|
||||||
int get maxRss;
|
int get maxRss;
|
||||||
|
|
||||||
|
File writePidFile(String pidFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessInfo get processInfo => ProcessInfo.instance;
|
ProcessInfo get processInfo => ProcessInfo.instance;
|
||||||
|
|
||||||
/// The default implementation of [ProcessInfo], which uses [io.ProcessInfo].
|
/// The default implementation of [ProcessInfo], which uses [io.ProcessInfo].
|
||||||
class _DefaultProcessInfo implements ProcessInfo {
|
class _DefaultProcessInfo implements ProcessInfo {
|
||||||
|
_DefaultProcessInfo(this._fileSystem);
|
||||||
|
|
||||||
|
final FileSystem _fileSystem;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get currentRss => io.ProcessInfo.currentRss;
|
int get currentRss => io.ProcessInfo.currentRss;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get maxRss => io.ProcessInfo.maxRss;
|
int get maxRss => io.ProcessInfo.maxRss;
|
||||||
|
|
||||||
|
@override
|
||||||
|
File writePidFile(String pidFile) {
|
||||||
|
assert(pidFile != null);
|
||||||
|
return _fileSystem.file(pidFile)
|
||||||
|
..writeAsStringSync(io.pid.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The test version of [ProcessInfo].
|
||||||
|
class _TestProcessInfo implements ProcessInfo {
|
||||||
|
_TestProcessInfo(this._fileSystem);
|
||||||
|
|
||||||
|
final FileSystem _fileSystem;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int currentRss = 1000;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int maxRss = 2000;
|
||||||
|
|
||||||
|
@override
|
||||||
|
File writePidFile(String pidFile) {
|
||||||
|
assert(pidFile != null);
|
||||||
|
return _fileSystem.file(pidFile)
|
||||||
|
..writeAsStringSync('12345');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The return type for [listNetworkInterfaces].
|
/// The return type for [listNetworkInterfaces].
|
||||||
|
@ -90,18 +90,26 @@ class AttachCommand extends FlutterCommand {
|
|||||||
'This parameter is case-insensitive.',
|
'This parameter is case-insensitive.',
|
||||||
)..addOption(
|
)..addOption(
|
||||||
'pid-file',
|
'pid-file',
|
||||||
help: 'Specify a file to write the process id to. '
|
help: 'Specify a file to write the process ID to. '
|
||||||
'You can send SIGUSR1 to trigger a hot reload '
|
'You can send SIGUSR1 to trigger a hot reload '
|
||||||
'and SIGUSR2 to trigger a hot restart.',
|
'and SIGUSR2 to trigger a hot restart. '
|
||||||
|
'The file is created when the signal handlers '
|
||||||
|
'are hooked and deleted when they are removed.',
|
||||||
|
)..addFlag(
|
||||||
|
'report-ready',
|
||||||
|
help: 'Print "ready" to the console after handling a keyboard command.\n'
|
||||||
|
'This is primarily useful for tests and other automation, but consider '
|
||||||
|
'using --machine instead.',
|
||||||
|
hide: !verboseHelp,
|
||||||
)..addOption(
|
)..addOption(
|
||||||
'project-root',
|
'project-root',
|
||||||
hide: !verboseHelp,
|
hide: !verboseHelp,
|
||||||
help: 'Normally used only in run target',
|
help: 'Normally used only in run target.',
|
||||||
)..addFlag('machine',
|
)..addFlag('machine',
|
||||||
hide: !verboseHelp,
|
hide: !verboseHelp,
|
||||||
negatable: false,
|
negatable: false,
|
||||||
help: 'Handle machine structured JSON command input and provide output '
|
help: 'Handle machine structured JSON command input and provide output '
|
||||||
'and progress in machine friendly format.',
|
'and progress in machine-friendly format.',
|
||||||
);
|
);
|
||||||
usesTrackWidgetCreation(verboseHelp: verboseHelp);
|
usesTrackWidgetCreation(verboseHelp: verboseHelp);
|
||||||
addDdsOptions(verboseHelp: verboseHelp);
|
addDdsOptions(verboseHelp: verboseHelp);
|
||||||
@ -200,8 +208,6 @@ known, it can be explicitly provided to attach via the command-line, e.g.
|
|||||||
Future<FlutterCommandResult> runCommand() async {
|
Future<FlutterCommandResult> runCommand() async {
|
||||||
await _validateArguments();
|
await _validateArguments();
|
||||||
|
|
||||||
writePidFile(stringArg('pid-file'));
|
|
||||||
|
|
||||||
final Device device = await findTargetDevice();
|
final Device device = await findTargetDevice();
|
||||||
|
|
||||||
final Artifacts overrideArtifacts = device.artifactOverrides ?? globals.artifacts;
|
final Artifacts overrideArtifacts = device.artifactOverrides ?? globals.artifacts;
|
||||||
@ -362,9 +368,12 @@ known, it can be explicitly provided to attach via the command-line, e.g.
|
|||||||
logger: globals.logger,
|
logger: globals.logger,
|
||||||
terminal: globals.terminal,
|
terminal: globals.terminal,
|
||||||
signals: globals.signals,
|
signals: globals.signals,
|
||||||
|
processInfo: processInfo,
|
||||||
|
reportReady: boolArg('report-ready'),
|
||||||
|
pidFile: stringArg('pid-file'),
|
||||||
)
|
)
|
||||||
..setupTerminal()
|
..registerSignalHandlers()
|
||||||
..registerSignalHandlers();
|
..setupTerminal();
|
||||||
}));
|
}));
|
||||||
result = await runner.attach(
|
result = await runner.attach(
|
||||||
appStartedCompleter: onAppStart,
|
appStartedCompleter: onAppStart,
|
||||||
|
@ -289,11 +289,18 @@ class RunCommand extends RunCommandBase {
|
|||||||
help: 'Stay resident after launching the application. Not available with "--trace-startup".',
|
help: 'Stay resident after launching the application. Not available with "--trace-startup".',
|
||||||
)
|
)
|
||||||
..addOption('pid-file',
|
..addOption('pid-file',
|
||||||
help: 'Specify a file to write the process id to. '
|
help: 'Specify a file to write the process ID to. '
|
||||||
'You can send SIGUSR1 to trigger a hot reload '
|
'You can send SIGUSR1 to trigger a hot reload '
|
||||||
'and SIGUSR2 to trigger a hot restart.',
|
'and SIGUSR2 to trigger a hot restart. '
|
||||||
)
|
'The file is created when the signal handlers '
|
||||||
..addFlag('benchmark',
|
'are hooked and deleted when they are removed.',
|
||||||
|
)..addFlag(
|
||||||
|
'report-ready',
|
||||||
|
help: 'Print "ready" to the console after handling a keyboard command.\n'
|
||||||
|
'This is primarily useful for tests and other automation, but consider '
|
||||||
|
'using --machine instead.',
|
||||||
|
hide: !verboseHelp,
|
||||||
|
)..addFlag('benchmark',
|
||||||
negatable: false,
|
negatable: false,
|
||||||
hide: !verboseHelp,
|
hide: !verboseHelp,
|
||||||
help: 'Enable a benchmarking mode. This will run the given application, '
|
help: 'Enable a benchmarking mode. This will run the given application, '
|
||||||
@ -514,8 +521,6 @@ class RunCommand extends RunCommandBase {
|
|||||||
final bool hotMode = shouldUseHotMode(buildInfo);
|
final bool hotMode = shouldUseHotMode(buildInfo);
|
||||||
final String applicationBinaryPath = stringArg('use-application-binary');
|
final String applicationBinaryPath = stringArg('use-application-binary');
|
||||||
|
|
||||||
writePidFile(stringArg('pid-file'));
|
|
||||||
|
|
||||||
if (boolArg('machine')) {
|
if (boolArg('machine')) {
|
||||||
if (devices.length > 1) {
|
if (devices.length > 1) {
|
||||||
throwToolExit('--machine does not support -d all.');
|
throwToolExit('--machine does not support -d all.');
|
||||||
@ -620,34 +625,39 @@ class RunCommand extends RunCommandBase {
|
|||||||
//
|
//
|
||||||
// Do not add more operations to the future.
|
// Do not add more operations to the future.
|
||||||
final Completer<void> appStartedTimeRecorder = Completer<void>.sync();
|
final Completer<void> appStartedTimeRecorder = Completer<void>.sync();
|
||||||
|
|
||||||
|
TerminalHandler handler;
|
||||||
// This callback can't throw.
|
// This callback can't throw.
|
||||||
unawaited(appStartedTimeRecorder.future.then<void>(
|
unawaited(appStartedTimeRecorder.future.then<void>(
|
||||||
(_) {
|
(_) {
|
||||||
appStartedTime = globals.systemClock.now();
|
appStartedTime = globals.systemClock.now();
|
||||||
if (stayResident) {
|
if (stayResident) {
|
||||||
TerminalHandler(
|
handler = TerminalHandler(
|
||||||
runner,
|
runner,
|
||||||
logger: globals.logger,
|
logger: globals.logger,
|
||||||
terminal: globals.terminal,
|
terminal: globals.terminal,
|
||||||
signals: globals.signals,
|
signals: globals.signals,
|
||||||
|
processInfo: processInfo,
|
||||||
|
reportReady: boolArg('report-ready'),
|
||||||
|
pidFile: stringArg('pid-file'),
|
||||||
)
|
)
|
||||||
..setupTerminal()
|
..registerSignalHandlers()
|
||||||
..registerSignalHandlers();
|
..setupTerminal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final int result = await runner.run(
|
final int result = await runner.run(
|
||||||
appStartedCompleter: appStartedTimeRecorder,
|
appStartedCompleter: appStartedTimeRecorder,
|
||||||
enableDevTools: stayResident && boolArg(FlutterCommand.kEnableDevTools),
|
enableDevTools: stayResident && boolArg(FlutterCommand.kEnableDevTools),
|
||||||
route: route,
|
route: route,
|
||||||
);
|
);
|
||||||
|
handler?.stop();
|
||||||
if (result != 0) {
|
if (result != 0) {
|
||||||
throwToolExit(null, exitCode: result);
|
throwToolExit(null, exitCode: result);
|
||||||
}
|
}
|
||||||
} on RPCError catch (err) {
|
} on RPCError catch (error) {
|
||||||
if (err.code == RPCErrorCodes.kServiceDisappeared) {
|
if (error.code == RPCErrorCodes.kServiceDisappeared) {
|
||||||
throwToolExit('Lost connection to device.');
|
throwToolExit('Lost connection to device.');
|
||||||
}
|
}
|
||||||
rethrow;
|
rethrow;
|
||||||
|
@ -177,7 +177,9 @@ class DevtoolsServerLauncher extends DevtoolsLauncher {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
devToolsUrl = null;
|
if (devToolsUrl != null) {
|
||||||
|
devToolsUrl = null;
|
||||||
|
}
|
||||||
if (_devToolsProcess != null) {
|
if (_devToolsProcess != null) {
|
||||||
_devToolsProcess.kill();
|
_devToolsProcess.kill();
|
||||||
await _devToolsProcess.exitCode;
|
await _devToolsProcess.exitCode;
|
||||||
|
@ -969,7 +969,7 @@ abstract class ResidentRunner {
|
|||||||
await residentDevtoolsHandler.shutdown();
|
await residentDevtoolsHandler.shutdown();
|
||||||
await stopEchoingDeviceLog();
|
await stopEchoingDeviceLog();
|
||||||
await preExit();
|
await preExit();
|
||||||
await exitApp();
|
await exitApp(); // calls appFinished
|
||||||
await shutdownDartDevelopmentService();
|
await shutdownDartDevelopmentService();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1452,17 +1452,27 @@ class TerminalHandler {
|
|||||||
@required Logger logger,
|
@required Logger logger,
|
||||||
@required Terminal terminal,
|
@required Terminal terminal,
|
||||||
@required Signals signals,
|
@required Signals signals,
|
||||||
|
@required io.ProcessInfo processInfo,
|
||||||
|
@required bool reportReady,
|
||||||
|
String pidFile,
|
||||||
}) : _logger = logger,
|
}) : _logger = logger,
|
||||||
_terminal = terminal,
|
_terminal = terminal,
|
||||||
_signals = signals;
|
_signals = signals,
|
||||||
|
_processInfo = processInfo,
|
||||||
|
_reportReady = reportReady,
|
||||||
|
_pidFile = pidFile;
|
||||||
|
|
||||||
final Logger _logger;
|
final Logger _logger;
|
||||||
final Terminal _terminal;
|
final Terminal _terminal;
|
||||||
final Signals _signals;
|
final Signals _signals;
|
||||||
|
final io.ProcessInfo _processInfo;
|
||||||
|
final bool _reportReady;
|
||||||
|
final String _pidFile;
|
||||||
|
|
||||||
final ResidentRunner residentRunner;
|
final ResidentRunner residentRunner;
|
||||||
bool _processingUserRequest = false;
|
bool _processingUserRequest = false;
|
||||||
StreamSubscription<void> subscription;
|
StreamSubscription<void> subscription;
|
||||||
|
File _actualPidFile;
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
String lastReceivedCommand;
|
String lastReceivedCommand;
|
||||||
@ -1476,7 +1486,6 @@ class TerminalHandler {
|
|||||||
subscription = _terminal.keystrokes.listen(processTerminalInput);
|
subscription = _terminal.keystrokes.listen(processTerminalInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
final Map<io.ProcessSignal, Object> _signalTokens = <io.ProcessSignal, Object>{};
|
final Map<io.ProcessSignal, Object> _signalTokens = <io.ProcessSignal, Object>{};
|
||||||
|
|
||||||
void _addSignalHandler(io.ProcessSignal signal, SignalHandler handler) {
|
void _addSignalHandler(io.ProcessSignal signal, SignalHandler handler) {
|
||||||
@ -1485,19 +1494,30 @@ class TerminalHandler {
|
|||||||
|
|
||||||
void registerSignalHandlers() {
|
void registerSignalHandlers() {
|
||||||
assert(residentRunner.stayResident);
|
assert(residentRunner.stayResident);
|
||||||
|
|
||||||
_addSignalHandler(io.ProcessSignal.SIGINT, _cleanUp);
|
_addSignalHandler(io.ProcessSignal.SIGINT, _cleanUp);
|
||||||
_addSignalHandler(io.ProcessSignal.SIGTERM, _cleanUp);
|
_addSignalHandler(io.ProcessSignal.SIGTERM, _cleanUp);
|
||||||
if (!residentRunner.supportsServiceProtocol || !residentRunner.supportsRestart) {
|
if (residentRunner.supportsServiceProtocol && residentRunner.supportsRestart) {
|
||||||
return;
|
_addSignalHandler(io.ProcessSignal.SIGUSR1, _handleSignal);
|
||||||
|
_addSignalHandler(io.ProcessSignal.SIGUSR2, _handleSignal);
|
||||||
|
if (_pidFile != null) {
|
||||||
|
_logger.printTrace('Writing pid to: $_pidFile');
|
||||||
|
_actualPidFile = _processInfo.writePidFile(_pidFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_addSignalHandler(io.ProcessSignal.SIGUSR1, _handleSignal);
|
|
||||||
_addSignalHandler(io.ProcessSignal.SIGUSR2, _handleSignal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unregisters terminal signal and keystroke handlers.
|
/// Unregisters terminal signal and keystroke handlers.
|
||||||
void stop() {
|
void stop() {
|
||||||
assert(residentRunner.stayResident);
|
assert(residentRunner.stayResident);
|
||||||
|
if (_actualPidFile != null) {
|
||||||
|
try {
|
||||||
|
_logger.printTrace('Deleting pid file (${_actualPidFile.path}).');
|
||||||
|
_actualPidFile.deleteSync();
|
||||||
|
} on FileSystemException catch (error) {
|
||||||
|
_logger.printError('Failed to delete pid file (${_actualPidFile.path}): ${error.message}');
|
||||||
|
}
|
||||||
|
_actualPidFile = null;
|
||||||
|
}
|
||||||
for (final MapEntry<io.ProcessSignal, Object> entry in _signalTokens.entries) {
|
for (final MapEntry<io.ProcessSignal, Object> entry in _signalTokens.entries) {
|
||||||
_signals.removeHandler(entry.key, entry.value);
|
_signals.removeHandler(entry.key, entry.value);
|
||||||
}
|
}
|
||||||
@ -1623,6 +1643,9 @@ class TerminalHandler {
|
|||||||
rethrow;
|
rethrow;
|
||||||
} finally {
|
} finally {
|
||||||
_processingUserRequest = false;
|
_processingUserRequest = false;
|
||||||
|
if (_reportReady) {
|
||||||
|
_logger.printStatus('ready');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
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/base/logger.dart';
|
||||||
import 'package:flutter_tools/src/base/signals.dart';
|
import 'package:flutter_tools/src/base/signals.dart';
|
||||||
import 'package:flutter_tools/src/base/terminal.dart';
|
import 'package:flutter_tools/src/base/terminal.dart';
|
||||||
@ -22,11 +24,15 @@ void main() {
|
|||||||
final Logger logger = BufferLogger.test();
|
final Logger logger = BufferLogger.test();
|
||||||
final Signals signals = Signals.test();
|
final Signals signals = Signals.test();
|
||||||
final Terminal terminal = Terminal.test();
|
final Terminal terminal = Terminal.test();
|
||||||
|
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||||
|
final ProcessInfo processInfo = ProcessInfo.test(fs);
|
||||||
final TerminalHandler terminalHandler = TerminalHandler(
|
final TerminalHandler terminalHandler = TerminalHandler(
|
||||||
testRunner,
|
testRunner,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
signals: signals,
|
signals: signals,
|
||||||
terminal: terminal,
|
terminal: terminal,
|
||||||
|
processInfo: processInfo,
|
||||||
|
reportReady: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(testRunner.hasHelpBeenPrinted, false);
|
expect(testRunner.hasHelpBeenPrinted, false);
|
||||||
@ -39,11 +45,15 @@ void main() {
|
|||||||
final Logger logger = BufferLogger.test();
|
final Logger logger = BufferLogger.test();
|
||||||
final Signals signals = Signals.test();
|
final Signals signals = Signals.test();
|
||||||
final Terminal terminal = Terminal.test();
|
final Terminal terminal = Terminal.test();
|
||||||
|
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||||
|
final ProcessInfo processInfo = ProcessInfo.test(fs);
|
||||||
final TerminalHandler terminalHandler = TerminalHandler(
|
final TerminalHandler terminalHandler = TerminalHandler(
|
||||||
testRunner,
|
testRunner,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
signals: signals,
|
signals: signals,
|
||||||
terminal: terminal,
|
terminal: terminal,
|
||||||
|
processInfo: processInfo,
|
||||||
|
reportReady: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(testRunner.hasHelpBeenPrinted, false);
|
expect(testRunner.hasHelpBeenPrinted, false);
|
||||||
@ -60,12 +70,16 @@ void main() {
|
|||||||
testLogger = BufferLogger.test();
|
testLogger = BufferLogger.test();
|
||||||
final Signals signals = Signals.test();
|
final Signals signals = Signals.test();
|
||||||
final Terminal terminal = Terminal.test();
|
final Terminal terminal = Terminal.test();
|
||||||
|
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||||
|
final ProcessInfo processInfo = ProcessInfo.test(fs);
|
||||||
mockResidentRunner = MockResidentRunner();
|
mockResidentRunner = MockResidentRunner();
|
||||||
terminalHandler = TerminalHandler(
|
terminalHandler = TerminalHandler(
|
||||||
mockResidentRunner,
|
mockResidentRunner,
|
||||||
logger: testLogger,
|
logger: testLogger,
|
||||||
signals: signals,
|
signals: signals,
|
||||||
terminal: terminal,
|
terminal: terminal,
|
||||||
|
processInfo: processInfo,
|
||||||
|
reportReady: false,
|
||||||
);
|
);
|
||||||
when(mockResidentRunner.supportsServiceProtocol).thenReturn(true);
|
when(mockResidentRunner.supportsServiceProtocol).thenReturn(true);
|
||||||
});
|
});
|
||||||
@ -310,6 +324,34 @@ void main() {
|
|||||||
verify(mockResidentRunner.debugToggleDebugCheckElevationsEnabled()).called(2);
|
verify(mockResidentRunner.debugToggleDebugCheckElevationsEnabled()).called(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWithoutContext('pidfile creation', () {
|
||||||
|
final BufferLogger testLogger = BufferLogger.test();
|
||||||
|
final Signals signals = _TestSignals(Signals.defaultExitSignals);
|
||||||
|
final Terminal terminal = Terminal.test();
|
||||||
|
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||||
|
final ProcessInfo processInfo = ProcessInfo.test(fs);
|
||||||
|
final ResidentRunner mockResidentRunner = MockResidentRunner();
|
||||||
|
when(mockResidentRunner.stayResident).thenReturn(true);
|
||||||
|
when(mockResidentRunner.supportsServiceProtocol).thenReturn(true);
|
||||||
|
when(mockResidentRunner.supportsRestart).thenReturn(true);
|
||||||
|
const String filename = 'test.pid';
|
||||||
|
final TerminalHandler terminalHandler = TerminalHandler(
|
||||||
|
mockResidentRunner,
|
||||||
|
logger: testLogger,
|
||||||
|
signals: signals,
|
||||||
|
terminal: terminal,
|
||||||
|
processInfo: processInfo,
|
||||||
|
reportReady: false,
|
||||||
|
pidFile: filename,
|
||||||
|
);
|
||||||
|
expect(fs.file(filename).existsSync(), isFalse);
|
||||||
|
terminalHandler.setupTerminal();
|
||||||
|
terminalHandler.registerSignalHandlers();
|
||||||
|
expect(fs.file(filename).existsSync(), isTrue);
|
||||||
|
terminalHandler.stop();
|
||||||
|
expect(fs.file(filename).existsSync(), isFalse);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockDevice extends Mock implements Device {
|
class MockDevice extends Mock implements Device {
|
||||||
@ -353,3 +395,35 @@ class TestRunner extends Mock implements ResidentRunner {
|
|||||||
bool enableDevTools = false,
|
bool enableDevTools = false,
|
||||||
}) async => null;
|
}) async => null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _TestSignals implements Signals {
|
||||||
|
_TestSignals(this.exitSignals);
|
||||||
|
|
||||||
|
final List<ProcessSignal> exitSignals;
|
||||||
|
|
||||||
|
final Map<ProcessSignal, Map<Object, SignalHandler>> _handlersTable =
|
||||||
|
<ProcessSignal, Map<Object, SignalHandler>>{};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object addHandler(ProcessSignal signal, SignalHandler handler) {
|
||||||
|
final Object token = Object();
|
||||||
|
_handlersTable.putIfAbsent(signal, () => <Object, SignalHandler>{})[token] = handler;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> removeHandler(ProcessSignal signal, Object token) async {
|
||||||
|
if (!_handlersTable.containsKey(signal)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!_handlersTable[signal].containsKey(token)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_handlersTable[signal].remove(token);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<Object> get errors => _errors.stream;
|
||||||
|
final StreamController<Object> _errors = StreamController<Object>();
|
||||||
|
}
|
||||||
|
@ -39,16 +39,6 @@ void main() {
|
|||||||
tryToDelete(tempDir);
|
tryToDelete(tempDir);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWithoutContext('writes pid-file', () async {
|
|
||||||
final File pidFile = tempDir.childFile('test.pid');
|
|
||||||
await _flutterRun.run(withDebugger: true);
|
|
||||||
await _flutterAttach.attach(
|
|
||||||
_flutterRun.vmServicePort,
|
|
||||||
pidFile: pidFile,
|
|
||||||
);
|
|
||||||
expect(pidFile.existsSync(), isTrue);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWithoutContext('can hot reload', () async {
|
testWithoutContext('can hot reload', () async {
|
||||||
await _flutterRun.run(withDebugger: true);
|
await _flutterRun.run(withDebugger: true);
|
||||||
await _flutterAttach.attach(_flutterRun.vmServicePort);
|
await _flutterAttach.attach(_flutterRun.vmServicePort);
|
||||||
|
@ -51,12 +51,6 @@ void main() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
testWithoutContext('flutter run writes pid-file', () async {
|
|
||||||
final File pidFile = tempDir.childFile('test.pid');
|
|
||||||
await _flutter.run(pidFile: pidFile);
|
|
||||||
expect(pidFile.existsSync(), isTrue);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWithoutContext('sets activeDevToolsServerAddress extension', () async {
|
testWithoutContext('sets activeDevToolsServerAddress extension', () async {
|
||||||
await _flutter.run(
|
await _flutter.run(
|
||||||
startPaused: true,
|
startPaused: true,
|
||||||
|
@ -103,36 +103,4 @@ void main() {
|
|||||||
|
|
||||||
expect(stdout.toString(), isNot(contains(_exceptionStart)));
|
expect(stdout.toString(), isNot(contains(_exceptionStart)));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWithoutContext('flutter run for web reports an early error in an application', () async {
|
|
||||||
final StringBuffer stdout = StringBuffer();
|
|
||||||
|
|
||||||
await _flutter.run(
|
|
||||||
startPaused: true,
|
|
||||||
withDebugger: true,
|
|
||||||
structuredErrors: true,
|
|
||||||
chrome: true,
|
|
||||||
machine: false,
|
|
||||||
);
|
|
||||||
await _flutter.resume();
|
|
||||||
final Completer<void> completer = Completer<void>();
|
|
||||||
bool lineFound = false;
|
|
||||||
|
|
||||||
await Future<void>(() async {
|
|
||||||
_flutter.stdout.listen((String line) {
|
|
||||||
stdout.writeln(line);
|
|
||||||
if (line.startsWith('Another exception was thrown') && !lineFound) {
|
|
||||||
lineFound = true;
|
|
||||||
completer.complete();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await completer.future;
|
|
||||||
}).timeout(const Duration(seconds: 15), onTimeout: () {
|
|
||||||
// Complete anyway in case we don't see the 'Another exception' line.
|
|
||||||
completer.complete();
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(stdout.toString(), contains(_exceptionStart));
|
|
||||||
await _flutter.stop();
|
|
||||||
}, skip: 'Running in cirrus environment causes premature exit');
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,587 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// The purpose of this test is to verify the end-to-end behavior of
|
||||||
|
// "flutter run" and other such commands, as closely as possible to
|
||||||
|
// the default behavior. To that end, it avoids the use of any test
|
||||||
|
// features that are not critical (-dflutter-test being the primary
|
||||||
|
// example of a test feature that it does use). For example, no use
|
||||||
|
// is made of "--machine" in these tests.
|
||||||
|
|
||||||
|
// There are a number of risks when it comes to writing a test such
|
||||||
|
// as this one. Typically these tests are hard to debug if they are
|
||||||
|
// in a failing condition, because they just hang as they await the
|
||||||
|
// next expected line that never comes. To avoid this, here we have
|
||||||
|
// the policy of looking for multiple lines, printing what expected
|
||||||
|
// lines were not seen when a short timeout expires (but timing out
|
||||||
|
// does not cause the test to fail, to reduce flakes), and wherever
|
||||||
|
// possible recording all output and comparing the actual output to
|
||||||
|
// the expected output only once the test is completed.
|
||||||
|
|
||||||
|
// To aid in debugging, consider passing the `debug: true` argument
|
||||||
|
// to the runFlutter function.
|
||||||
|
|
||||||
|
// @dart = 2.8
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:pedantic/pedantic.dart';
|
||||||
|
import 'package:process/process.dart';
|
||||||
|
|
||||||
|
import '../src/common.dart';
|
||||||
|
import 'test_utils.dart' show fileSystem;
|
||||||
|
|
||||||
|
const ProcessManager processManager = LocalProcessManager();
|
||||||
|
final String flutterRoot = getFlutterRoot();
|
||||||
|
final String flutterBin = fileSystem.path.join(flutterRoot, 'bin', 'flutter');
|
||||||
|
|
||||||
|
typedef LineHandler = String/*?*/ Function(String line);
|
||||||
|
|
||||||
|
abstract class Transition {
|
||||||
|
const Transition({this.handler, this.logging});
|
||||||
|
|
||||||
|
/// Callback that is invoked when the transition matches.
|
||||||
|
///
|
||||||
|
/// This should not throw, even if the test is failing. (For example, don't use "expect"
|
||||||
|
/// in these callbacks.) Throwing here would prevent the [runFlutter] function from running
|
||||||
|
/// to completion, which would leave zombie `flutter` processes around.
|
||||||
|
final LineHandler/*?*/ handler;
|
||||||
|
|
||||||
|
/// Whether to enable or disable logging when this transition is matched.
|
||||||
|
///
|
||||||
|
/// The default value, null, leaves the logging state unaffected.
|
||||||
|
final bool/*?*/ logging;
|
||||||
|
|
||||||
|
bool matches(String line);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
bool lineMatchesPattern(String line, Pattern pattern) {
|
||||||
|
if (pattern is String) {
|
||||||
|
return line == pattern;
|
||||||
|
}
|
||||||
|
return line.contains(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
String describe(Pattern pattern) {
|
||||||
|
if (pattern is String) {
|
||||||
|
return '"$pattern"';
|
||||||
|
}
|
||||||
|
if (pattern is RegExp) {
|
||||||
|
return '/${pattern.pattern}/';
|
||||||
|
}
|
||||||
|
return '$pattern';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Barrier extends Transition {
|
||||||
|
const Barrier(this.pattern, {LineHandler/*?*/ handler, bool/*?*/ logging}) : super(handler: handler, logging: logging);
|
||||||
|
final Pattern pattern;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool matches(String line) => lineMatchesPattern(line, pattern);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => describe(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Multiple extends Transition {
|
||||||
|
Multiple(List<Pattern> patterns, {
|
||||||
|
LineHandler/*?*/ handler,
|
||||||
|
bool/*?*/ logging,
|
||||||
|
}) : _originalPatterns = patterns,
|
||||||
|
patterns = patterns.toList(),
|
||||||
|
super(handler: handler, logging: logging);
|
||||||
|
|
||||||
|
final List<Pattern> _originalPatterns;
|
||||||
|
final List<Pattern> patterns;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool matches(String line) {
|
||||||
|
for (int index = 0; index < patterns.length; index += 1) {
|
||||||
|
if (lineMatchesPattern(line, patterns[index])) {
|
||||||
|
patterns.removeAt(index);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return patterns.isEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return _originalPatterns.map(describe).join(', ') + ' (matched ${_originalPatterns.length - patterns.length} so far)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProcessTestResult {
|
||||||
|
const ProcessTestResult(this.exitCode, this.stdout, this.stderr);
|
||||||
|
final int exitCode;
|
||||||
|
final List<String> stdout;
|
||||||
|
final List<String> stderr;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'exit code $exitCode\nstdout:\n ${stdout.join('\n ')}\nstderr:\n ${stderr.join('\n ')}\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
String clarify(String line) {
|
||||||
|
return line.runes.map<String>((int rune) {
|
||||||
|
if (rune >= 0x20 && rune <= 0x7F) {
|
||||||
|
return String.fromCharCode(rune);
|
||||||
|
}
|
||||||
|
switch (rune) {
|
||||||
|
case 0x00: return '<NUL>';
|
||||||
|
case 0x07: return '<BEL>';
|
||||||
|
case 0x08: return '<TAB>';
|
||||||
|
case 0x09: return '<BS>';
|
||||||
|
case 0x0A: return '<LF>';
|
||||||
|
case 0x0D: return '<CR>';
|
||||||
|
}
|
||||||
|
return '<${rune.toRadixString(16).padLeft(rune <= 0xFF ? 2 : rune <= 0xFFFF ? 4 : 5, '0')}>';
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
void printClearly(String line) {
|
||||||
|
print(clarify(line));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ProcessTestResult> runFlutter(
|
||||||
|
List<String> arguments,
|
||||||
|
String workingDirectory,
|
||||||
|
List<Transition> transitions, {
|
||||||
|
bool debug = false,
|
||||||
|
bool logging = true,
|
||||||
|
Duration expectedMaxDuration = const Duration(seconds: 25), // must be less than test timeout of 30 seconds!
|
||||||
|
}) async {
|
||||||
|
final Process process = await processManager.start(
|
||||||
|
<String>[flutterBin, ...arguments],
|
||||||
|
workingDirectory: workingDirectory,
|
||||||
|
);
|
||||||
|
final List<String> stdoutLog = <String>[];
|
||||||
|
final List<String> stderrLog = <String>[];
|
||||||
|
final List<String> stdinLog = <String>[];
|
||||||
|
int nextTransition = 0;
|
||||||
|
void describeStatus() {
|
||||||
|
if (transitions.isNotEmpty) {
|
||||||
|
print('Expected state transitions:');
|
||||||
|
for (int index = 0; index < transitions.length; index += 1) {
|
||||||
|
print(
|
||||||
|
'${index.toString().padLeft(5)} '
|
||||||
|
'${index < nextTransition ? 'ALREADY MATCHED ' :
|
||||||
|
index == nextTransition ? 'NOW WAITING FOR>' :
|
||||||
|
' '} ${transitions[index]}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (stdoutLog.isEmpty && stderrLog.isEmpty && stdinLog.isEmpty) {
|
||||||
|
print('So far nothing has been logged${ debug ? "" : "; use debug:true to print all output" }.');
|
||||||
|
} else {
|
||||||
|
print('Log${ debug ? "" : " (only contains logged lines; use debug:true to print all output)" }:');
|
||||||
|
stdoutLog.map<String>((String line) => 'stdout: $line').forEach(printClearly);
|
||||||
|
stderrLog.map<String>((String line) => 'stderr: $line').forEach(printClearly);
|
||||||
|
stdinLog.map<String>((String line) => 'stdin: $line').forEach(printClearly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool streamingLogs = false;
|
||||||
|
Timer/*?*/ timeout;
|
||||||
|
void processTimeout() {
|
||||||
|
if (!streamingLogs) {
|
||||||
|
streamingLogs = true;
|
||||||
|
if (!debug) {
|
||||||
|
print('Test is taking a long time.');
|
||||||
|
}
|
||||||
|
describeStatus();
|
||||||
|
print('(streaming all logs from this point on...)');
|
||||||
|
} else {
|
||||||
|
print('(taking a long time...)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void processStdout(String line) {
|
||||||
|
if (logging) {
|
||||||
|
stdoutLog.add(line);
|
||||||
|
}
|
||||||
|
if (streamingLogs) {
|
||||||
|
print('stdout: $line');
|
||||||
|
}
|
||||||
|
if (nextTransition < transitions.length && transitions[nextTransition].matches(line)) {
|
||||||
|
if (streamingLogs) {
|
||||||
|
print('(matched ${transitions[nextTransition]})');
|
||||||
|
}
|
||||||
|
if (transitions[nextTransition].logging != null) {
|
||||||
|
if (!logging && transitions[nextTransition].logging/*!*/) {
|
||||||
|
stdoutLog.add(line);
|
||||||
|
}
|
||||||
|
logging = transitions[nextTransition].logging/*!*/;
|
||||||
|
if (streamingLogs) {
|
||||||
|
if (logging) {
|
||||||
|
print('(enabled logging)');
|
||||||
|
} else {
|
||||||
|
print('(disabled logging)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (transitions[nextTransition].handler != null) {
|
||||||
|
final String/*?*/ command = transitions[nextTransition].handler/*!*/(line);
|
||||||
|
if (command != null) {
|
||||||
|
stdinLog.add(command);
|
||||||
|
if (streamingLogs) {
|
||||||
|
print('stdin: $command');
|
||||||
|
}
|
||||||
|
process.stdin.write(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nextTransition += 1;
|
||||||
|
timeout?.cancel();
|
||||||
|
timeout = Timer(expectedMaxDuration ~/ 5, processTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void processStderr(String line) {
|
||||||
|
stderrLog.add(line);
|
||||||
|
if (streamingLogs) {
|
||||||
|
print('stderr: $line');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (debug) {
|
||||||
|
processTimeout();
|
||||||
|
} else {
|
||||||
|
timeout = Timer(expectedMaxDuration ~/ 2, processTimeout);
|
||||||
|
}
|
||||||
|
process.stdout.transform<String>(utf8.decoder).transform<String>(const LineSplitter()).listen(processStdout);
|
||||||
|
process.stderr.transform<String>(utf8.decoder).transform<String>(const LineSplitter()).listen(processStderr);
|
||||||
|
unawaited(process.exitCode.timeout(expectedMaxDuration, onTimeout: () {
|
||||||
|
print('(process is not quitting, trying to send a "q" just in case that helps)');
|
||||||
|
print('(a functional test should never reach this point)');
|
||||||
|
process.stdin.write('q');
|
||||||
|
return null;
|
||||||
|
}).catchError((Object error) { /* ignore the error here, it'll be reported on the next line */ }));
|
||||||
|
final int exitCode = await process.exitCode;
|
||||||
|
if (streamingLogs) {
|
||||||
|
print('(process terminated with exit code $exitCode)');
|
||||||
|
}
|
||||||
|
timeout?.cancel();
|
||||||
|
if (nextTransition < transitions.length) {
|
||||||
|
print('The subprocess terminated before all the expected transitions had been matched.');
|
||||||
|
if (stderrLog.any((String line) => line.contains('Oops; flutter has exited unexpectedly:'))) {
|
||||||
|
print('The subprocess may in fact have crashed. Check the stderr logs below.');
|
||||||
|
}
|
||||||
|
print('The transition that we were hoping to see next but that we never saw was:');
|
||||||
|
print('${nextTransition.toString().padLeft(5)} NOW WAITING FOR> ${transitions[nextTransition]}');
|
||||||
|
if (!streamingLogs) {
|
||||||
|
describeStatus();
|
||||||
|
print('(process terminated with exit code $exitCode)');
|
||||||
|
}
|
||||||
|
throw TestFailure('Missed some expected transitions.');
|
||||||
|
}
|
||||||
|
if (streamingLogs) {
|
||||||
|
print('(completed execution successfully!)');
|
||||||
|
}
|
||||||
|
return ProcessTestResult(exitCode, stdoutLog, stderrLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWithoutContext('flutter run writes and clears pidfile appropriately', () async {
|
||||||
|
final String tempDirectory = fileSystem.systemTempDirectory.createTempSync('flutter_overall_experience_test.').resolveSymbolicLinksSync();
|
||||||
|
final String pidFile = fileSystem.path.join(tempDirectory, 'flutter.pid');
|
||||||
|
final String testDirectory = fileSystem.path.join(flutterRoot, 'examples', 'hello_world');
|
||||||
|
bool/*?*/ existsDuringTest;
|
||||||
|
try {
|
||||||
|
expect(fileSystem.file(pidFile).existsSync(), isFalse);
|
||||||
|
final ProcessTestResult result = await runFlutter(
|
||||||
|
<String>['run', '-dflutter-tester', '--pid-file', pidFile],
|
||||||
|
testDirectory,
|
||||||
|
<Transition>[
|
||||||
|
Barrier('q Quit (terminate the application on the device).', handler: (String line) {
|
||||||
|
existsDuringTest = fileSystem.file(pidFile).existsSync();
|
||||||
|
return 'q';
|
||||||
|
}),
|
||||||
|
const Barrier('Application finished.'),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
expect(existsDuringTest, isNot(isNull));
|
||||||
|
expect(existsDuringTest, isTrue);
|
||||||
|
expect(result.exitCode, 0, reason: 'subprocess failed; $result');
|
||||||
|
expect(fileSystem.file(pidFile).existsSync(), isFalse);
|
||||||
|
// This first test ignores the stdout and stderr, so that if the
|
||||||
|
// first run outputs "building flutter", or the "there's a new
|
||||||
|
// flutter" banner, or other such first-run messages, they won't
|
||||||
|
// fail the tests. This does mean that running this test first is
|
||||||
|
// actually important in the case where you're running the tests
|
||||||
|
// manually. (On CI, all those messages are expected to be seen
|
||||||
|
// long before we get here, e.g. because we run "flutter doctor".)
|
||||||
|
} finally {
|
||||||
|
tryToDelete(fileSystem.directory(tempDirectory));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('flutter run handle SIGUSR1/2', () async {
|
||||||
|
final String tempDirectory = fileSystem.systemTempDirectory.createTempSync('flutter_overall_experience_test.').resolveSymbolicLinksSync();
|
||||||
|
final String pidFile = fileSystem.path.join(tempDirectory, 'flutter.pid');
|
||||||
|
final String testDirectory = fileSystem.path.join(flutterRoot, 'dev', 'integration_tests', 'ui');
|
||||||
|
final String testScript = fileSystem.path.join('lib', 'commands.dart');
|
||||||
|
/*late*/ int pid;
|
||||||
|
try {
|
||||||
|
final ProcessTestResult result = await runFlutter(
|
||||||
|
<String>['run', '-dflutter-tester', '--report-ready', '--pid-file', pidFile, '--no-devtools', testScript],
|
||||||
|
testDirectory,
|
||||||
|
<Transition>[
|
||||||
|
Barrier('Flutter run key commands.', handler: (String line) {
|
||||||
|
pid = int.parse(fileSystem.file(pidFile).readAsStringSync());
|
||||||
|
processManager.killPid(pid, ProcessSignal.sigusr1);
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
|
Barrier(RegExp(r'^Performing hot reload\.\.\.'), logging: true),
|
||||||
|
Multiple(<Pattern>[RegExp(r'^Reloaded 0 libraries in [0-9]+ms\.$'), /*'called reassemble', (see TODO below)*/ 'called paint'], handler: (String line) {
|
||||||
|
processManager.killPid(pid, ProcessSignal.sigusr2);
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
|
Barrier(RegExp(r'^Performing hot restart\.\.\.')),
|
||||||
|
Multiple(<Pattern>[RegExp(r'^Restarted application in [0-9]+ms.$'), 'called main', 'called paint'], handler: (String line) {
|
||||||
|
return 'q';
|
||||||
|
}),
|
||||||
|
const Barrier('Application finished.'),
|
||||||
|
],
|
||||||
|
logging: false, // we ignore leading log lines to avoid making this test sensitive to e.g. the help message text
|
||||||
|
);
|
||||||
|
// We check the output from the app (all starts with "called ...") and the output from the tool
|
||||||
|
// (everything else) separately, because their relative timing isn't guaranteed. Their rough timing
|
||||||
|
// is verified by the expected transitions above.
|
||||||
|
// TODO(ianh): Fix the tool so that the output isn't garbled (right now we're putting debug output from
|
||||||
|
// the app on the line where we're spinning the busy signal, rather than adding a newline).
|
||||||
|
expect(result.stdout.where((String line) => line.startsWith('called ') && line != 'called reassemble' /* see todo above*/), <Object>[
|
||||||
|
// logs start after we receive the response to sending SIGUSR1
|
||||||
|
// SIGUSR1:
|
||||||
|
// 'called reassemble', // see todo above, this only sometimes gets included, other times it's on the "performing..." line
|
||||||
|
'called paint',
|
||||||
|
// SIGUSR2:
|
||||||
|
'called main',
|
||||||
|
'called paint',
|
||||||
|
]);
|
||||||
|
expect(result.stdout.where((String line) => !line.startsWith('called ')), <Object>[
|
||||||
|
// logs start after we receive the response to sending SIGUSR1
|
||||||
|
startsWith('Performing hot reload...'), // see todo above, this sometimes ends with "called reassemble"
|
||||||
|
'', // this newline is probably the misplaced one for the reassemble; see todo above
|
||||||
|
startsWith('Reloaded 0 libraries in '),
|
||||||
|
'Performing hot restart... ',
|
||||||
|
startsWith('Restarted application in '),
|
||||||
|
'', // this newline is the one for after we hit "q"
|
||||||
|
'Application finished.',
|
||||||
|
'ready',
|
||||||
|
]);
|
||||||
|
expect(result.exitCode, 0);
|
||||||
|
} finally {
|
||||||
|
tryToDelete(fileSystem.directory(tempDirectory));
|
||||||
|
}
|
||||||
|
}, skip: Platform.isWindows); // Windows doesn't support sending signals.
|
||||||
|
|
||||||
|
testWithoutContext('flutter run can hot reload and hot restart, handle "p" key', () async {
|
||||||
|
final String tempDirectory = fileSystem.systemTempDirectory.createTempSync('flutter_overall_experience_test.').resolveSymbolicLinksSync();
|
||||||
|
final String testDirectory = fileSystem.path.join(flutterRoot, 'dev', 'integration_tests', 'ui');
|
||||||
|
final String testScript = fileSystem.path.join('lib', 'commands.dart');
|
||||||
|
try {
|
||||||
|
final ProcessTestResult result = await runFlutter(
|
||||||
|
<String>['run', '-dflutter-tester', '--report-ready', '--no-devtools', testScript],
|
||||||
|
testDirectory,
|
||||||
|
<Transition>[
|
||||||
|
Multiple(<Pattern>['Flutter run key commands.', 'called main'], handler: (String line) {
|
||||||
|
return 'r';
|
||||||
|
}),
|
||||||
|
Barrier(RegExp(r'^Performing hot reload\.\.\.'), logging: true),
|
||||||
|
Multiple(<Pattern>['ready', /*'reassemble', (see todo below)*/ 'called paint'], handler: (String line) {
|
||||||
|
return 'R';
|
||||||
|
}),
|
||||||
|
Barrier(RegExp(r'^Performing hot restart\.\.\.')),
|
||||||
|
Multiple(<Pattern>['ready', 'called main', 'called paint'], handler: (String line) {
|
||||||
|
return 'p';
|
||||||
|
}),
|
||||||
|
Multiple(<Pattern>['ready', 'called paint', 'called debugPaintSize'], handler: (String line) {
|
||||||
|
return 'p';
|
||||||
|
}),
|
||||||
|
Multiple(<Pattern>['ready', 'called paint'], handler: (String line) {
|
||||||
|
return 'q';
|
||||||
|
}),
|
||||||
|
const Barrier('Application finished.'),
|
||||||
|
],
|
||||||
|
logging: false, // we ignore leading log lines to avoid making this test sensitive to e.g. the help message text
|
||||||
|
);
|
||||||
|
// We check the output from the app (all starts with "called ...") and the output from the tool
|
||||||
|
// (everything else) separately, because their relative timing isn't guaranteed. Their rough timing
|
||||||
|
// is verified by the expected transitions above.
|
||||||
|
// TODO(ianh): Fix the tool so that the output isn't garbled (right now we're putting debug output from
|
||||||
|
// the app on the line where we're spinning the busy signal, rather than adding a newline).
|
||||||
|
expect(result.stdout.where((String line) => line.startsWith('called ') && line != 'called reassemble' /* see todo above*/), <Object>[
|
||||||
|
// hot reload:
|
||||||
|
// 'called reassemble', // see todo above, this sometimes gets placed on the "Performing hot reload..." line
|
||||||
|
'called paint',
|
||||||
|
// hot restart:
|
||||||
|
'called main',
|
||||||
|
'called paint',
|
||||||
|
// debugPaintSizeEnabled = true:
|
||||||
|
'called paint',
|
||||||
|
'called debugPaintSize',
|
||||||
|
// debugPaintSizeEnabled = false:
|
||||||
|
'called paint',
|
||||||
|
]);
|
||||||
|
expect(result.stdout.where((String line) => !line.startsWith('called ')), <Object>[
|
||||||
|
// logs start after we receive the response to hitting "r"
|
||||||
|
startsWith('Performing hot reload...'), // see todo above, this sometimes ends with "called reassemble"
|
||||||
|
'', // this newline is probably the misplaced one for the reassemble; see todo above
|
||||||
|
startsWith('Reloaded 0 libraries in '),
|
||||||
|
'ready',
|
||||||
|
'', // this newline is the one for after we hit "R"
|
||||||
|
'Performing hot restart... ',
|
||||||
|
startsWith('Restarted application in '),
|
||||||
|
'ready',
|
||||||
|
'', // newline for after we hit "p" the first time
|
||||||
|
'ready',
|
||||||
|
'', // newline for after we hit "p" the second time
|
||||||
|
'ready',
|
||||||
|
'', // this newline is the one for after we hit "q"
|
||||||
|
'Application finished.',
|
||||||
|
'ready',
|
||||||
|
]);
|
||||||
|
expect(result.exitCode, 0);
|
||||||
|
} finally {
|
||||||
|
tryToDelete(fileSystem.directory(tempDirectory));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('flutter error messages include a DevTools link', () async {
|
||||||
|
final String tempDirectory = fileSystem.systemTempDirectory.createTempSync('flutter_overall_experience_test.').resolveSymbolicLinksSync();
|
||||||
|
final String testDirectory = fileSystem.path.join(flutterRoot, 'dev', 'integration_tests', 'ui');
|
||||||
|
final String testScript = fileSystem.path.join('lib', 'overflow.dart');
|
||||||
|
try {
|
||||||
|
final ProcessTestResult result = await runFlutter(
|
||||||
|
<String>['run', '-dflutter-tester', testScript],
|
||||||
|
testDirectory,
|
||||||
|
<Transition>[
|
||||||
|
Barrier(RegExp(r'^An Observatory debugger and profiler on Flutter test device is available at: ')),
|
||||||
|
Barrier(RegExp(r'^The Flutter DevTools debugger and profiler on Flutter test device is available at: '), handler: (String line) {
|
||||||
|
return 'r';
|
||||||
|
}),
|
||||||
|
Barrier(RegExp(r'^Performing hot reload\.\.\.'), logging: true),
|
||||||
|
Barrier(RegExp(r'^Reloaded 0 libraries in [0-9]+ms.'), handler: (String line) {
|
||||||
|
return 'q';
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
logging: false,
|
||||||
|
);
|
||||||
|
expect(result.exitCode, 0);
|
||||||
|
expect(result.stdout, <Object>[
|
||||||
|
startsWith('Performing hot reload...'),
|
||||||
|
'══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════',
|
||||||
|
'The following assertion was thrown during layout:',
|
||||||
|
'A RenderFlex overflowed by 69200 pixels on the right.',
|
||||||
|
'',
|
||||||
|
'The relevant error-causing widget was:',
|
||||||
|
matches(RegExp(r'^ Row .+flutter/dev/integration_tests/ui/lib/overflow\.dart:31:12$')),
|
||||||
|
'',
|
||||||
|
'To inspect this widget in Flutter DevTools, visit:',
|
||||||
|
startsWith('http'),
|
||||||
|
'',
|
||||||
|
'The overflowing RenderFlex has an orientation of Axis.horizontal.',
|
||||||
|
'The edge of the RenderFlex that is overflowing has been marked in the rendering with a yellow and',
|
||||||
|
'black striped pattern. This is usually caused by the contents being too big for the RenderFlex.',
|
||||||
|
'Consider applying a flex factor (e.g. using an Expanded widget) to force the children of the',
|
||||||
|
'RenderFlex to fit within the available space instead of being sized to their natural size.',
|
||||||
|
'This is considered an error condition because it indicates that there is content that cannot be',
|
||||||
|
'seen. If the content is legitimately bigger than the available space, consider clipping it with a',
|
||||||
|
'ClipRect widget before putting it in the flex, or using a scrollable container rather than a Flex,',
|
||||||
|
'like a ListView.',
|
||||||
|
matches(RegExp(r'^The specific RenderFlex in question is: RenderFlex#..... OVERFLOWING:$')),
|
||||||
|
startsWith(' creator: Row ← Test ← '),
|
||||||
|
contains(' ← '),
|
||||||
|
endsWith(' ← ⋯'),
|
||||||
|
' parentData: <none> (can use size)',
|
||||||
|
' constraints: BoxConstraints(w=800.0, h=600.0)',
|
||||||
|
' size: Size(800.0, 600.0)',
|
||||||
|
' direction: horizontal',
|
||||||
|
' mainAxisAlignment: start',
|
||||||
|
' mainAxisSize: max',
|
||||||
|
' crossAxisAlignment: center',
|
||||||
|
' textDirection: ltr',
|
||||||
|
' verticalDirection: down',
|
||||||
|
'◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤',
|
||||||
|
'════════════════════════════════════════════════════════════════════════════════════════════════════',
|
||||||
|
'',
|
||||||
|
startsWith('Reloaded 0 libraries in '),
|
||||||
|
'',
|
||||||
|
'Application finished.',
|
||||||
|
]);
|
||||||
|
} finally {
|
||||||
|
tryToDelete(fileSystem.directory(tempDirectory));
|
||||||
|
}
|
||||||
|
}, skip: 'DevTools does not reliably launch on bots currently.'); // TODO(ianh): fix and re-enable test.
|
||||||
|
|
||||||
|
testWithoutContext('flutter run help output', () async {
|
||||||
|
// This test enables all logging so that it checks the exact text of starting up an application.
|
||||||
|
// The idea is to verify that we're not outputting spurious messages.
|
||||||
|
// WHEN EDITING THIS TEST PLEASE CAREFULLY CONSIDER WHETHER THE NEW OUTPUT IS AN IMPROVEMENT.
|
||||||
|
final String testDirectory = fileSystem.path.join(flutterRoot, 'examples', 'hello_world');
|
||||||
|
final RegExp finalLine = RegExp(r'^An Observatory'); /* RegExp(r'^The Flutter DevTools'); */ // TODO(ianh): use this when enabling devtools
|
||||||
|
final ProcessTestResult result = await runFlutter(
|
||||||
|
<String>['run', '-dflutter-tester', '--no-devtools'], // TODO(ianh): enable devtools
|
||||||
|
testDirectory,
|
||||||
|
<Transition>[
|
||||||
|
Barrier(finalLine, handler: (String line) {
|
||||||
|
return 'h';
|
||||||
|
}),
|
||||||
|
Barrier(finalLine, handler: (String line) {
|
||||||
|
return 'q';
|
||||||
|
}),
|
||||||
|
const Barrier('Application finished.'),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
expect(result.exitCode, 0);
|
||||||
|
expect(result.stderr, isEmpty);
|
||||||
|
expect(result.stdout, <Object>[
|
||||||
|
startsWith('Launching '),
|
||||||
|
startsWith('Syncing files to device Flutter test device...'),
|
||||||
|
'',
|
||||||
|
'Flutter run key commands.',
|
||||||
|
startsWith('r Hot reload.'),
|
||||||
|
'R Hot restart.',
|
||||||
|
'h Repeat this help message.',
|
||||||
|
'd Detach (terminate "flutter run" but leave application running).',
|
||||||
|
'c Clear the screen',
|
||||||
|
'q Quit (terminate the application on the device).',
|
||||||
|
'',
|
||||||
|
contains('Running with sound null safety'),
|
||||||
|
'',
|
||||||
|
startsWith('An Observatory debugger and profiler on Flutter test device is available at: http://'),
|
||||||
|
/* startsWith('The Flutter DevTools debugger and profiler on Flutter test device is available at: http://'), */ // TODO(ianh): enable devtools
|
||||||
|
'',
|
||||||
|
'Flutter run key commands.',
|
||||||
|
startsWith('r Hot reload.'),
|
||||||
|
'R Hot restart.',
|
||||||
|
'h Repeat this help message.',
|
||||||
|
'd Detach (terminate "flutter run" but leave application running).',
|
||||||
|
'c Clear the screen',
|
||||||
|
'q Quit (terminate the application on the device).',
|
||||||
|
'b Toggle the platform brightness setting (dark and light mode). (debugBrightnessOverride)',
|
||||||
|
'w Dump widget hierarchy to the console. (debugDumpApp)',
|
||||||
|
't Dump rendering tree to the console. (debugDumpRenderTree)',
|
||||||
|
'L Dump layer tree to the console. (debugDumpLayerTree)',
|
||||||
|
'S Dump accessibility tree in traversal order. (debugDumpSemantics)',
|
||||||
|
'U Dump accessibility tree in inverse hit test order. (debugDumpSemantics)',
|
||||||
|
'i Toggle widget inspector. (WidgetsApp.showWidgetInspectorOverride)',
|
||||||
|
startsWith('I Toggle oversized image inversion'),
|
||||||
|
'p Toggle the display of construction lines. (debugPaintSizeEnabled)',
|
||||||
|
'o Simulate different operating systems. (defaultTargetPlatform)',
|
||||||
|
'z Toggle elevation checker.',
|
||||||
|
'g Run source code generators.',
|
||||||
|
'M Write SkSL shaders to a unique file in the project directory.',
|
||||||
|
'v Launch DevTools.', // TODO(ianh): hide this when devtools isn't available
|
||||||
|
'P Toggle performance overlay. (WidgetsApp.showPerformanceOverlay)',
|
||||||
|
'a Toggle timeline events for all widget build methods. (debugProfileWidgetBuilds)',
|
||||||
|
'',
|
||||||
|
contains('Running with sound null safety'),
|
||||||
|
'',
|
||||||
|
startsWith('An Observatory debugger and profiler on Flutter test device is available at: http://'),
|
||||||
|
/* startsWith('The Flutter DevTools debugger and profiler on Flutter test device is available at: http://'), */ // TODO(ianh): enable devtools
|
||||||
|
'',
|
||||||
|
'Application finished.',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
@ -86,7 +86,6 @@ abstract class FlutterTestDriver {
|
|||||||
List<String> arguments, {
|
List<String> arguments, {
|
||||||
String script,
|
String script,
|
||||||
bool withDebugger = false,
|
bool withDebugger = false,
|
||||||
File pidFile,
|
|
||||||
bool singleWidgetReloads = false,
|
bool singleWidgetReloads = false,
|
||||||
}) async {
|
}) async {
|
||||||
final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
|
final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
|
||||||
@ -96,9 +95,6 @@ abstract class FlutterTestDriver {
|
|||||||
if (_printDebugOutputToStdOut) {
|
if (_printDebugOutputToStdOut) {
|
||||||
arguments.add('--verbose');
|
arguments.add('--verbose');
|
||||||
}
|
}
|
||||||
if (pidFile != null) {
|
|
||||||
arguments.addAll(<String>['--pid-file', pidFile.path]);
|
|
||||||
}
|
|
||||||
if (script != null) {
|
if (script != null) {
|
||||||
arguments.add(script);
|
arguments.add(script);
|
||||||
}
|
}
|
||||||
@ -467,9 +463,7 @@ class FlutterRunTestDriver extends FlutterTestDriver {
|
|||||||
bool chrome = false,
|
bool chrome = false,
|
||||||
bool expressionEvaluation = true,
|
bool expressionEvaluation = true,
|
||||||
bool structuredErrors = false,
|
bool structuredErrors = false,
|
||||||
bool machine = true,
|
|
||||||
bool singleWidgetReloads = false,
|
bool singleWidgetReloads = false,
|
||||||
File pidFile,
|
|
||||||
String script,
|
String script,
|
||||||
List<String> additionalCommandArgs,
|
List<String> additionalCommandArgs,
|
||||||
}) async {
|
}) async {
|
||||||
@ -478,7 +472,7 @@ class FlutterRunTestDriver extends FlutterTestDriver {
|
|||||||
'run',
|
'run',
|
||||||
if (!chrome)
|
if (!chrome)
|
||||||
'--disable-service-auth-codes',
|
'--disable-service-auth-codes',
|
||||||
if (machine) '--machine',
|
'--machine',
|
||||||
if (!spawnDdsInstance) '--disable-dds',
|
if (!spawnDdsInstance) '--disable-dds',
|
||||||
...getLocalEngineArguments(),
|
...getLocalEngineArguments(),
|
||||||
'-d',
|
'-d',
|
||||||
@ -497,7 +491,6 @@ class FlutterRunTestDriver extends FlutterTestDriver {
|
|||||||
withDebugger: withDebugger,
|
withDebugger: withDebugger,
|
||||||
startPaused: startPaused,
|
startPaused: startPaused,
|
||||||
pauseOnExceptions: pauseOnExceptions,
|
pauseOnExceptions: pauseOnExceptions,
|
||||||
pidFile: pidFile,
|
|
||||||
script: script,
|
script: script,
|
||||||
singleWidgetReloads: singleWidgetReloads,
|
singleWidgetReloads: singleWidgetReloads,
|
||||||
);
|
);
|
||||||
@ -508,7 +501,6 @@ class FlutterRunTestDriver extends FlutterTestDriver {
|
|||||||
bool withDebugger = false,
|
bool withDebugger = false,
|
||||||
bool startPaused = false,
|
bool startPaused = false,
|
||||||
bool pauseOnExceptions = false,
|
bool pauseOnExceptions = false,
|
||||||
File pidFile,
|
|
||||||
bool singleWidgetReloads = false,
|
bool singleWidgetReloads = false,
|
||||||
List<String> additionalCommandArgs,
|
List<String> additionalCommandArgs,
|
||||||
}) async {
|
}) async {
|
||||||
@ -529,7 +521,6 @@ class FlutterRunTestDriver extends FlutterTestDriver {
|
|||||||
withDebugger: withDebugger,
|
withDebugger: withDebugger,
|
||||||
startPaused: startPaused,
|
startPaused: startPaused,
|
||||||
pauseOnExceptions: pauseOnExceptions,
|
pauseOnExceptions: pauseOnExceptions,
|
||||||
pidFile: pidFile,
|
|
||||||
singleWidgetReloads: singleWidgetReloads,
|
singleWidgetReloads: singleWidgetReloads,
|
||||||
attachPort: port,
|
attachPort: port,
|
||||||
);
|
);
|
||||||
@ -543,7 +534,6 @@ class FlutterRunTestDriver extends FlutterTestDriver {
|
|||||||
bool startPaused = false,
|
bool startPaused = false,
|
||||||
bool pauseOnExceptions = false,
|
bool pauseOnExceptions = false,
|
||||||
bool singleWidgetReloads = false,
|
bool singleWidgetReloads = false,
|
||||||
File pidFile,
|
|
||||||
int attachPort,
|
int attachPort,
|
||||||
}) async {
|
}) async {
|
||||||
assert(!startPaused || withDebugger);
|
assert(!startPaused || withDebugger);
|
||||||
@ -551,7 +541,6 @@ class FlutterRunTestDriver extends FlutterTestDriver {
|
|||||||
args,
|
args,
|
||||||
script: script,
|
script: script,
|
||||||
withDebugger: withDebugger,
|
withDebugger: withDebugger,
|
||||||
pidFile: pidFile,
|
|
||||||
singleWidgetReloads: singleWidgetReloads,
|
singleWidgetReloads: singleWidgetReloads,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -738,7 +727,6 @@ class FlutterTestTestDriver extends FlutterTestDriver {
|
|||||||
bool withDebugger = false,
|
bool withDebugger = false,
|
||||||
bool pauseOnExceptions = false,
|
bool pauseOnExceptions = false,
|
||||||
bool coverage = false,
|
bool coverage = false,
|
||||||
File pidFile,
|
|
||||||
Future<void> Function() beforeStart,
|
Future<void> Function() beforeStart,
|
||||||
}) async {
|
}) async {
|
||||||
await _setupProcess(<String>[
|
await _setupProcess(<String>[
|
||||||
@ -748,7 +736,7 @@ class FlutterTestTestDriver extends FlutterTestDriver {
|
|||||||
'--machine',
|
'--machine',
|
||||||
if (coverage)
|
if (coverage)
|
||||||
'--coverage',
|
'--coverage',
|
||||||
], script: testFile, withDebugger: withDebugger, pauseOnExceptions: pauseOnExceptions, pidFile: pidFile, beforeStart: beforeStart);
|
], script: testFile, withDebugger: withDebugger, pauseOnExceptions: pauseOnExceptions, beforeStart: beforeStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -757,7 +745,6 @@ class FlutterTestTestDriver extends FlutterTestDriver {
|
|||||||
String script,
|
String script,
|
||||||
bool withDebugger = false,
|
bool withDebugger = false,
|
||||||
bool pauseOnExceptions = false,
|
bool pauseOnExceptions = false,
|
||||||
File pidFile,
|
|
||||||
Future<void> Function() beforeStart,
|
Future<void> Function() beforeStart,
|
||||||
bool singleWidgetReloads = false,
|
bool singleWidgetReloads = false,
|
||||||
}) async {
|
}) async {
|
||||||
@ -765,7 +752,6 @@ class FlutterTestTestDriver extends FlutterTestDriver {
|
|||||||
args,
|
args,
|
||||||
script: script,
|
script: script,
|
||||||
withDebugger: withDebugger,
|
withDebugger: withDebugger,
|
||||||
pidFile: pidFile,
|
|
||||||
singleWidgetReloads: singleWidgetReloads,
|
singleWidgetReloads: singleWidgetReloads,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user