Skip to content

Commit 4d28e6d

Browse files
authored
Terminate simulator app on "q" (#113581)
1 parent 3f89d63 commit 4d28e6d

File tree

7 files changed

+110
-5
lines changed

7 files changed

+110
-5
lines changed

.ci.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3690,6 +3690,15 @@ targets:
36903690
["devicelab", "ios", "mac"]
36913691
task_name: hot_mode_dev_cycle_ios__benchmark
36923692

3693+
- name: Mac_ios hot_mode_dev_cycle_ios_simulator
3694+
recipe: devicelab/devicelab_drone
3695+
bringup: true
3696+
timeout: 60
3697+
properties:
3698+
tags: >
3699+
["devicelab", "ios", "mac"]
3700+
task_name: hot_mode_dev_cycle_ios_simulator
3701+
36933702
- name: Mac_ios fullscreen_textfield_perf_ios__e2e_summary
36943703
recipe: devicelab/devicelab_drone
36953704
presubmit: false

TESTOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@
168168
/dev/devicelab/bin/tasks/fullscreen_textfield_perf_impeller_ios__e2e_summary.dart @zanderso @flutter/engine
169169
/dev/devicelab/bin/tasks/fullscreen_textfield_perf_ios__e2e_summary.dart @zanderso @flutter/engine
170170
/dev/devicelab/bin/tasks/hello_world_ios__compile.dart @zanderso @flutter/engine
171+
/dev/devicelab/bin/tasks/hot_mode_dev_cycle_ios_simulator.dart @jmagman @flutter/tool
171172
/dev/devicelab/bin/tasks/hot_mode_dev_cycle_ios__benchmark.dart @zanderso @flutter/tool
172173
/dev/devicelab/bin/tasks/hot_mode_dev_cycle_macos_target__benchmark.dart @zanderso @flutter/tool
173174
/dev/devicelab/bin/tasks/imagefiltered_transform_animation_perf_impeller_ios__timeline_summary.dart @zanderso @flutter/engine
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter_devicelab/framework/devices.dart';
6+
import 'package:flutter_devicelab/framework/framework.dart';
7+
import 'package:flutter_devicelab/framework/ios.dart';
8+
import 'package:flutter_devicelab/framework/task_result.dart';
9+
import 'package:flutter_devicelab/tasks/hot_mode_tests.dart';
10+
11+
Future<void> main() async {
12+
await task(() async {
13+
deviceOperatingSystem = DeviceOperatingSystem.ios;
14+
String? simulatorDeviceId;
15+
try {
16+
await testWithNewIOSSimulator('TestHotReloadSim', (String deviceId) async {
17+
simulatorDeviceId = deviceId;
18+
// This isn't actually a benchmark test, so do not use the returned `benchmarkScoreKeys` result.
19+
await createHotModeTest(deviceIdOverride: deviceId, checkAppRunningOnLocalDevice: true)();
20+
});
21+
} finally {
22+
await removeIOSimulator(simulatorDeviceId);
23+
}
24+
25+
return TaskResult.success(null);
26+
});
27+
}

dev/devicelab/bin/tasks/hot_mode_dev_cycle_linux_target__benchmark.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ import 'package:flutter_devicelab/framework/framework.dart';
66
import 'package:flutter_devicelab/tasks/hot_mode_tests.dart';
77

88
Future<void> main() async {
9-
await task(createHotModeTest(deviceIdOverride: 'linux'));
9+
await task(createHotModeTest(deviceIdOverride: 'linux', checkAppRunningOnLocalDevice: true));
1010
}

dev/devicelab/lib/tasks/hot_mode_tests.dart

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import 'dart:convert';
77
import 'dart:io';
88

99
import 'package:path/path.dart' as path;
10+
import 'package:process/process.dart';
1011

1112
import '../framework/devices.dart';
1213
import '../framework/framework.dart';
14+
import '../framework/running_processes.dart';
1315
import '../framework/task_result.dart';
1416
import '../framework/utils.dart';
1517

@@ -18,7 +20,11 @@ final Directory flutterGalleryDir = dir(path.join(flutterDirectory.path, 'dev/in
1820
const String kSourceLine = 'fontSize: (orientation == Orientation.portrait) ? 32.0 : 24.0';
1921
const String kReplacementLine = 'fontSize: (orientation == Orientation.portrait) ? 34.0 : 24.0';
2022

21-
TaskFunction createHotModeTest({String? deviceIdOverride, Map<String, String>? environment}) {
23+
TaskFunction createHotModeTest({
24+
String? deviceIdOverride,
25+
Map<String, String>? environment,
26+
bool checkAppRunningOnLocalDevice = false,
27+
}) {
2228
// This file is modified during the test and needs to be restored at the end.
2329
final File flutterFrameworkSource = file(path.join(
2430
flutterDirectory.path, 'packages/flutter/lib/src/widgets/framework.dart',
@@ -96,7 +102,7 @@ TaskFunction createHotModeTest({String? deviceIdOverride, Map<String, String>? e
96102
}
97103
});
98104

99-
largeReloadData = await captureReloadData(options, environment, benchmarkFile, (String line, Process process) {
105+
largeReloadData = await captureReloadData(options, environment, benchmarkFile, (String line, Process process) async {
100106
if (!line.contains('Reloaded ')) {
101107
return;
102108
}
@@ -108,6 +114,9 @@ TaskFunction createHotModeTest({String? deviceIdOverride, Map<String, String>? e
108114
process.stdin.writeln('r');
109115
hotReloadCount += 1;
110116
} else {
117+
if (checkAppRunningOnLocalDevice) {
118+
await _checkAppRunning(true);
119+
}
111120
process.stdin.writeln('q');
112121
}
113122
});
@@ -150,6 +159,9 @@ TaskFunction createHotModeTest({String? deviceIdOverride, Map<String, String>? e
150159
json.decode(benchmarkFile.readAsStringSync()) as Map<String, dynamic>;
151160
}
152161
});
162+
if (checkAppRunningOnLocalDevice) {
163+
await _checkAppRunning(false);
164+
}
153165
} finally {
154166
flutterFrameworkSource.writeAsStringSync(oldContents);
155167
}
@@ -257,3 +269,23 @@ Future<Map<String, dynamic>> captureReloadData(
257269
benchmarkFile.deleteSync();
258270
return result;
259271
}
272+
273+
Future<void> _checkAppRunning(bool shouldBeRunning) async {
274+
late Set<RunningProcessInfo> galleryProcesses;
275+
for (int i = 0; i < 10; i++) {
276+
final String exe = Platform.isWindows ? '.exe' : '';
277+
galleryProcesses = await getRunningProcesses(
278+
processName: 'Flutter Gallery$exe',
279+
processManager: const LocalProcessManager(),
280+
);
281+
282+
if (galleryProcesses.isNotEmpty == shouldBeRunning) {
283+
return;
284+
}
285+
286+
// Give the app time to shut down.
287+
sleep(const Duration(seconds: 1));
288+
}
289+
print(galleryProcesses.join('\n'));
290+
throw TaskResult.failure('Flutter Gallery app is ${shouldBeRunning ? 'not' : 'still'} running');
291+
}

packages/flutter_tools/lib/src/ios/simulators.dart

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,25 @@ class SimControl {
257257
return result;
258258
}
259259

260+
Future<RunResult> stopApp(String deviceId, String appIdentifier) async {
261+
RunResult result;
262+
try {
263+
result = await _processUtils.run(
264+
<String>[
265+
..._xcode.xcrunCommand(),
266+
'simctl',
267+
'terminate',
268+
deviceId,
269+
appIdentifier,
270+
],
271+
throwOnError: true,
272+
);
273+
} on ProcessException catch (exception) {
274+
throwToolExit('Unable to terminate $appIdentifier on $deviceId:\n$exception');
275+
}
276+
return result;
277+
}
278+
260279
Future<void> takeScreenshot(String deviceId, String outputPath) async {
261280
try {
262281
await _processUtils.run(
@@ -536,8 +555,7 @@ class IOSSimulator extends Device {
536555
ApplicationPackage app, {
537556
String? userIdentifier,
538557
}) async {
539-
// Currently we don't have a way to stop an app running on iOS.
540-
return false;
558+
return (await _simControl.stopApp(id, app.id)).exitCode == 0;
541559
}
542560

543561
String get logFilePath {

packages/flutter_tools/test/general.shard/ios/simulators_test.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,24 @@ Dec 20 17:04:32 md32-11-vm1 Another App[88374]: Ignore this text'''
901901
throwsToolExit(message: r'Unable to launch'),
902902
);
903903
});
904+
905+
testWithoutContext('.stopApp() handles exceptions', () async {
906+
fakeProcessManager.addCommand(const FakeCommand(
907+
command: <String>[
908+
'xcrun',
909+
'simctl',
910+
'terminate',
911+
deviceId,
912+
appId,
913+
],
914+
exception: ProcessException('xcrun', <String>[]),
915+
));
916+
917+
expect(
918+
() async => simControl.stopApp(deviceId, appId),
919+
throwsToolExit(message: r'Unable to terminate'),
920+
);
921+
});
904922
});
905923

906924
group('startApp', () {

0 commit comments

Comments
 (0)