blob: 868391b7f8e9660b822b50f356498ead696b06e4 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
@TestOn('vm')
import 'dart:async';
import 'package:devtools_app/src/eval_on_dart_library.dart';
import 'package:devtools_app/src/globals.dart';
import 'package:devtools_app/src/service_extensions.dart' as extensions;
import 'package:devtools_app/src/service_manager.dart';
import 'package:devtools_app/src/service_registrations.dart' as registrations;
import 'package:test/test.dart';
import 'package:vm_service/vm_service.dart';
import 'support/flutter_test_environment.dart';
// Error codes defined by
// https://www.jsonrpc.org/specification#error_object
const jsonRpcInvalidParamsCode = -32602;
Future<void> runServiceManagerTests(FlutterTestEnvironment env) async {
group('ServiceConnectionManager', () {
tearDownAll(() async {
await env.tearDownEnvironment(force: true);
});
test('vmServiceOpened', () async {
await env.setupEnvironment();
expect(serviceManager.service, equals(env.service));
expect(serviceManager.isolateManager, isNotNull);
expect(serviceManager.serviceExtensionManager, isNotNull);
expect(serviceManager.vmFlagManager, isNotNull);
expect(serviceManager.isolateManager.isolates, isNotEmpty);
expect(serviceManager.vmFlagManager.flags.value, isNotNull);
if (serviceManager.isolateManager.selectedIsolate == null) {
await serviceManager.isolateManager.onSelectedIsolateChanged
.firstWhere((ref) => ref != null);
}
await env.tearDownEnvironment();
});
test('invalid setBreakpoint throws exception', () async {
await env.setupEnvironment();
await expectLater(
serviceManager.service.addBreakpoint(
serviceManager.isolateManager.selectedIsolate.id,
'fake-script-id',
1),
throwsA(const TypeMatcher<RPCError>()
.having((e) => e.code, 'code', equals(jsonRpcInvalidParamsCode))),
);
await env.tearDownEnvironment();
});
test('toggle boolean service extension', () async {
await env.setupEnvironment();
await serviceManager.service.allFuturesCompleted;
final extensionName = extensions.debugPaint.extension;
const evalExpression = 'debugPaintSizeEnabled';
final library = EvalOnDartLibrary(
[
'package:flutter/src/rendering/debug.dart',
'package:flutter_web/src/rendering/debug.dart',
],
env.service,
);
await _serviceExtensionAvailable(extensionName);
await _verifyExtensionStateOnTestDevice(evalExpression, 'false', library);
await _verifyInitialExtensionStateInServiceManager(extensionName);
// Enable the service extension via ServiceExtensionManager.
await serviceManager.serviceExtensionManager.setServiceExtensionState(
extensionName,
true,
true,
);
await _verifyExtensionStateOnTestDevice(evalExpression, 'true', library);
await _verifyExtensionStateInServiceManager(extensionName, true, true);
await env.tearDownEnvironment();
});
test('toggle String service extension', () async {
await env.setupEnvironment();
await serviceManager.service.allFuturesCompleted;
final extensionName = extensions.togglePlatformMode.extension;
await _serviceExtensionAvailable(extensionName);
const evalExpression = 'defaultTargetPlatform.toString()';
final library = EvalOnDartLibrary(
[
'package:flutter_web/src/foundation/platform.dart',
'package:flutter/src/foundation/platform.dart',
],
env.service,
);
await _verifyExtensionStateOnTestDevice(
evalExpression,
'TargetPlatform.android',
library,
);
await _verifyExtensionStateInServiceManager(
extensionName,
true,
'android',
);
// Enable the service extension via ServiceExtensionManager.
await serviceManager.serviceExtensionManager.setServiceExtensionState(
extensionName,
true,
'iOS',
);
await _verifyExtensionStateOnTestDevice(
evalExpression,
'TargetPlatform.iOS',
library,
);
await _verifyExtensionStateInServiceManager(extensionName, true, 'iOS');
await env.tearDownEnvironment();
});
test('toggle numeric service extension', () async {
await env.setupEnvironment();
await serviceManager.service.allFuturesCompleted;
final extensionName = extensions.slowAnimations.extension;
await _serviceExtensionAvailable(extensionName);
const evalExpression = 'timeDilation';
final library = EvalOnDartLibrary(
[
'package:flutter_web/src/scheduler/binding.dart',
'package:flutter/src/scheduler/binding.dart',
],
env.service,
);
await _verifyExtensionStateOnTestDevice(evalExpression, '1.0', library);
await _verifyInitialExtensionStateInServiceManager(extensionName);
// Enable the service extension via ServiceExtensionManager.
await serviceManager.serviceExtensionManager.setServiceExtensionState(
extensionName,
true,
5.0,
);
await _verifyExtensionStateOnTestDevice(evalExpression, '5.0', library);
await _verifyExtensionStateInServiceManager(extensionName, true, 5.0);
await env.tearDownEnvironment();
});
test('callService', () async {
await env.setupEnvironment();
final registeredService = serviceManager
.registeredMethodsForService[registrations.hotReload.service] ??
const [];
expect(registeredService, isNotEmpty);
await serviceManager.callService(
registrations.hotReload.service,
isolateId: serviceManager.isolateManager.selectedIsolate.id,
);
await env.tearDownEnvironment();
});
test('callService throws exception', () async {
await env.setupEnvironment();
// Service with 0 registrations.
await expectLater(
serviceManager.callService('fakeMethod'), throwsException);
await env.tearDownEnvironment();
});
test('hotReload', () async {
await env.setupEnvironment();
await serviceManager.performHotReload();
await env.tearDownEnvironment();
});
// TODO(kenz): once hot restart tests are fixed, add a hot restart test
// that verifies the state of service extensions after a hot restart.
// TODO(jacobr): uncomment out the hotRestart tests once
// https://github.com/flutter/devtools/issues/337 is fixed.
/*
test('hotRestart', () async {
await env.setupEnvironment();
const evalExpression = 'topLevelFieldForTest';
final library = EvalOnDartLibrary(
'package:flutter_app/main.dart',
env.service,
);
// Verify topLevelFieldForTest is false initially.
final initialResult = await library.eval(evalExpression, isAlive: null);
expect(initialResult.runtimeType, equals(InstanceRef));
expect(initialResult.valueAsString, equals('false'));
// Set field to true by calling the service extension.
await library.eval('$evalExpression = true', isAlive: null);
// Verify topLevelFieldForTest is now true.
final intermediateResult =
await library.eval(evalExpression, isAlive: null);
expect(intermediateResult.runtimeType, equals(InstanceRef));
expect(intermediateResult.valueAsString, equals('true'));
await serviceManager.performHotRestart();
/// After the hot restart some existing calls to the vm service may
/// timeout and that is ok.
serviceManager.service.doNotWaitForPendingFuturesBeforeExit();
// Verify topLevelFieldForTest is false again after hot restart.
final finalResult = await library.eval(evalExpression, isAlive: null);
expect(finalResult.runtimeType, equals(InstanceRef));
expect(finalResult.valueAsString, equals('false'));
await env.tearDownEnvironment();
});
*/
test('getDisplayRefreshRate', () async {
await env.setupEnvironment();
expect(await serviceManager.queryDisplayRefreshRate, equals(60));
await env.tearDownEnvironment();
});
}, timeout: const Timeout.factor(4));
group('VmFlagManager', () {
tearDownAll(() async {
await env.tearDownEnvironment(force: true);
});
test('flags initialized on vm service opened', () async {
await env.setupEnvironment();
expect(serviceManager.service, equals(env.service));
expect(serviceManager.vmFlagManager, isNotNull);
expect(serviceManager.vmFlagManager.flags.value, isNotNull);
await env.tearDownEnvironment();
});
test('notifies on flag change', () async {
await env.setupEnvironment();
const profiler = 'profiler';
final flagManager = serviceManager.vmFlagManager;
final initialFlags = flagManager.flags.value;
final profilerFlagNotifier = flagManager.flag(profiler);
expect(profilerFlagNotifier.value.valueAsString, equals('true'));
await serviceManager.service.setFlag(profiler, 'false');
expect(profilerFlagNotifier.value.valueAsString, equals('false'));
// Await a delay so the new flags have time to be pulled and set.
await Future.delayed(const Duration(milliseconds: 5000));
final newFlags = flagManager.flags.value;
expect(newFlags, isNot(equals(initialFlags)));
await env.tearDownEnvironment();
});
}, timeout: const Timeout.factor(4));
group('ServiceConnectionManager - restoring device-enabled extension', () {
test('all extension types', () async {
await env.setupEnvironment();
final service = serviceManager.service;
final _flutterIsolateRef =
serviceManager.isolateManager.mainIsolate.value;
expect(_flutterIsolateRef.id, isNotNull);
/// Helper method to call an extension on the test device and verify that
/// the device reflects the new extension state.
Future<void> _enableExtensionOnTestDevice(
extensions.ServiceExtensionDescription extensionDescription,
Map<String, dynamic> args,
String evalExpression,
EvalOnDartLibrary library, {
String newValue,
String oldValue,
}) async {
if (extensionDescription
is extensions.ToggleableServiceExtensionDescription) {
newValue ??= extensionDescription.enabledValue.toString();
oldValue ??= extensionDescription.disabledValue.toString();
}
// Verify initial extension state on test device.
await _verifyExtensionStateOnTestDevice(
evalExpression,
oldValue,
library,
);
// Enable service extension on test device.
await service.callServiceExtension(
extensionDescription.extension,
isolateId: _flutterIsolateRef.id,
args: args,
);
// Verify extension state after calling the service extension.
await _verifyExtensionStateOnTestDevice(
evalExpression,
newValue,
library,
);
}
// Enable a boolean extension on the test device.
final boolExtensionDescription = extensions.debugPaint;
final boolArgs = {'enabled': true};
const boolEvalExpression = 'debugPaintSizeEnabled';
final boolLibrary = EvalOnDartLibrary(
[
'package:flutter/src/rendering/debug.dart',
'package:flutter_web/src/rendering/debug.dart',
],
service,
isolateRef: _flutterIsolateRef,
);
await _enableExtensionOnTestDevice(
boolExtensionDescription,
boolArgs,
boolEvalExpression,
boolLibrary,
);
// Enable a String extension on the test device.
final stringExtensionDescription = extensions.togglePlatformMode;
final stringArgs = {'value': stringExtensionDescription.values[0]};
const stringEvalExpression = 'defaultTargetPlatform.toString()';
final stringLibrary = EvalOnDartLibrary(
[
'package:flutter/src/foundation/platform.dart',
'package:flutter_web/src/foundation/platform.dart',
],
service,
isolateRef: _flutterIsolateRef,
);
await _enableExtensionOnTestDevice(
stringExtensionDescription,
stringArgs,
stringEvalExpression,
stringLibrary,
newValue: 'TargetPlatform.iOS',
oldValue: 'TargetPlatform.android',
);
// Enable a numeric extension on the test device.
final numericExtensionDescription = extensions.slowAnimations;
final numericArgs = {
numericExtensionDescription.extension.substring(
numericExtensionDescription.extension.lastIndexOf('.') + 1):
numericExtensionDescription.enabledValue
};
const numericEvalExpression = 'timeDilation';
final numericLibrary = EvalOnDartLibrary(
[
'package:flutter/src/scheduler/binding.dart',
'package:flutter_web/src/scheduler/binding.dart',
],
service,
isolateRef: _flutterIsolateRef,
);
await _enableExtensionOnTestDevice(
numericExtensionDescription,
numericArgs,
numericEvalExpression,
numericLibrary,
);
await _verifyExtensionStateInServiceManager(
boolExtensionDescription.extension,
true,
boolExtensionDescription.enabledValue,
);
await _verifyExtensionStateInServiceManager(
stringExtensionDescription.extension,
true,
stringExtensionDescription.values[0],
);
await _verifyExtensionStateInServiceManager(
numericExtensionDescription.extension,
true,
numericExtensionDescription.enabledValue,
);
await env.tearDownEnvironment();
});
}, timeout: const Timeout.factor(4));
}
// Returns a future that completes when the service extension is available.
Future<void> _serviceExtensionAvailable(String extensionName) async {
final listenable =
serviceManager.serviceExtensionManager.hasServiceExtension(extensionName);
final completer = Completer<void>();
final listener = () {
if (listenable.value == true && !completer.isCompleted) {
completer.complete();
}
};
listener();
listenable.addListener(listener);
await completer.future;
listenable.removeListener(listener);
}
Future<void> _verifyExtensionStateOnTestDevice(String evalExpression,
String expectedResult, EvalOnDartLibrary library) async {
final result = await library.eval(evalExpression, isAlive: null);
if (result is InstanceRef) {
expect(result.valueAsString, equals(expectedResult));
}
}
Future<void> _verifyInitialExtensionStateInServiceManager(
String extensionName) async {
// For all service extensions, the initial state in ServiceExtensionManager
// should be disabled with value null.
await _verifyExtensionStateInServiceManager(extensionName, false, null);
}
Future<void> _verifyExtensionStateInServiceManager(
String extensionName, bool enabled, dynamic value) async {
final stateListenable = serviceManager.serviceExtensionManager
.getServiceExtensionState(extensionName);
// Wait for the service extension state to match the expected value.
final Completer<ServiceExtensionState> stateCompleter = Completer();
final stateListener = () {
if (stateListenable.value.value == value) {
stateCompleter.complete(stateListenable.value);
}
};
stateListenable.addListener(stateListener);
stateListener();
final ServiceExtensionState state = await stateCompleter.future;
stateListenable.removeListener(stateListener);
expect(state.enabled, equals(enabled));
expect(state.value, equals(value));
}