Terminate non-detached test devices on flutter run
completion (#159170)
See https://github.com/flutter/flutter/issues/159154. See https://github.com/flutter/flutter/pull/159169. Before this PR, it appeared we were accidentally leaking (keeping active) `flutter_tester` instances (or any test device) after `flutter run` completion, even if the runner was not explicitly detached. I _think_ this is a bug, but I'll check with the tools team and possibly @jonahwilliams before finalizing this. /cc @jason-simmons --------- Co-authored-by: Andrew Kolos <andrewrkolos@gmail.com>
This commit is contained in:
parent
1d24fa8f60
commit
5ead4e15a7
@ -1118,6 +1118,16 @@ abstract class ResidentRunner extends ResidentHandlers {
|
|||||||
return 'main.dart${swap ? '.swap' : ''}.dill';
|
return 'main.dart${swap ? '.swap' : ''}.dill';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the app being instrumented by the runner should be stopped during
|
||||||
|
/// cleanup.
|
||||||
|
///
|
||||||
|
/// A detached app can happen one of two ways:
|
||||||
|
/// - [run] is used, and then the created application is manually [detach]ed;
|
||||||
|
/// - [attach] is used to explicitly connect to an already running app.
|
||||||
|
@protected
|
||||||
|
@visibleForTesting
|
||||||
|
bool stopAppDuringCleanup = true;
|
||||||
|
|
||||||
bool get debuggingEnabled => debuggingOptions.debuggingEnabled;
|
bool get debuggingEnabled => debuggingOptions.debuggingEnabled;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1254,7 +1264,10 @@ abstract class ResidentRunner extends ResidentHandlers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@mustCallSuper
|
||||||
Future<void> detach() async {
|
Future<void> detach() async {
|
||||||
|
stopAppDuringCleanup = false;
|
||||||
|
|
||||||
// TODO(bkonyi): remove when ready to serve DevTools from DDS.
|
// TODO(bkonyi): remove when ready to serve DevTools from DDS.
|
||||||
await residentDevtoolsHandler!.shutdown();
|
await residentDevtoolsHandler!.shutdown();
|
||||||
await stopEchoingDeviceLog();
|
await stopEchoingDeviceLog();
|
||||||
@ -1398,6 +1411,7 @@ abstract class ResidentRunner extends ResidentHandlers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
void appFinished() {
|
void appFinished() {
|
||||||
if (_finished.isCompleted) {
|
if (_finished.isCompleted) {
|
||||||
return;
|
return;
|
||||||
|
@ -125,9 +125,6 @@ class HotRunner extends ResidentRunner {
|
|||||||
/// reload process do not have this issue.
|
/// reload process do not have this issue.
|
||||||
bool _swap = false;
|
bool _swap = false;
|
||||||
|
|
||||||
/// Whether the resident runner has correctly attached to the running application.
|
|
||||||
bool _didAttach = false;
|
|
||||||
|
|
||||||
final Map<String, List<int>> benchmarkData = <String, List<int>>{};
|
final Map<String, List<int>> benchmarkData = <String, List<int>>{};
|
||||||
|
|
||||||
String? _targetPlatform;
|
String? _targetPlatform;
|
||||||
@ -220,15 +217,29 @@ class HotRunner extends ResidentRunner {
|
|||||||
throw Exception('Failed to compile $expression');
|
throw Exception('Failed to compile $expression');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the exit code of the flutter tool process, like [run].
|
|
||||||
@override
|
@override
|
||||||
|
@nonVirtual
|
||||||
Future<int> attach({
|
Future<int> attach({
|
||||||
Completer<DebugConnectionInfo>? connectionInfoCompleter,
|
Completer<DebugConnectionInfo>? connectionInfoCompleter,
|
||||||
Completer<void>? appStartedCompleter,
|
Completer<void>? appStartedCompleter,
|
||||||
bool allowExistingDdsInstance = false,
|
bool allowExistingDdsInstance = false,
|
||||||
bool needsFullRestart = true,
|
bool needsFullRestart = true,
|
||||||
}) async {
|
}) async {
|
||||||
_didAttach = true;
|
stopAppDuringCleanup = false;
|
||||||
|
return _attach(
|
||||||
|
connectionInfoCompleter: connectionInfoCompleter,
|
||||||
|
appStartedCompleter: appStartedCompleter,
|
||||||
|
allowExistingDdsInstance: allowExistingDdsInstance,
|
||||||
|
needsFullRestart: needsFullRestart,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> _attach({
|
||||||
|
Completer<DebugConnectionInfo>? connectionInfoCompleter,
|
||||||
|
Completer<void>? appStartedCompleter,
|
||||||
|
bool allowExistingDdsInstance = false,
|
||||||
|
bool needsFullRestart = true,
|
||||||
|
}) async {
|
||||||
try {
|
try {
|
||||||
await connectToServiceProtocol(
|
await connectToServiceProtocol(
|
||||||
reloadSources: _reloadSourcesService,
|
reloadSources: _reloadSourcesService,
|
||||||
@ -464,7 +475,7 @@ class HotRunner extends ResidentRunner {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return attach(
|
return _attach(
|
||||||
connectionInfoCompleter: connectionInfoCompleter,
|
connectionInfoCompleter: connectionInfoCompleter,
|
||||||
appStartedCompleter: appStartedCompleter,
|
appStartedCompleter: appStartedCompleter,
|
||||||
needsFullRestart: false,
|
needsFullRestart: false,
|
||||||
@ -1142,7 +1153,7 @@ class HotRunner extends ResidentRunner {
|
|||||||
} else {
|
} else {
|
||||||
commandHelp.hWithoutDetails.print();
|
commandHelp.hWithoutDetails.print();
|
||||||
}
|
}
|
||||||
if (_didAttach) {
|
if (stopAppDuringCleanup) {
|
||||||
commandHelp.d.print();
|
commandHelp.d.print();
|
||||||
}
|
}
|
||||||
commandHelp.c.print();
|
commandHelp.c.print();
|
||||||
@ -1239,11 +1250,10 @@ class HotRunner extends ResidentRunner {
|
|||||||
Future<void> cleanupAfterSignal() async {
|
Future<void> cleanupAfterSignal() async {
|
||||||
await stopEchoingDeviceLog();
|
await stopEchoingDeviceLog();
|
||||||
await hotRunnerConfig!.runPreShutdownOperations();
|
await hotRunnerConfig!.runPreShutdownOperations();
|
||||||
if (_didAttach) {
|
if (stopAppDuringCleanup) {
|
||||||
appFinished();
|
return exitApp();
|
||||||
} else {
|
|
||||||
await exitApp();
|
|
||||||
}
|
}
|
||||||
|
appFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import 'package:flutter_tools/src/application_package.dart';
|
import 'package:flutter_tools/src/application_package.dart';
|
||||||
import 'package:flutter_tools/src/asset.dart';
|
import 'package:flutter_tools/src/asset.dart';
|
||||||
|
import 'package:flutter_tools/src/base/dds.dart';
|
||||||
import 'package:flutter_tools/src/build_info.dart';
|
import 'package:flutter_tools/src/build_info.dart';
|
||||||
import 'package:flutter_tools/src/build_system/tools/shader_compiler.dart';
|
import 'package:flutter_tools/src/build_system/tools/shader_compiler.dart';
|
||||||
import 'package:flutter_tools/src/compile.dart';
|
import 'package:flutter_tools/src/compile.dart';
|
||||||
@ -52,6 +53,9 @@ class FakeDevice extends Fake implements Device {
|
|||||||
|
|
||||||
bool disposed = false;
|
bool disposed = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final DartDevelopmentService dds = _FakeDartDevelopmentService();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool isSupported() => true;
|
bool isSupported() => true;
|
||||||
|
|
||||||
@ -90,6 +94,11 @@ class FakeDevice extends Fake implements Device {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _FakeDartDevelopmentService extends Fake implements DartDevelopmentService {
|
||||||
|
@override
|
||||||
|
void shutdown() {}
|
||||||
|
}
|
||||||
|
|
||||||
class FakeFlutterDevice extends Fake implements FlutterDevice {
|
class FakeFlutterDevice extends Fake implements FlutterDevice {
|
||||||
FakeFlutterDevice(this.device);
|
FakeFlutterDevice(this.device);
|
||||||
|
|
||||||
|
@ -146,6 +146,9 @@ class FakeDartDevelopmentService extends Fake with DartDevelopmentServiceLocalOp
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Uri? get uri => null;
|
Uri? get uri => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void shutdown() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FakeDartDevelopmentServiceException implements DartDevelopmentServiceException {
|
class FakeDartDevelopmentServiceException implements DartDevelopmentServiceException {
|
||||||
|
@ -96,6 +96,12 @@ void main() {
|
|||||||
expect(fakeVmServiceHost?.hasRemainingExpectations, false);
|
expect(fakeVmServiceHost?.hasRemainingExpectations, false);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
testUsingContext('ResidentRunner reports whether detach() was used', () => testbed.run(() async {
|
||||||
|
expect(residentRunner.stopAppDuringCleanup, true);
|
||||||
|
await residentRunner.detach();
|
||||||
|
expect(residentRunner.stopAppDuringCleanup, false);
|
||||||
|
}));
|
||||||
|
|
||||||
testUsingContext('ResidentRunner suppresses errors for the initial compilation', () => testbed.run(() async {
|
testUsingContext('ResidentRunner suppresses errors for the initial compilation', () => testbed.run(() async {
|
||||||
globals.fs.file(globals.fs.path.join('lib', 'main.dart'))
|
globals.fs.file(globals.fs.path.join('lib', 'main.dart'))
|
||||||
.createSync(recursive: true);
|
.createSync(recursive: true);
|
||||||
@ -1302,6 +1308,7 @@ flutter:
|
|||||||
commandHelp.M,
|
commandHelp.M,
|
||||||
commandHelp.g,
|
commandHelp.g,
|
||||||
commandHelp.hWithDetails,
|
commandHelp.hWithDetails,
|
||||||
|
commandHelp.d,
|
||||||
commandHelp.c,
|
commandHelp.c,
|
||||||
commandHelp.q,
|
commandHelp.q,
|
||||||
'',
|
'',
|
||||||
@ -1331,6 +1338,7 @@ flutter:
|
|||||||
commandHelp.r,
|
commandHelp.r,
|
||||||
commandHelp.R,
|
commandHelp.R,
|
||||||
commandHelp.hWithoutDetails,
|
commandHelp.hWithoutDetails,
|
||||||
|
commandHelp.d,
|
||||||
commandHelp.c,
|
commandHelp.c,
|
||||||
commandHelp.q,
|
commandHelp.q,
|
||||||
'',
|
'',
|
||||||
|
@ -2,7 +2,12 @@
|
|||||||
// 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:file/memory.dart';
|
||||||
|
import 'package:flutter_tools/src/base/file_system.dart';
|
||||||
|
import 'package:flutter_tools/src/build_info.dart';
|
||||||
|
import 'package:flutter_tools/src/compile.dart';
|
||||||
import 'package:flutter_tools/src/devfs.dart';
|
import 'package:flutter_tools/src/devfs.dart';
|
||||||
|
import 'package:flutter_tools/src/device.dart';
|
||||||
import 'package:flutter_tools/src/reporting/reporting.dart';
|
import 'package:flutter_tools/src/reporting/reporting.dart';
|
||||||
import 'package:flutter_tools/src/resident_runner.dart';
|
import 'package:flutter_tools/src/resident_runner.dart';
|
||||||
import 'package:flutter_tools/src/run_hot.dart';
|
import 'package:flutter_tools/src/run_hot.dart';
|
||||||
@ -11,11 +16,13 @@ import 'package:test/fake.dart';
|
|||||||
import 'package:unified_analytics/unified_analytics.dart';
|
import 'package:unified_analytics/unified_analytics.dart';
|
||||||
import 'package:vm_service/vm_service.dart' as vm_service;
|
import 'package:vm_service/vm_service.dart' as vm_service;
|
||||||
|
|
||||||
//import '../src/context.dart';
|
|
||||||
import '../src/common.dart';
|
import '../src/common.dart';
|
||||||
|
import '../src/context.dart';
|
||||||
|
import 'hot_shared.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWithoutContext('defaultReloadSourcesHelper() handles empty DeviceReloadReports)', () {
|
testWithoutContext(
|
||||||
|
'defaultReloadSourcesHelper() handles empty DeviceReloadReports)', () {
|
||||||
defaultReloadSourcesHelper(
|
defaultReloadSourcesHelper(
|
||||||
_FakeHotRunner(),
|
_FakeHotRunner(),
|
||||||
<FlutterDevice?>[_FakeFlutterDevice()],
|
<FlutterDevice?>[_FakeFlutterDevice()],
|
||||||
@ -29,6 +36,88 @@ void main() {
|
|||||||
const NoOpAnalytics(),
|
const NoOpAnalytics(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('signal handling', () {
|
||||||
|
late _FakeHotCompatibleFlutterDevice flutterDevice;
|
||||||
|
late MemoryFileSystem fileSystem;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
flutterDevice = _FakeHotCompatibleFlutterDevice(FakeDevice());
|
||||||
|
fileSystem = MemoryFileSystem.test();
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext(
|
||||||
|
'kills the test device',
|
||||||
|
() async {
|
||||||
|
final HotRunner runner = HotRunner(
|
||||||
|
<FlutterDevice>[
|
||||||
|
flutterDevice,
|
||||||
|
],
|
||||||
|
target: 'main.dart',
|
||||||
|
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
|
||||||
|
analytics: _FakeAnalytics(),
|
||||||
|
);
|
||||||
|
|
||||||
|
await runner.run();
|
||||||
|
await runner.cleanupAfterSignal();
|
||||||
|
expect(flutterDevice.wasExited, true);
|
||||||
|
},
|
||||||
|
overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => fileSystem,
|
||||||
|
ProcessManager: FakeProcessManager.empty,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testUsingContext(
|
||||||
|
'kill with a detach keeps the test device running',
|
||||||
|
() async {
|
||||||
|
final HotRunner runner = HotRunner(
|
||||||
|
<FlutterDevice>[
|
||||||
|
flutterDevice,
|
||||||
|
],
|
||||||
|
target: 'main.dart',
|
||||||
|
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
|
||||||
|
analytics: _FakeAnalytics(),
|
||||||
|
);
|
||||||
|
|
||||||
|
await runner.run();
|
||||||
|
await runner.detach();
|
||||||
|
await runner.cleanupAfterSignal();
|
||||||
|
expect(flutterDevice.wasExited, false);
|
||||||
|
},
|
||||||
|
overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => fileSystem,
|
||||||
|
ProcessManager: FakeProcessManager.empty,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testUsingContext(
|
||||||
|
'kill on an attached device keeps the test device running',
|
||||||
|
() async {
|
||||||
|
final HotRunner runner = HotRunner(
|
||||||
|
<FlutterDevice>[
|
||||||
|
flutterDevice,
|
||||||
|
],
|
||||||
|
target: 'main.dart',
|
||||||
|
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
|
||||||
|
analytics: _FakeAnalytics(),
|
||||||
|
);
|
||||||
|
|
||||||
|
await runner.attach();
|
||||||
|
await runner.cleanupAfterSignal();
|
||||||
|
expect(flutterDevice.wasExited, false);
|
||||||
|
},
|
||||||
|
overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => fileSystem,
|
||||||
|
ProcessManager: FakeProcessManager.empty,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FakeAnalytics extends Fake implements Analytics {
|
||||||
|
@override
|
||||||
|
void send(Event event) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FakeHotRunner extends Fake implements HotRunner {}
|
class _FakeHotRunner extends Fake implements HotRunner {}
|
||||||
@ -37,6 +126,9 @@ class _FakeDevFS extends Fake implements DevFS {
|
|||||||
@override
|
@override
|
||||||
final Uri? baseUri = Uri();
|
final Uri? baseUri = Uri();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> destroy() async {}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void resetLastCompiled() {}
|
void resetLastCompiled() {}
|
||||||
}
|
}
|
||||||
@ -49,6 +141,36 @@ class _FakeFlutterDevice extends Fake implements FlutterDevice {
|
|||||||
final FlutterVmService? vmService = _FakeFlutterVmService();
|
final FlutterVmService? vmService = _FakeFlutterVmService();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _FakeHotCompatibleFlutterDevice extends Fake implements FlutterDevice {
|
||||||
|
_FakeHotCompatibleFlutterDevice(this.device);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final Device device;
|
||||||
|
|
||||||
|
@override
|
||||||
|
DevFS? devFS = _FakeDevFS();
|
||||||
|
|
||||||
|
@override
|
||||||
|
ResidentCompiler? get generator => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> runHot({required HotRunner hotRunner, String? route}) async {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> stopEchoingDeviceLog() async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> exitApps({
|
||||||
|
Duration timeoutDelay = const Duration(seconds: 10),
|
||||||
|
}) async {
|
||||||
|
wasExited = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wasExited = false;
|
||||||
|
}
|
||||||
|
|
||||||
class _FakeFlutterVmService extends Fake implements FlutterVmService {
|
class _FakeFlutterVmService extends Fake implements FlutterVmService {
|
||||||
@override
|
@override
|
||||||
final vm_service.VmService service = _FakeVmService();
|
final vm_service.VmService service = _FakeVmService();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user