Skip to content

Commit 4075790

Browse files
authored
Configure DevTools to pause isolates on hot restart, and then only resume once breakpoints have been set (#7234)
1 parent 8327c5e commit 4075790

File tree

10 files changed

+478
-352
lines changed

10 files changed

+478
-352
lines changed

packages/devtools_app/integration_test/test/live_connection/service_connection_test.dart

Lines changed: 4 additions & 346 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,13 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
import 'dart:async';
6-
7-
import 'package:collection/collection.dart';
85
import 'package:devtools_app/devtools_app.dart';
9-
import 'package:devtools_app/src/service/service_extension_widgets.dart';
10-
import 'package:devtools_app/src/service/service_extensions.dart' as extensions;
11-
import 'package:devtools_app_shared/service.dart';
126
import 'package:devtools_app_shared/ui.dart';
137
import 'package:devtools_app_shared/utils.dart';
14-
import 'package:devtools_shared/devtools_shared.dart';
158
import 'package:devtools_test/helpers.dart';
169
import 'package:devtools_test/integration_test.dart';
17-
import 'package:flutter/widgets.dart';
1810
import 'package:flutter_test/flutter_test.dart';
1911
import 'package:integration_test/integration_test.dart';
20-
import 'package:vm_service/vm_service.dart';
2112

2213
// To run:
2314
// dart run integration_test/run_tests.dart --target=integration_test/test/live_connection/service_connection_test.dart
@@ -54,7 +45,7 @@ void main() {
5445
// Use a range instead of an exact number because service extension
5546
// calls are not consistent. This will still catch any spurious calls
5647
// that are unintentionally added at start up.
57-
const Range(40, 70).contains(vmServiceCallCount),
48+
const Range(35, 70).contains(vmServiceCallCount),
5849
isTrue,
5950
reason: 'Unexpected number of vm service calls upon connection: '
6051
'$vmServiceCallCount. If this is expected, please update this test '
@@ -69,10 +60,12 @@ void main() {
6960
// Filter out unawaited streamListen calls.
7061
.where((call) => call != 'streamListen')
7162
.toList()
72-
.sublist(0, 6),
63+
.sublist(0, 8),
7364
equals([
7465
'getSupportedProtocols',
7566
'getVersion',
67+
'setFlag',
68+
'requirePermissionToResume',
7669
'getFlagList',
7770
'getDartDevelopmentServiceVersion',
7871
'getDartDevelopmentServiceVersion',
@@ -110,339 +103,4 @@ void main() {
110103

111104
await disconnectFromTestApp(tester);
112105
});
113-
114-
testWidgets('can call services and service extensions', (tester) async {
115-
await pumpAndConnectDevTools(tester, testApp);
116-
await tester.pump(longDuration);
117-
118-
// TODO(kenz): re-work this integration test so that we do not have to be
119-
// on the inspector screen for this to pass.
120-
await switchToScreen(
121-
tester,
122-
tabIcon: ScreenMetaData.inspector.icon!,
123-
screenId: ScreenMetaData.inspector.id,
124-
);
125-
await tester.pump(longDuration);
126-
127-
// Ensure all futures are completed before running checks.
128-
await serviceConnection.serviceManager.service!.allFuturesCompleted;
129-
130-
logStatus('verify Flutter framework service extensions');
131-
await _verifyBooleanExtension(tester);
132-
await _verifyNumericExtension(tester);
133-
await _verifyStringExtension(tester);
134-
135-
logStatus('verify Flutter engine service extensions');
136-
expect(
137-
await serviceConnection.queryDisplayRefreshRate,
138-
equals(60),
139-
);
140-
141-
logStatus('verify services that are registered to exactly one client');
142-
await _verifyHotReloadAndHotRestart();
143-
await expectLater(
144-
serviceConnection.serviceManager.callService('fakeMethod'),
145-
throwsException,
146-
);
147-
148-
await disconnectFromTestApp(tester);
149-
});
150-
151-
testWidgets('loads initial extension states from device', (tester) async {
152-
await pumpAndConnectDevTools(tester, testApp);
153-
await tester.pump(longDuration);
154-
155-
// Ensure all futures are completed before running checks.
156-
final service = serviceConnection.serviceManager.service!;
157-
await service.allFuturesCompleted;
158-
159-
final serviceExtensionsToEnable = [
160-
(extensions.debugPaint.extension, true),
161-
(extensions.slowAnimations.extension, 5.0),
162-
(extensions.togglePlatformMode.extension, 'iOS'),
163-
];
164-
165-
logStatus('enabling service extensions on the test device');
166-
// Enable a service extension of each type (boolean, numeric, string).
167-
for (final ext in serviceExtensionsToEnable) {
168-
await serviceConnection.serviceManager.serviceExtensionManager
169-
.setServiceExtensionState(
170-
ext.$1,
171-
enabled: true,
172-
value: ext.$2,
173-
);
174-
}
175-
176-
logStatus('disconnecting from the test device');
177-
await disconnectFromTestApp(tester);
178-
179-
for (final ext in serviceExtensionsToEnable) {
180-
expect(
181-
serviceConnection.serviceManager.serviceExtensionManager
182-
.isServiceExtensionAvailable(ext.$1),
183-
isFalse,
184-
);
185-
}
186-
187-
logStatus('reconnecting to the test device');
188-
await connectToTestApp(tester, testApp);
189-
190-
logStatus('verify extension states have been restored from the device');
191-
for (final ext in serviceExtensionsToEnable) {
192-
expect(
193-
serviceConnection.serviceManager.serviceExtensionManager
194-
.isServiceExtensionAvailable(ext.$1),
195-
isTrue,
196-
);
197-
await _verifyExtensionStateInServiceManager(
198-
ext.$1,
199-
enabled: true,
200-
value: ext.$2,
201-
);
202-
}
203-
204-
await disconnectFromTestApp(tester);
205-
});
206-
}
207-
208-
Future<void> _verifyBooleanExtension(WidgetTester tester) async {
209-
final extensionName = extensions.debugPaint.extension;
210-
const evalExpression = 'debugPaintSizeEnabled';
211-
final library = EvalOnDartLibrary(
212-
'package:flutter/src/rendering/debug.dart',
213-
serviceConnection.serviceManager.service!,
214-
serviceManager: serviceConnection.serviceManager,
215-
);
216-
await _verifyExtension(
217-
tester,
218-
extensionName: extensionName,
219-
evalExpression: evalExpression,
220-
library: library,
221-
initialValue: false,
222-
newValue: true,
223-
);
224-
}
225-
226-
Future<void> _verifyNumericExtension(WidgetTester tester) async {
227-
final extensionName = extensions.slowAnimations.extension;
228-
const evalExpression = 'timeDilation';
229-
final library = EvalOnDartLibrary(
230-
'package:flutter/src/scheduler/binding.dart',
231-
serviceConnection.serviceManager.service!,
232-
serviceManager: serviceConnection.serviceManager,
233-
);
234-
await _verifyExtension(
235-
tester,
236-
extensionName: extensionName,
237-
evalExpression: evalExpression,
238-
library: library,
239-
initialValue: 1.0,
240-
newValue: 5.0,
241-
initialValueOnDevice: '1.0',
242-
newValueOnDevice: '5.0',
243-
);
244-
}
245-
246-
Future<void> _verifyStringExtension(WidgetTester tester) async {
247-
final extensionName = extensions.togglePlatformMode.extension;
248-
await _serviceExtensionAvailable(extensionName);
249-
const evalExpression = 'defaultTargetPlatform.toString()';
250-
final library = EvalOnDartLibrary(
251-
'package:flutter/src/foundation/platform.dart',
252-
serviceConnection.serviceManager.service!,
253-
serviceManager: serviceConnection.serviceManager,
254-
);
255-
await _verifyExtension(
256-
tester,
257-
extensionName: extensionName,
258-
evalExpression: evalExpression,
259-
library: library,
260-
initialValue: 'android',
261-
newValue: 'iOS',
262-
initialValueOnDevice: 'TargetPlatform.android',
263-
newValueOnDevice: 'TargetPlatform.iOS',
264-
initialValueInServiceManager: (true, 'android'),
265-
// TODO(https://github.com/flutter/devtools/issues/2780): change this
266-
// extension from the DevTools UI when it has a button in the inspector.
267-
toggleExtensionFromUi: false,
268-
);
269-
}
270-
271-
Future<void> _verifyHotReloadAndHotRestart() async {
272-
const evalExpression = 'topLevelFieldForTest';
273-
final library = EvalOnDartLibrary(
274-
'package:flutter_app/main.dart',
275-
serviceConnection.serviceManager.service!,
276-
serviceManager: serviceConnection.serviceManager,
277-
);
278-
279-
// Verify the initial value of [topLevelFieldForTest].
280-
var value = await library.eval(evalExpression, isAlive: null);
281-
expect(value.runtimeType, InstanceRef);
282-
expect(value!.valueAsString, 'false');
283-
284-
// Change the value of [topLevelFieldForTest].
285-
await library.eval('$evalExpression = true', isAlive: null);
286-
287-
// Verify the value of [topLevelFieldForTest] is now changed.
288-
value = await library.eval(evalExpression, isAlive: null);
289-
expect(value.runtimeType, InstanceRef);
290-
expect(value!.valueAsString, 'true');
291-
292-
await serviceConnection.serviceManager.performHotReload();
293-
294-
// Verify the value of [topLevelFieldForTest] is still changed after hot
295-
// reload.
296-
value = await library.eval(evalExpression, isAlive: null);
297-
expect(value.runtimeType, InstanceRef);
298-
expect(value!.valueAsString, 'true');
299-
300-
await serviceConnection.serviceManager.performHotRestart();
301-
302-
// Verify the value of [topLevelFieldForTest] is back to its original value
303-
// after hot restart.
304-
value = await library.eval(evalExpression, isAlive: null);
305-
expect(value.runtimeType, InstanceRef);
306-
expect(value!.valueAsString, 'false');
307-
}
308-
309-
Future<void> _verifyExtension(
310-
WidgetTester tester, {
311-
required String extensionName,
312-
required String evalExpression,
313-
required EvalOnDartLibrary library,
314-
required Object initialValue,
315-
required Object newValue,
316-
(bool, Object?)? initialValueInServiceManager,
317-
String? initialValueOnDevice,
318-
String? newValueOnDevice,
319-
bool toggleExtensionFromUi = true,
320-
}) async {
321-
await _serviceExtensionAvailable(extensionName);
322-
323-
await _verifyExtensionStateOnTestDevice(
324-
evalExpression: evalExpression,
325-
expectedResult: initialValueOnDevice ?? initialValue.toString(),
326-
library: library,
327-
);
328-
await _verifyExtensionStateInServiceManager(
329-
extensionName,
330-
enabled: initialValueInServiceManager?.$1 ?? false,
331-
value: initialValueInServiceManager?.$2,
332-
);
333-
334-
// Enable the service extension state from the service manager.
335-
await serviceConnection.serviceManager.serviceExtensionManager
336-
.setServiceExtensionState(
337-
extensionName,
338-
enabled: true,
339-
value: newValue,
340-
);
341-
342-
await _verifyExtensionStateOnTestDevice(
343-
evalExpression: evalExpression,
344-
expectedResult: newValueOnDevice ?? newValue.toString(),
345-
library: library,
346-
);
347-
await _verifyExtensionStateInServiceManager(
348-
extensionName,
349-
enabled: true,
350-
value: newValue,
351-
);
352-
353-
if (toggleExtensionFromUi) {
354-
// Disable the service extension state from the UI.
355-
await _changeServiceExtensionFromButton(
356-
extensionName,
357-
evalExpression: evalExpression,
358-
library: library,
359-
expectedResultOnDevice: initialValueOnDevice ?? initialValue.toString(),
360-
expectedResultInServiceManager: (false, initialValue),
361-
tester: tester,
362-
);
363-
}
364-
}
365-
366-
Future<void> _changeServiceExtensionFromButton(
367-
String extensionName, {
368-
required String evalExpression,
369-
required EvalOnDartLibrary library,
370-
required String? expectedResultOnDevice,
371-
required (bool, Object?) expectedResultInServiceManager,
372-
required WidgetTester tester,
373-
}) async {
374-
final serviceExtensionButtons = tester
375-
.widgetList<ServiceExtensionButton>(find.byType(ServiceExtensionButton));
376-
final button = serviceExtensionButtons.firstWhereOrNull(
377-
(b) => b.extensionState.description.extension == extensionName,
378-
);
379-
expect(button, isNotNull);
380-
await tester.tap(find.byWidget(button as Widget));
381-
await tester.pumpAndSettle(shortPumpDuration);
382-
383-
await _verifyExtensionStateOnTestDevice(
384-
evalExpression: evalExpression,
385-
expectedResult: expectedResultOnDevice,
386-
library: library,
387-
);
388-
await _verifyExtensionStateInServiceManager(
389-
extensionName,
390-
enabled: expectedResultInServiceManager.$1,
391-
value: expectedResultInServiceManager.$2,
392-
);
393-
}
394-
395-
/// Returns a future that completes when the service extension is available.
396-
Future<void> _serviceExtensionAvailable(String extensionName) async {
397-
final listenable = serviceConnection.serviceManager.serviceExtensionManager
398-
.hasServiceExtension(extensionName);
399-
400-
final completer = Completer<void>();
401-
void listener() {
402-
if (listenable.value) {
403-
completer.safeComplete();
404-
}
405-
}
406-
407-
listener();
408-
listenable.addListener(listener);
409-
await completer.future;
410-
listenable.removeListener(listener);
411-
}
412-
413-
Future<void> _verifyExtensionStateOnTestDevice({
414-
required String evalExpression,
415-
required String? expectedResult,
416-
required EvalOnDartLibrary library,
417-
}) async {
418-
final result = await library.eval(evalExpression, isAlive: null);
419-
if (result is InstanceRef) {
420-
expect(result.valueAsString, equals(expectedResult));
421-
}
422-
}
423-
424-
Future<void> _verifyExtensionStateInServiceManager(
425-
String extensionName, {
426-
required bool enabled,
427-
required Object? value,
428-
}) async {
429-
final stateListenable = serviceConnection
430-
.serviceManager.serviceExtensionManager
431-
.getServiceExtensionState(extensionName);
432-
433-
// Wait for the service extension state to match the expected value.
434-
final Completer<ServiceExtensionState> stateCompleter = Completer();
435-
void stateListener() {
436-
if (stateListenable.value.value == value) {
437-
stateCompleter.complete(stateListenable.value);
438-
}
439-
}
440-
441-
stateListenable.addListener(stateListener);
442-
stateListener();
443-
444-
final ServiceExtensionState state = await stateCompleter.future;
445-
stateListenable.removeListener(stateListener);
446-
expect(state.enabled, equals(enabled));
447-
expect(state.value, equals(value));
448106
}

0 commit comments

Comments
 (0)