blob: eae7a3fe5b3541c03c282a4e77dac6550d1a2f77 [file] [log] [blame]
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
@TestOn('vm')
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:dwds/src/connections/debug_connection.dart';
import 'package:dwds/src/loaders/strategy.dart';
import 'package:dwds/src/services/chrome_proxy_service.dart';
import 'package:dwds/src/utilities/dart_uri.dart';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as path;
import 'package:pub_semver/pub_semver.dart' as semver;
import 'package:test/test.dart';
import 'package:vm_service/vm_service.dart';
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
import 'fixtures/context.dart';
import 'fixtures/logging.dart';
final context = TestContext();
ChromeProxyService get service =>
fetchChromeProxyService(context.debugConnection);
WipConnection get tabConnection => context.tabConnection;
void main() {
// Change to true to see verbose output from the tests.
final debug = false;
group('shared context', () {
setUpAll(() async {
setCurrentLogWriter(debug: debug);
await context.setUp(
enableExpressionEvaluation: true,
verboseCompiler: false,
);
});
tearDownAll(() async {
await context.tearDown();
});
group('breakpoints', () {
VM vm;
late Isolate isolate;
late ScriptList scripts;
late ScriptRef mainScript;
setUp(() async {
setCurrentLogWriter(debug: debug);
vm = await fetchChromeProxyService(context.debugConnection).getVM();
isolate = await fetchChromeProxyService(context.debugConnection)
.getIsolate(vm.isolates!.first.id!);
scripts = await fetchChromeProxyService(context.debugConnection)
.getScripts(isolate.id!);
mainScript = scripts.scripts!
.firstWhere((each) => each.uri!.contains('main.dart'));
});
test('addBreakpoint', () async {
// TODO: Much more testing.
final line = await context.findBreakpointLine(
'printHelloWorld', isolate.id!, mainScript);
final firstBp =
await service.addBreakpoint(isolate.id!, mainScript.id!, line);
expect(firstBp, isNotNull);
expect(firstBp.id, isNotNull);
final secondBp =
await service.addBreakpoint(isolate.id!, mainScript.id!, line);
expect(secondBp, isNotNull);
expect(secondBp.id, isNotNull);
expect(firstBp.id, equals(secondBp.id));
// Remove breakpoint so it doesn't impact other tests.
await service.removeBreakpoint(isolate.id!, firstBp.id!);
});
test('addBreakpoint succeeds when sending the same breakpoint twice',
() async {
final line = await context.findBreakpointLine(
'printHelloWorld', isolate.id!, mainScript);
final firstBp =
service.addBreakpoint(isolate.id!, mainScript.id!, line);
final secondBp =
service.addBreakpoint(isolate.id!, mainScript.id!, line);
// Remove breakpoint so it doesn't impact other tests.
await service.removeBreakpoint(isolate.id!, (await firstBp).id!);
expect((await firstBp).id, equals((await secondBp).id));
});
test('addBreakpoint in nonsense location throws', () async {
expect(service.addBreakpoint(isolate.id!, mainScript.id!, 200000),
throwsA(predicate((dynamic e) => e is RPCError && e.code == 102)));
});
test('addBreakpoint on a part file', () async {
final partScript = scripts.scripts!
.firstWhere((script) => script.uri!.contains('part.dart'));
final bp = await service.addBreakpoint(isolate.id!, partScript.id!, 10);
// Remove breakpoint so it doesn't impact other tests.
await service.removeBreakpoint(isolate.id!, bp.id!);
expect(bp.id, isNotNull);
});
test('addBreakpointAtEntry', () async {
await expectLater(service.addBreakpointAtEntry('', ''), throwsRPCError);
});
test('addBreakpointWithScriptUri', () async {
final line = await context.findBreakpointLine(
'printHelloWorld', isolate.id!, mainScript);
final bp = await service.addBreakpointWithScriptUri(
isolate.id!, mainScript.uri!, line);
// Remove breakpoint so it doesn't impact other tests.
await service.removeBreakpoint(isolate.id!, bp.id!);
expect(bp.id, isNotNull);
});
test('addBreakpointWithScriptUri absolute file URI', () async {
final current = context.workingDirectory;
final test = path.join(path.dirname(current), '_test');
final scriptPath = Uri.parse(mainScript.uri!).path.substring(1);
final fullPath = path.join(test, scriptPath);
final fileUri = Uri.file(fullPath);
final line = await context.findBreakpointLine(
'printHelloWorld', isolate.id!, mainScript);
final bp = await service.addBreakpointWithScriptUri(
isolate.id!, '$fileUri', line);
// Remove breakpoint so it doesn't impact other tests.
await service.removeBreakpoint(isolate.id!, bp.id!);
expect(bp.id, isNotNull);
});
test('removeBreakpoint null arguments', () async {
await expectLater(
service.removeBreakpoint('', ''), throwsSentinelException);
await expectLater(
service.removeBreakpoint(isolate.id!, ''), throwsRPCError);
});
test("removeBreakpoint that doesn't exist fails", () async {
await expectLater(
service.removeBreakpoint(isolate.id!, '1234'), throwsRPCError);
});
test('add and remove breakpoint', () async {
final line = await context.findBreakpointLine(
'printHelloWorld', isolate.id!, mainScript);
final bp =
await service.addBreakpoint(isolate.id!, mainScript.id!, line);
expect(isolate.breakpoints, [bp]);
await service.removeBreakpoint(isolate.id!, bp.id!);
expect(isolate.breakpoints, isEmpty);
});
});
group('callServiceExtension', () {
setUp(() {
setCurrentLogWriter(debug: debug);
});
test('success', () async {
final serviceMethod = 'ext.test.callServiceExtension';
await tabConnection.runtime
.evaluate('registerExtension("$serviceMethod");');
// The non-string keys/values get auto json-encoded to match the vm
// behavior.
final args = {
'bool': true,
'list': [1, '2', 3],
'map': {'foo': 'bar'},
'num': 1.0,
'string': 'hello',
1: 2,
false: true,
};
final result =
await service.callServiceExtension(serviceMethod, args: args);
expect(
result.json,
args.map((k, v) => MapEntry(k is String ? k : jsonEncode(k),
v is String ? v : jsonEncode(v))));
}, onPlatform: {
'windows': const Skip('https://github.com/dart-lang/webdev/issues/711'),
});
test('failure', () async {
final serviceMethod = 'ext.test.callServiceExtensionWithError';
await tabConnection.runtime
.evaluate('registerExtensionWithError("$serviceMethod");');
final errorDetails = {'intentional': 'error'};
expect(
service.callServiceExtension(serviceMethod, args: {
'code': '-32001',
'details': jsonEncode(errorDetails),
}),
throwsA(predicate((dynamic error) =>
error is RPCError &&
error.code == -32001 &&
error.details == jsonEncode(errorDetails))));
}, onPlatform: {
'windows': const Skip('https://github.com/dart-lang/webdev/issues/711'),
});
});
group('VMTimeline', () {
setUp(() {
setCurrentLogWriter(debug: debug);
});
test('clearVMTimeline', () async {
await expectLater(service.clearVMTimeline(), throwsRPCError);
});
test('getVMTimelineMicros', () async {
await expectLater(service.getVMTimelineMicros(), throwsRPCError);
});
test('getVMTimeline', () async {
await expectLater(service.getVMTimeline(), throwsRPCError);
});
test('getVMTimelineFlags', () async {
await expectLater(service.getVMTimelineFlags(), throwsRPCError);
});
test('setVMTimelineFlags', () async {
await expectLater(
service.setVMTimelineFlags(<String>[]), throwsRPCError);
});
});
test('getMemoryUsage', () async {
final vm = await service.getVM();
final isolate = await service.getIsolate(vm.isolates!.first.id!);
final memoryUsage = await service.getMemoryUsage(isolate.id!);
expect(memoryUsage.heapUsage, isNotNull);
expect(memoryUsage.heapUsage, greaterThan(0));
expect(memoryUsage.heapCapacity, greaterThan(0));
expect(memoryUsage.externalUsage, equals(0));
});
group('evaluate', () {
late Isolate isolate;
LibraryRef? bootstrap;
setUpAll(() async {
setCurrentLogWriter(debug: debug);
final vm = await service.getVM();
isolate = await service.getIsolate(vm.isolates!.first.id!);
bootstrap = isolate.rootLib;
});
group('top level methods', () {
setUp(() {
setCurrentLogWriter(debug: debug);
});
test('can return strings', () async {
expect(
await service.evaluate(
isolate.id!, bootstrap!.id!, "helloString('world')"),
const TypeMatcher<InstanceRef>().having(
(instance) => instance.valueAsString, 'value', 'world'));
});
test('can return bools', () async {
expect(
await service.evaluate(
isolate.id!, bootstrap!.id!, 'helloBool(true)'),
const TypeMatcher<InstanceRef>().having(
(instance) => instance.valueAsString,
'valueAsString',
'true'));
expect(
await service.evaluate(
isolate.id!, bootstrap!.id!, 'helloBool(false)'),
const TypeMatcher<InstanceRef>().having(
(instance) => instance.valueAsString,
'valueAsString',
'false'));
});
test('can return nums', () async {
expect(
await service.evaluate(
isolate.id!, bootstrap!.id!, 'helloNum(42.0)'),
const TypeMatcher<InstanceRef>().having(
(instance) => instance.valueAsString, 'valueAsString', '42'));
expect(
await service.evaluate(
isolate.id!, bootstrap!.id!, 'helloNum(42.2)'),
const TypeMatcher<InstanceRef>().having(
(instance) => instance.valueAsString,
'valueAsString',
'42.2'));
});
test('can return objects with ids', () async {
final object = await service.evaluate(
isolate.id!, bootstrap!.id!, 'createObject("cool")');
expect(
object,
const TypeMatcher<InstanceRef>()
.having((instance) => instance.id, 'id', isNotNull));
// TODO(jakemac): Add tests for the ClassRef once we create one,
// https://github.com/dart-lang/sdk/issues/36771.
});
group('with provided scope', () {
setUp(() {
setCurrentLogWriter(debug: debug);
});
Future<InstanceRef> createRemoteObject(String message) async {
return await service.evaluate(
isolate.id!, bootstrap!.id!, 'createObject("$message")')
as InstanceRef;
}
test('single scope object', () async {
final instance = await createRemoteObject('A');
final result = await service.evaluate(
isolate.id!, bootstrap!.id!, 'messageFor(arg1)',
scope: {'arg1': instance.id!});
expect(
result,
const TypeMatcher<InstanceRef>().having(
(instance) => instance.valueAsString,
'valueAsString',
'A'));
});
test('multiple scope objects', () async {
final instance1 = await createRemoteObject('A');
final instance2 = await createRemoteObject('B');
final result = await service.evaluate(
isolate.id!, bootstrap!.id!, 'messagesCombined(arg1, arg2)',
scope: {'arg1': instance1.id!, 'arg2': instance2.id!});
expect(
result,
const TypeMatcher<InstanceRef>().having(
(instance) => instance.valueAsString,
'valueAsString',
'AB'));
});
});
});
});
test('evaluateInFrame', () async {
await expectLater(
service.evaluateInFrame('', 0, ''), throwsSentinelException);
});
test('getAllocationProfile', () async {
await expectLater(service.getAllocationProfile(''), throwsRPCError);
});
test('getClassList', () async {
await expectLater(service.getClassList(''), throwsRPCError);
});
test('getFlagList', () async {
expect(await service.getFlagList(), isA<FlagList>());
});
test('getInstances', () async {
await expectLater(service.getInstances('', '', 0), throwsRPCError);
});
group('getIsolate', () {
setUp(() {
setCurrentLogWriter(debug: debug);
});
test('works for existing isolates', () async {
final vm = await service.getVM();
final result = await service.getIsolate(vm.isolates!.first.id!);
expect(result, const TypeMatcher<Isolate>());
final isolate = result;
expect(isolate.name, contains('main'));
// TODO: library names change with kernel dart-lang/sdk#36736
expect(isolate.rootLib!.uri, endsWith('.dart'));
expect(
isolate.libraries,
containsAll([
_libRef('package:path/path.dart'),
// TODO: library names change with kernel dart-lang/sdk#36736
_libRef(endsWith('main.dart')),
]));
expect(isolate.extensionRPCs, contains('ext.hello_world.existing'));
});
test('throws for invalid ids', () async {
expect(service.getIsolate('bad'), throwsSentinelException);
});
});
group('getObject', () {
late Isolate isolate;
LibraryRef? bootstrap;
Library? rootLibrary;
setUpAll(() async {
setCurrentLogWriter(debug: debug);
final vm = await service.getVM();
isolate = await service.getIsolate(vm.isolates!.first.id!);
bootstrap = isolate.rootLib;
rootLibrary =
await service.getObject(isolate.id!, bootstrap!.id!) as Library;
});
setUp(() {
setCurrentLogWriter(debug: debug);
});
test('root Library', () async {
expect(rootLibrary, isNotNull);
// TODO: library names change with kernel dart-lang/sdk#36736
expect(rootLibrary!.uri, endsWith('main.dart'));
expect(rootLibrary!.classes, hasLength(1));
final testClass = rootLibrary!.classes!.first;
expect(testClass.name, 'MyTestClass');
});
test('Library only contains included scripts', () async {
final library =
await service.getObject(isolate.id!, rootLibrary!.id!) as Library;
expect(library.scripts, hasLength(2));
expect(
library.scripts,
unorderedEquals([
predicate((ScriptRef s) =>
s.uri == 'org-dartlang-app:///example/hello_world/main.dart'),
predicate((ScriptRef s) =>
s.uri == 'org-dartlang-app:///example/hello_world/part.dart'),
]));
});
test('Can get the same library in parallel', () async {
final futures = [
service.getObject(isolate.id!, rootLibrary!.id!),
service.getObject(isolate.id!, rootLibrary!.id!),
];
final results = await Future.wait(futures);
final library1 = results[0] as Library;
final library2 = results[1] as Library;
expect(library1, equals(library2));
});
test(
'Classes',
() async {
final testClass = await service.getObject(
isolate.id!, rootLibrary!.classes!.first.id!) as Class;
expect(
testClass.functions,
unorderedEquals([
predicate(
(FuncRef f) => f.name == 'staticHello' && f.isStatic!),
predicate((FuncRef f) => f.name == 'message' && !f.isStatic!),
predicate((FuncRef f) => f.name == 'notFinal' && !f.isStatic!),
predicate((FuncRef f) => f.name == 'hello' && !f.isStatic!),
predicate((FuncRef f) => f.name == '_equals' && !f.isStatic!),
predicate((FuncRef f) => f.name == 'hashCode' && !f.isStatic!),
predicate((FuncRef f) => f.name == 'toString' && !f.isStatic!),
predicate(
(FuncRef f) => f.name == 'noSuchMethod' && !f.isStatic!),
predicate(
(FuncRef f) => f.name == 'runtimeType' && !f.isStatic!),
]));
expect(
testClass.fields,
unorderedEquals([
predicate((FieldRef f) =>
f.name == 'message' &&
f.declaredType != null &&
!f.isStatic! &&
!f.isConst! &&
f.isFinal!),
predicate((FieldRef f) =>
f.name == 'notFinal' &&
f.declaredType != null &&
!f.isStatic! &&
!f.isConst! &&
!f.isFinal!),
predicate((FieldRef f) =>
f.name == 'staticMessage' &&
f.declaredType != null &&
f.isStatic! &&
!f.isConst! &&
!f.isFinal!),
]));
},
// TODO(elliette): Remove once 2.15.0 is the stable release.
skip: semver.Version.parse(Platform.version.split(' ').first) >=
semver.Version.parse('2.15.0-268.18.beta')
? null
: 'SDK does not expose static member information.',
);
test('Runtime classes', () async {
final testClass = await service.getObject(
isolate.id!, 'classes|dart:_runtime|_Type') as Class;
expect(testClass.name, '_Type');
});
test('String', () async {
final worldRef = await service.evaluate(
isolate.id!, bootstrap!.id!, "helloString('world')") as InstanceRef;
final world =
await service.getObject(isolate.id!, worldRef.id!) as Instance;
expect(world.valueAsString, 'world');
});
test('Large strings not truncated', () async {
final largeString = await service.evaluate(
isolate.id!, bootstrap!.id!, "helloString('${'abcde' * 250}')")
as InstanceRef;
expect(largeString.valueAsStringIsTruncated, isNot(isTrue));
expect(largeString.valueAsString!.length, largeString.length);
expect(largeString.length, 5 * 250);
});
/// Helper to create a list of 1001 elements, doing a direct JS eval.
Future<RemoteObject> createList() {
final expr = '''
(function () {
const sdk = ${globalLoadStrategy.loadModuleSnippet}("dart_sdk");
const list = sdk.dart.dsend(sdk.core.List,"filled", [1001, 5]);
list[4] = 100;
return list;
})()''';
return service.inspector.jsEvaluate(expr);
}
/// Helper to create a LinkedHashMap with 1001 entries, doing a direct JS eval.
Future<RemoteObject> createMap() {
final expr = '''
(function () {
const sdk = ${globalLoadStrategy.loadModuleSnippet}("dart_sdk");
const iterable = sdk.dart.dsend(sdk.core.Iterable, "generate", [1001]);
const list1 = sdk.dart.dsend(iterable, "toList", []);
const reversed = sdk.dart.dload(list1, "reversed");
const list2 = sdk.dart.dsend(reversed, "toList", []);
const map = sdk.dart.dsend(list2, "asMap", []);
const linkedMap = sdk.dart.dsend(sdk.collection.LinkedHashMap, "from", [map]);
return linkedMap;
})()''';
return service.inspector.jsEvaluate(expr);
}
test('Lists', () async {
final list = await createList();
final inst =
await service.getObject(isolate.id!, list.objectId!) as Instance;
expect(inst.length, 1001);
expect(inst.offset, null);
expect(inst.count, null);
final fifth = inst.elements![4] as InstanceRef;
expect(fifth.valueAsString, '100');
final sixth = inst.elements![5] as InstanceRef;
expect(sixth.valueAsString, '5');
});
test('Maps', () async {
final map = await createMap();
final inst =
await service.getObject(isolate.id!, map.objectId!) as Instance;
expect(inst.length, 1001);
expect(inst.offset, null);
expect(inst.count, null);
final fifth = inst.associations![4];
expect(fifth.key.valueAsString, '4');
expect(fifth.value.valueAsString, '996');
final sixth = inst.associations![5];
expect(sixth.key.valueAsString, '5');
expect(sixth.value.valueAsString, '995');
});
test('bool', () async {
final ref = await service.evaluate(
isolate.id!, bootstrap!.id!, 'helloBool(true)') as InstanceRef;
final obj = await service.getObject(isolate.id!, ref.id!) as Instance;
expect(obj.kind, InstanceKind.kBool);
expect(obj.classRef!.name, 'Bool');
expect(obj.valueAsString, 'true');
});
test('num', () async {
final ref = await service.evaluate(
isolate.id!, bootstrap!.id!, 'helloNum(42)') as InstanceRef;
final obj = await service.getObject(isolate.id!, ref.id!) as Instance;
expect(obj.kind, InstanceKind.kDouble);
expect(obj.classRef!.name, 'Double');
expect(obj.valueAsString, '42');
});
test('null', () async {
final ref = await service.evaluate(
isolate.id!, bootstrap!.id!, 'helloNum(null)') as InstanceRef;
final obj = await service.getObject(isolate.id!, ref.id!) as Instance;
expect(obj.kind, InstanceKind.kNull);
expect(obj.classRef!.name, 'Null');
expect(obj.valueAsString, 'null');
});
test('Scripts', () async {
final scripts = await service.getScripts(isolate.id!);
assert(scripts.scripts!.isNotEmpty);
for (var scriptRef in scripts.scripts!) {
final script =
await service.getObject(isolate.id!, scriptRef.id!) as Script;
final serverPath = DartUri(script.uri!, 'hello_world/').serverPath;
final result = await http
.get(Uri.parse('http://localhost:${context.port}/$serverPath'));
expect(script.source, result.body);
expect(scriptRef.uri, endsWith('.dart'));
expect(script.tokenPosTable, isNotEmpty);
}
});
group('getObject called with offset/count parameters', () {
test('Lists with offset/count are truncated', () async {
final list = await createList();
final inst = await service.getObject(
isolate.id!,
list.objectId!,
count: 7,
offset: 4,
) as Instance;
expect(inst.length, 1001);
expect(inst.offset, 4);
expect(inst.count, 7);
final fifth = inst.elements![0] as InstanceRef;
expect(fifth.valueAsString, '100');
final sixth = inst.elements![1] as InstanceRef;
expect(sixth.valueAsString, '5');
});
test('Lists are truncated to the end if offset/count runs off the end',
() async {
final list = await createList();
final inst = await service.getObject(
isolate.id!,
list.objectId!,
count: 5,
offset: 1000,
) as Instance;
expect(inst.length, 1001);
expect(inst.offset, 1000);
expect(inst.count, 1);
final only = inst.elements![0] as InstanceRef;
expect(only.valueAsString, '5');
});
test('Maps with offset/count are truncated', () async {
final map = await createMap();
final inst = await service.getObject(
isolate.id!,
map.objectId!,
count: 7,
offset: 4,
) as Instance;
expect(inst.length, 1001);
expect(inst.offset, 4);
expect(inst.count, 7);
final fifth = inst.associations![0];
expect(fifth.key.valueAsString, '4');
expect(fifth.value.valueAsString, '996');
final sixth = inst.associations![1];
expect(sixth.key.valueAsString, '5');
expect(sixth.value.valueAsString, '995');
});
test('Maps are truncated to the end if offset/count runs off the end',
() async {
final map = await createMap();
final inst = await service.getObject(
isolate.id!,
map.objectId!,
count: 5,
offset: 1000,
) as Instance;
expect(inst.length, 1001);
expect(inst.offset, 1000);
expect(inst.count, 1);
final only = inst.associations![0];
expect(only.key.valueAsString, '1000');
expect(only.value.valueAsString, '0');
});
test('Strings with offset/count are truncated', () async {
final worldRef = await service.evaluate(
isolate.id!, bootstrap!.id!, "helloString('world')")
as InstanceRef;
final world = await service.getObject(
isolate.id!,
worldRef.id!,
count: 2,
offset: 1,
) as Instance;
expect(world.valueAsString, 'or');
expect(world.count, 2);
expect(world.length, 5);
expect(world.offset, 1);
});
test(
'Strings are truncated to the end if offset/count runs off the end',
() async {
final worldRef = await service.evaluate(
isolate.id!, bootstrap!.id!, "helloString('world')")
as InstanceRef;
final world = await service.getObject(
isolate.id!,
worldRef.id!,
count: 5,
offset: 3,
) as Instance;
expect(world.valueAsString, 'ld');
expect(world.count, 2);
expect(world.length, 5);
expect(world.offset, 3);
});
test(
'offset/count parameters are ignored for Classes',
() async {
final testClass = await service.getObject(
isolate.id!,
rootLibrary!.classes!.first.id!,
offset: 100,
count: 100,
) as Class;
expect(
testClass.functions,
unorderedEquals([
predicate(
(FuncRef f) => f.name == 'staticHello' && f.isStatic!),
predicate((FuncRef f) => f.name == 'message' && !f.isStatic!),
predicate(
(FuncRef f) => f.name == 'notFinal' && !f.isStatic!),
predicate((FuncRef f) => f.name == 'hello' && !f.isStatic!),
predicate((FuncRef f) => f.name == '_equals' && !f.isStatic!),
predicate(
(FuncRef f) => f.name == 'hashCode' && !f.isStatic!),
predicate(
(FuncRef f) => f.name == 'toString' && !f.isStatic!),
predicate(
(FuncRef f) => f.name == 'noSuchMethod' && !f.isStatic!),
predicate(
(FuncRef f) => f.name == 'runtimeType' && !f.isStatic!),
]));
expect(
testClass.fields,
unorderedEquals([
predicate((FieldRef f) =>
f.name == 'message' &&
f.declaredType != null &&
!f.isStatic! &&
!f.isConst! &&
f.isFinal!),
predicate((FieldRef f) =>
f.name == 'notFinal' &&
f.declaredType != null &&
!f.isStatic! &&
!f.isConst! &&
!f.isFinal!),
predicate((FieldRef f) =>
f.name == 'staticMessage' &&
f.declaredType != null &&
f.isStatic! &&
!f.isConst! &&
!f.isFinal!),
]));
},
// TODO(elliette): Remove once 2.15.0 is the stable release.
skip: semver.Version.parse(Platform.version.split(' ').first) >=
semver.Version.parse('2.15.0-268.18.beta')
? null
: 'SDK does not expose static member information.',
);
test('offset/count parameters are ignored for bools', () async {
final ref = await service.evaluate(
isolate.id!, bootstrap!.id!, 'helloBool(true)') as InstanceRef;
final obj = await service.getObject(
isolate.id!,
ref.id!,
offset: 100,
count: 100,
) as Instance;
expect(obj.kind, InstanceKind.kBool);
expect(obj.classRef!.name, 'Bool');
expect(obj.valueAsString, 'true');
});
test('offset/count parameters are ignored for nums', () async {
final ref = await service.evaluate(
isolate.id!, bootstrap!.id!, 'helloNum(42)') as InstanceRef;
final obj = await service.getObject(
isolate.id!,
ref.id!,
offset: 100,
count: 100,
) as Instance;
expect(obj.kind, InstanceKind.kDouble);
expect(obj.classRef!.name, 'Double');
expect(obj.valueAsString, '42');
});
test('offset/count parameters are ignored for null', () async {
final ref = await service.evaluate(
isolate.id!, bootstrap!.id!, 'helloNum(null)') as InstanceRef;
final obj = await service.getObject(
isolate.id!,
ref.id!,
offset: 100,
count: 100,
) as Instance;
expect(obj.kind, InstanceKind.kNull);
expect(obj.classRef!.name, 'Null');
expect(obj.valueAsString, 'null');
});
});
});
test('getScripts', () async {
final vm = await service.getVM();
final isolateId = vm.isolates!.first.id!;
final scripts = await service.getScripts(isolateId);
expect(scripts, isNotNull);
expect(scripts.scripts, isNotEmpty);
final scriptUris = scripts.scripts!.map((s) => s.uri);
// Contains main script only once.
expect(scriptUris.where((uri) => uri!.contains('hello_world/main.dart')),
hasLength(1));
// Containts a known script.
expect(scriptUris, contains('package:path/path.dart'));
// Containts part files as well.
expect(scriptUris, contains(endsWith('part.dart')));
expect(scriptUris,
contains('package:intl/src/intl/date_format_helpers.dart'));
});
group('getSourceReport', () {
setUp(() {
setCurrentLogWriter(debug: debug);
});
test('Coverage report', () async {
final vm = await service.getVM();
final isolateId = vm.isolates!.first.id!;
await expectLater(
service.getSourceReport(isolateId, ['Coverage']), throwsRPCError);
});
test('Coverage report', () async {
final vm = await service.getVM();
final isolateId = vm.isolates!.first.id!;
await expectLater(
service.getSourceReport(isolateId, ['Coverage'],
libraryFilters: ['foo']),
throwsRPCError);
});
test('report type not understood', () async {
final vm = await service.getVM();
final isolateId = vm.isolates!.first.id!;
await expectLater(
service.getSourceReport(isolateId, ['FooBar']), throwsRPCError);
});
test('PossibleBreakpoints report', () async {
final vm = await service.getVM();
final isolateId = vm.isolates!.first.id!;
final scripts = await service.getScripts(isolateId);
final mainScript = scripts.scripts!
.firstWhere((script) => script.uri!.contains('main.dart'));
final sourceReport = await service.getSourceReport(
isolateId,
['PossibleBreakpoints'],
scriptId: mainScript.id,
);
expect(sourceReport.scripts, isNotEmpty);
expect(sourceReport.ranges, isNotEmpty);
final sourceReportRange = sourceReport.ranges!.first;
expect(sourceReportRange.possibleBreakpoints, isNotEmpty);
});
});
group('Pausing', () {
String? isolateId;
late Stream<Event> stream;
ScriptList scripts;
late ScriptRef mainScript;
setUp(() async {
setCurrentLogWriter(debug: debug);
final vm = await service.getVM();
isolateId = vm.isolates!.first.id;
scripts = await service.getScripts(isolateId!);
await service.streamListen('Debug');
stream = service.onEvent('Debug');
mainScript = scripts.scripts!
.firstWhere((script) => script.uri!.contains('main.dart'));
});
test('at breakpoints sets pauseBreakPoints', () async {
final line = await context.findBreakpointLine(
'callPrintCount', isolateId!, mainScript);
final bp =
await service.addBreakpoint(isolateId!, mainScript.id!, line);
final event = await stream
.firstWhere((event) => event.kind == EventKind.kPauseBreakpoint);
final pauseBreakpoints = event.pauseBreakpoints!;
expect(pauseBreakpoints, hasLength(1));
expect(pauseBreakpoints.first.id, bp.id);
await service.removeBreakpoint(isolateId!, bp.id!);
});
tearDown(() async {
// Resume execution to not impact other tests.
await service.resume(isolateId!);
});
});
group('Step', () {
String? isolateId;
late Stream<Event> stream;
ScriptList scripts;
ScriptRef mainScript;
setUp(() async {
setCurrentLogWriter(debug: debug);
final vm = await service.getVM();
isolateId = vm.isolates!.first.id;
scripts = await service.getScripts(isolateId!);
await service.streamListen('Debug');
stream = service.onEvent('Debug');
mainScript = scripts.scripts!
.firstWhere((script) => script.uri!.contains('main.dart'));
final line = await context.findBreakpointLine(
'callPrintCount', isolateId!, mainScript);
final bp =
await service.addBreakpoint(isolateId!, mainScript.id!, line);
// Wait for breakpoint to trigger.
await stream
.firstWhere((event) => event.kind == EventKind.kPauseBreakpoint);
await service.removeBreakpoint(isolateId!, bp.id!);
});
tearDown(() async {
// Resume execution to not impact other tests.
await service.resume(isolateId!);
});
test('Into goes to the next Dart location', () async {
await service.resume(isolateId!, step: 'Into');
// Wait for the step to actually occur.
await stream
.firstWhere((event) => event.kind == EventKind.kPauseInterrupted);
final stack = await service.getStack(isolateId!);
expect(stack, isNotNull);
final first = stack.frames!.first;
expect(first.kind, 'Regular');
expect(first.code!.kind, 'Dart');
expect(first.code!.name, 'printCount');
});
test('Over goes to the next Dart location', () async {
await service.resume(isolateId!, step: 'Over');
// Wait for the step to actually occur.
await stream
.firstWhere((event) => event.kind == EventKind.kPauseInterrupted);
final stack = await service.getStack(isolateId!);
expect(stack, isNotNull);
final first = stack.frames!.first;
expect(first.kind, 'Regular');
expect(first.code!.kind, 'Dart');
expect(first.code!.name, '<closure>');
});
test('Out goes to the next Dart location', () async {
await service.resume(isolateId!, step: 'Out');
// Wait for the step to actually occur.
await stream
.firstWhere((event) => event.kind == EventKind.kPauseInterrupted);
final stack = await service.getStack(isolateId!);
expect(stack, isNotNull);
final first = stack.frames!.first;
expect(first.kind, 'Regular');
expect(first.code!.kind, 'Dart');
expect(first.code!.name, '<closure>');
});
});
group('getStack', () {
String? isolateId;
late Stream<Event> stream;
ScriptList scripts;
late ScriptRef mainScript;
setUp(() async {
setCurrentLogWriter(debug: debug);
final vm = await service.getVM();
isolateId = vm.isolates!.first.id;
scripts = await service.getScripts(isolateId!);
await service.streamListen('Debug');
stream = service.onEvent('Debug');
mainScript = scripts.scripts!
.firstWhere((each) => each.uri!.contains('main.dart'));
});
test('throws if not paused', () async {
await expectLater(service.getStack(isolateId!), throwsRPCError);
},
skip: Platform
.isWindows); // Issue: https://github.com/dart-lang/webdev/issues/1749
/// Support function for pausing and returning the stack at a line.
Future<Stack> breakAt(String breakpointId, {int? limit}) async {
final line = await context.findBreakpointLine(
breakpointId, isolateId!, mainScript);
Breakpoint? bp;
try {
bp = await service.addBreakpoint(isolateId!, mainScript.id!, line);
// Wait for breakpoint to trigger.
await stream
.firstWhere((event) => event.kind == EventKind.kPauseBreakpoint);
return await service.getStack(isolateId!, limit: limit);
} finally {
// Remove breakpoint and resume so it doesn't impact other tests.
if (bp != null) {
await service.removeBreakpoint(isolateId!, bp.id!);
}
await service.resume(isolateId!);
}
}
test('returns stack when broken', () async {
final stack = await breakAt('inPrintCount');
expect(stack, isNotNull);
expect(stack.frames, hasLength(2));
final first = stack.frames!.first;
expect(first.kind, 'Regular');
expect(first.code!.kind, 'Dart');
expect(first.code!.name, 'printCount');
});
test('stack has a variable', () async {
final stack = await breakAt('callPrintCount');
expect(stack, isNotNull);
expect(stack.frames, hasLength(1));
final first = stack.frames!.first;
expect(first.kind, 'Regular');
expect(first.code!.kind, 'Dart');
expect(first.code!.name, '<closure>');
// TODO: Make this more precise once this case doesn't
// also include all the libraries.
expect(first.vars, hasLength(greaterThanOrEqualTo(1)));
final underscore = first.vars!.firstWhere((v) => v.name == '_');
expect(underscore, isNotNull);
});
test('collects async frames', () async {
final stack = await breakAt('asyncCall');
expect(stack, isNotNull);
expect(stack.frames, hasLength(greaterThan(1)));
final first = stack.frames!.first;
expect(first.kind, 'Regular');
expect(first.code!.kind, 'Dart');
// We should have an async marker.
final suspensionFrames = stack.frames!
.where((frame) => frame.kind == FrameKind.kAsyncSuspensionMarker);
expect(suspensionFrames, isNotEmpty);
// We should have async frames.
final asyncFrames = stack.frames!
.where((frame) => frame.kind == FrameKind.kAsyncCausal);
expect(asyncFrames, isNotEmpty);
});
test('returns the correct number of frames when a limit is provided',
() async {
var stack = await breakAt('asyncCall', limit: 4);
expect(stack, isNotNull);
expect(stack.frames, hasLength(equals(4)));
stack = await breakAt('asyncCall', limit: 2);
expect(stack, isNotNull);
expect(stack.frames, hasLength(equals(2)));
stack = await breakAt('asyncCall');
expect(stack, isNotNull);
expect(stack.frames, hasLength(equals(5)));
});
test('truncated stacks are properly indicated', () async {
var stack = await breakAt('asyncCall', limit: 3);
expect(stack, isNotNull);
expect(stack.truncated, isTrue);
stack = await breakAt('asyncCall');
expect(stack, isNotNull);
expect(stack.truncated, isFalse);
stack = await breakAt('asyncCall', limit: 20000);
expect(stack, isNotNull);
expect(stack.truncated, isFalse);
});
test('break on exceptions with legacy setExceptionPauseMode', () async {
final oldPauseMode =
(await service.getIsolate(isolateId!)).exceptionPauseMode!;
await service.setExceptionPauseMode(
isolateId!, ExceptionPauseMode.kAll);
// Wait for pausing to actually propagate.
final event = await stream
.firstWhere((event) => event.kind == EventKind.kPauseException);
expect(event.exception, isNotNull);
// Check that the exception stack trace has been mapped to Dart source files.
expect(event.exception!.valueAsString, contains('main.dart'));
final stack = await service.getStack(isolateId!);
expect(stack, isNotNull);
await service.setExceptionPauseMode(isolateId!, oldPauseMode);
await service.resume(isolateId!);
});
test('break on exceptions with setIsolatePauseMode', () async {
final oldPauseMode =
(await service.getIsolate(isolateId!)).exceptionPauseMode;
await service.setIsolatePauseMode(isolateId!,
exceptionPauseMode: ExceptionPauseMode.kAll);
// Wait for pausing to actually propagate.
final event = await stream
.firstWhere((event) => event.kind == EventKind.kPauseException);
expect(event.exception, isNotNull);
final stack = await service.getStack(isolateId!);
expect(stack, isNotNull);
await service.setIsolatePauseMode(isolateId!,
exceptionPauseMode: oldPauseMode);
await service.resume(isolateId!);
});
test('returns non-null stack when paused', () async {
await service.pause(isolateId!);
// Wait for pausing to actually propagate.
await stream
.firstWhere((event) => event.kind == EventKind.kPauseInterrupted);
expect(await service.getStack(isolateId!), isNotNull);
// Resume the isolate to not impact other tests.
await service.resume(isolateId!);
});
});
test('getVM', () async {
final vm = await service.getVM();
expect(vm.name, isNotNull);
expect(vm.version, Platform.version);
expect(vm.isolates, hasLength(1));
final isolate = vm.isolates!.first;
expect(isolate.id, isNotNull);
expect(isolate.name, isNotNull);
expect(isolate.number, isNotNull);
});
test('getVersion', () async {
final version = await service.getVersion();
expect(version, isNotNull);
expect(version.major, greaterThan(0));
});
group('invoke', () {
VM vm;
late Isolate isolate;
LibraryRef? bootstrap;
late InstanceRef testInstance;
setUp(() async {
setCurrentLogWriter(debug: debug);
vm = await service.getVM();
isolate = await service.getIsolate(vm.isolates!.first.id!);
bootstrap = isolate.rootLib;
testInstance = await service.evaluate(
isolate.id!, bootstrap!.id!, 'myInstance') as InstanceRef;
});
test('rootLib', () async {
expect(
bootstrap,
const TypeMatcher<LibraryRef>().having((library) => library.name,
'name', 'org-dartlang-app:///example/hello_world/main.dart'));
});
test('toString()', () async {
final remote =
await service.invoke(isolate.id!, testInstance.id!, 'toString', []);
expect(
remote,
const TypeMatcher<InstanceRef>().having(
(instance) => instance.valueAsString,
'toString()',
"Instance of 'MyTestClass'"));
});
test('hello()', () async {
final remote =
await service.invoke(isolate.id!, testInstance.id!, 'hello', []);
expect(
remote,
const TypeMatcher<InstanceRef>().having(
(instance) => instance.valueAsString, 'hello()', 'world'));
});
test('helloString', () async {
final remote = await service.invoke(isolate.id!, bootstrap!.id!,
'helloString', ['#StringInstanceRef#abc']);
expect(
remote,
const TypeMatcher<InstanceRef>().having(
(instance) => instance.valueAsString, 'helloString', 'abc'));
expect(
remote,
const TypeMatcher<InstanceRef>()
.having((instance) => instance.kind, 'kind', 'String'));
});
test('null argument', () async {
final remote = await service.invoke(
isolate.id!, bootstrap!.id!, 'helloString', ['objects/null']);
expect(
remote,
const TypeMatcher<InstanceRef>().having(
(instance) => instance.valueAsString, 'helloString', 'null'));
expect(
remote,
const TypeMatcher<InstanceRef>()
.having((instance) => instance.kind, 'kind', 'Null'));
});
test('helloBool', () async {
final remote = await service.invoke(
isolate.id!, bootstrap!.id!, 'helloBool', ['objects/bool-true']);
expect(
remote,
const TypeMatcher<InstanceRef>().having(
(instance) => instance.valueAsString, 'helloBool', 'true'));
expect(
remote,
const TypeMatcher<InstanceRef>()
.having((instance) => instance.kind, 'kind', 'Bool'));
});
test('helloNum', () async {
final remote = await service.invoke(
isolate.id!, bootstrap!.id!, 'helloNum', ['objects/int-123']);
expect(
remote,
const TypeMatcher<InstanceRef>().having(
(instance) => instance.valueAsString, 'helloNum', '123'));
expect(
remote,
const TypeMatcher<InstanceRef>()
.having((instance) => instance.kind, 'kind', 'Double'));
});
test('two object arguments', () async {
final remote = await service.invoke(isolate.id!, bootstrap!.id!,
'messagesCombined', [testInstance.id, testInstance.id]);
expect(
remote,
const TypeMatcher<InstanceRef>().having(
(instance) => instance.valueAsString,
'messagesCombined',
'worldworld'));
expect(
remote,
const TypeMatcher<InstanceRef>()
.having((instance) => instance.kind, 'kind', 'String'));
});
});
test('kill', () async {
await expectLater(service.kill(''), throwsRPCError);
});
test('onEvent', () async {
expect(() => service.onEvent(''), throwsRPCError);
});
test('pause / resume', () async {
await service.streamListen('Debug');
final stream = service.onEvent('Debug');
final vm = await service.getVM();
final isolateId = vm.isolates!.first.id!;
final pauseCompleter = Completer();
final pauseSub = tabConnection.debugger.onPaused.listen((_) {
pauseCompleter.complete();
});
final resumeCompleter = Completer();
final resumeSub = tabConnection.debugger.onResumed.listen((_) {
resumeCompleter.complete();
});
expect(await service.pause(isolateId), const TypeMatcher<Success>());
await stream
.firstWhere((event) => event.kind == EventKind.kPauseInterrupted);
expect((await service.getIsolate(isolateId)).pauseEvent!.kind,
EventKind.kPauseInterrupted);
await pauseCompleter.future;
expect(await service.resume(isolateId), const TypeMatcher<Success>());
await stream.firstWhere((event) => event.kind == EventKind.kResume);
expect((await service.getIsolate(isolateId)).pauseEvent!.kind,
EventKind.kResume);
await resumeCompleter.future;
await pauseSub.cancel();
await resumeSub.cancel();
});
test('getInboundReferences', () async {
await expectLater(
service.getInboundReferences('', '', 0), throwsRPCError);
});
test('getRetainingPath', () async {
await expectLater(service.getRetainingPath('', '', 0), throwsRPCError);
});
test('lookupResolvedPackageUris converts package and org-dartlang-app uris',
() async {
final vm = await service.getVM();
final isolateId = vm.isolates!.first.id!;
final scriptList = await service.getScripts(isolateId);
final uris = scriptList.scripts!.map((e) => e.uri!).toList();
final resolvedUris =
await service.lookupResolvedPackageUris(isolateId, uris);
expect(
resolvedUris.uris,
containsAll([
contains('/_test/example/hello_world/main.dart'),
contains('/lib/path.dart'),
contains('/lib/src/path_set.dart'),
]));
});
test('lookupResolvedPackageUris does not translate non-existent paths',
() async {
final vm = await service.getVM();
final isolateId = vm.isolates!.first.id!;
final resolvedUris = await service.lookupResolvedPackageUris(isolateId, [
'package:does/not/exist.dart',
'dart:does_not_exist',
'file:///does_not_exist.dart',
]);
expect(resolvedUris.uris, [null, null, null]);
});
test('lookupResolvedPackageUris translates dart uris', () async {
final vm = await service.getVM();
final isolateId = vm.isolates!.first.id!;
final resolvedUris = await service.lookupResolvedPackageUris(isolateId, [
'dart:html',
'dart:async',
]);
expect(resolvedUris.uris, [
'org-dartlang-sdk:///sdk/lib/html/dart2js/html_dart2js.dart',
'org-dartlang-sdk:///sdk/lib/async/async.dart',
]);
}, skip: 'https://github.com/dart-lang/webdev/issues/1584');
test('lookupPackageUris finds package and org-dartlang-app paths',
() async {
final vm = await service.getVM();
final isolateId = vm.isolates!.first.id!;
final scriptList = await service.getScripts(isolateId);
final uris = scriptList.scripts!.map((e) => e.uri!).toList();
final resolvedUris =
await service.lookupResolvedPackageUris(isolateId, uris);
final packageUris = await service.lookupPackageUris(
isolateId, List<String>.from(resolvedUris.uris!));
expect(
packageUris.uris,
containsAll([
'org-dartlang-app:///example/hello_world/main.dart',
'package:path/path.dart',
'package:path/src/path_set.dart',
]));
});
test('lookupPackageUris ignores local parameter', () async {
final vm = await service.getVM();
final isolateId = vm.isolates!.first.id!;
final scriptList = await service.getScripts(isolateId);
final uris = scriptList.scripts!.map((e) => e.uri!).toList();
final resolvedUrisWithLocal =
await service.lookupResolvedPackageUris(isolateId, uris, local: true);
final packageUrisWithLocal = await service.lookupPackageUris(
isolateId, List<String>.from(resolvedUrisWithLocal.uris!));
expect(
packageUrisWithLocal.uris,
containsAll([
'org-dartlang-app:///example/hello_world/main.dart',
'package:path/path.dart',
'package:path/src/path_set.dart',
]));
final resolvedUrisWithoutLocal =
await service.lookupResolvedPackageUris(isolateId, uris, local: true);
final packageUrisWithoutLocal = await service.lookupPackageUris(
isolateId, List<String>.from(resolvedUrisWithoutLocal.uris!));
expect(
packageUrisWithoutLocal.uris,
containsAll([
'org-dartlang-app:///example/hello_world/main.dart',
'package:path/path.dart',
'package:path/src/path_set.dart',
]));
});
test('lookupPackageUris does not translate non-existent paths', () async {
final vm = await service.getVM();
final isolateId = vm.isolates!.first.id!;
final resolvedUris = await service.lookupPackageUris(isolateId, [
'org-dartlang-sdk:///sdk/does/not/exist.dart',
'does_not_exist.dart',
'file:///does_not_exist.dart',
]);
expect(resolvedUris.uris, [null, null, null]);
});
test('lookupPackageUris translates dart uris', () async {
final vm = await service.getVM();
final isolateId = vm.isolates!.first.id!;
final resolvedUris = await service.lookupPackageUris(isolateId, [
'org-dartlang-sdk:///sdk/lib/html/dart2js/html_dart2js.dart',
'org-dartlang-sdk:///sdk/lib/async/async.dart',
]);
expect(resolvedUris.uris, [
'dart:html',
'dart:async',
]);
}, skip: 'https://github.com/dart-lang/webdev/issues/1584');
test('registerService', () async {
await expectLater(
service.registerService('ext.foo.bar', ''), throwsRPCError);
});
test('reloadSources', () async {
await expectLater(service.reloadSources(''), throwsRPCError);
});
test('setExceptionPauseMode', () async {
final vm = await service.getVM();
final isolateId = vm.isolates!.first.id!;
expect(await service.setExceptionPauseMode(isolateId, 'all'), _isSuccess);
expect(await service.setExceptionPauseMode(isolateId, 'unhandled'),
_isSuccess);
// Make sure this is the last one - or future tests might hang.
expect(
await service.setExceptionPauseMode(isolateId, 'none'), _isSuccess);
await expectLater(
service.setExceptionPauseMode(isolateId, 'invalid'), throwsRPCError);
});
test('setFlag', () async {
await expectLater(service.setFlag('', ''), throwsRPCError);
});
test('setLibraryDebuggable', () async {
await expectLater(
service.setLibraryDebuggable('', '', false), throwsRPCError);
});
test('setName', () async {
final vm = await service.getVM();
final isolateId = vm.isolates!.first.id!;
expect(service.setName(isolateId, 'test'), completion(_isSuccess));
final isolate = await service.getIsolate(isolateId);
expect(isolate.name, 'test');
});
test('setVMName', () async {
expect(service.setVMName('foo'), completion(_isSuccess));
final vm = await service.getVM();
expect(vm.name, 'foo');
});
test('streamCancel', () async {
await expectLater(service.streamCancel(''), throwsRPCError);
});
group('streamListen/onEvent', () {
group('Debug', () {
late Stream<Event> eventStream;
setUp(() async {
setCurrentLogWriter(debug: debug);
expect(await service.streamListen('Debug'),
const TypeMatcher<Success>());
eventStream = service.onEvent('Debug');
});
test('basic Pause/Resume', () async {
expect(service.streamListen('Debug'), completion(_isSuccess));
final stream = service.onEvent('Debug');
unawaited(tabConnection.debugger.pause());
await expectLater(
stream,
emitsThrough(const TypeMatcher<Event>()
.having((e) => e.kind, 'kind', EventKind.kPauseInterrupted)));
unawaited(tabConnection.debugger.resume());
expect(
eventStream,
emitsThrough(const TypeMatcher<Event>()
.having((e) => e.kind, 'kind', EventKind.kResume)));
});
test('Inspect', () async {
expect(
eventStream,
emitsThrough(const TypeMatcher<Event>()
.having((e) => e.kind, 'kind', EventKind.kInspect)
.having(
(e) => e.inspectee,
'inspectee',
const TypeMatcher<InstanceRef>()
.having((instance) => instance.id, 'id', isNotNull)
.having((instance) => instance.kind, 'inspectee.kind',
InstanceKind.kPlainInstance))));
await tabConnection.runtime.evaluate('inspectInstance()');
});
});
group('Extension', () {
late Stream<Event> eventStream;
setUp(() async {
setCurrentLogWriter(debug: debug);
expect(await service.streamListen('Extension'),
const TypeMatcher<Success>());
eventStream = service.onEvent('Extension');
});
test('Custom debug event', () async {
final eventKind = 'my.custom.event';
expect(
eventStream,
emitsThrough(predicate((Event event) =>
event.kind == EventKind.kExtension &&
event.extensionKind == eventKind &&
event.extensionData!.data['example'] == 'data')));
await tabConnection.runtime.evaluate("postEvent('$eventKind');");
});
test('Batched debug events from injected client', () async {
final eventKind = EventKind.kExtension;
final extensionKind = 'MyEvent';
final eventData = 'eventData';
final delay = const Duration(milliseconds: 2000);
TypeMatcher<Event> eventMatcher(
String data) =>
const TypeMatcher<Event>()
.having((event) => event.kind, 'kind', eventKind)
.having((event) => event.extensionKind, 'extensionKind',
extensionKind)
.having((event) => event.extensionData!.data['eventData'],
'eventData', data);
String emitDebugEvent(String data) =>
"\$emitDebugEvent('$extensionKind', '{ \"$eventData\": \"$data\" }');";
final size = 2;
final batch1 = List.generate(size, (int i) => 'data$i');
final batch2 = List.generate(size, (int i) => 'data${size + i}');
expect(
eventStream,
emitsInOrder([
...batch1.map(eventMatcher),
...batch2.map(eventMatcher),
]));
for (var data in batch1) {
await tabConnection.runtime.evaluate(emitDebugEvent(data));
}
await Future.delayed(delay);
for (var data in batch2) {
await tabConnection.runtime.evaluate(emitDebugEvent(data));
}
});
});
test('GC', () async {
expect(service.streamListen('GC'), completion(_isSuccess));
});
group('Isolate', () {
late Stream<Event> isolateEventStream;
setUp(() async {
expect(await service.streamListen(EventStreams.kIsolate), _isSuccess);
isolateEventStream = service.onEvent(EventStreams.kIsolate);
});
test('ServiceExtensionAdded', () async {
final extensionMethod = 'ext.foo.bar';
expect(
isolateEventStream,
emitsThrough(predicate((Event event) =>
event.kind == EventKind.kServiceExtensionAdded &&
event.extensionRPC == extensionMethod)));
await tabConnection.runtime
.evaluate("registerExtension('$extensionMethod');");
});
test('lifecycle events', () async {
final vm = await service.getVM();
final initialIsolateId = vm.isolates!.first.id;
final eventsDone = expectLater(
isolateEventStream,
emitsThrough(emitsInOrder([
predicate((Event event) =>
event.kind == EventKind.kIsolateExit &&
event.isolate!.id == initialIsolateId),
predicate((Event event) =>
event.kind == EventKind.kIsolateStart &&
event.isolate!.id != initialIsolateId),
predicate((Event event) =>
event.kind == EventKind.kIsolateRunnable &&
event.isolate!.id != initialIsolateId),
])));
service.destroyIsolate();
await service.createIsolate(context.appConnection);
await eventsDone;
expect((await service.getVM()).isolates!.first.id,
isNot(initialIsolateId));
});
test('RegisterExtension events from injected client', () async {
final eventKind = EventKind.kServiceExtensionAdded;
final extensions = List.generate(10, (index) => 'extension$index');
TypeMatcher<Event> eventMatcher(String extension) =>
const TypeMatcher<Event>()
.having((event) => event.kind, 'kind', eventKind)
.having((event) => event.extensionRPC, 'RPC', extension);
String emitRegisterEvent(String extension) =>
"\$emitRegisterEvent('$extension')";
expect(
isolateEventStream, emitsInOrder(extensions.map(eventMatcher)));
for (var extension in extensions) {
await tabConnection.runtime.evaluate(emitRegisterEvent(extension));
}
});
});
test('Timeline', () async {
expect(service.streamListen('Timeline'), completion(_isSuccess));
});
test('Stdout', () async {
expect(service.streamListen('Stdout'), completion(_isSuccess));
expect(
service.onEvent('Stdout'),
emitsThrough(predicate((Event event) =>
event.kind == EventKind.kWriteEvent &&
String.fromCharCodes(base64.decode(event.bytes!))
.contains('hello'))));
await tabConnection.runtime.evaluate('console.log("hello");');
});
test('Stderr', () async {
expect(service.streamListen('Stderr'), completion(_isSuccess));
final stderrStream = service.onEvent('Stderr');
expect(
stderrStream,
emitsThrough(predicate((Event event) =>
event.kind == EventKind.kWriteEvent &&
String.fromCharCodes(base64.decode(event.bytes!))
.contains('Error'))));
await tabConnection.runtime.evaluate('console.error("Error");');
});
test('exception stack trace mapper', () async {
expect(service.streamListen('Stderr'), completion(_isSuccess));
final stderrStream = service.onEvent('Stderr');
expect(
stderrStream,
emitsThrough(predicate((Event event) =>
event.kind == EventKind.kWriteEvent &&
String.fromCharCodes(base64.decode(event.bytes!))
.contains('main.dart'))));
await tabConnection.runtime.evaluate('throwUncaughtException();');
});
test('VM', () async {
final status = await service.streamListen('VM');
expect(status, _isSuccess);
final stream = service.onEvent('VM');
expect(
stream,
emitsThrough(predicate((Event e) =>
e.kind == EventKind.kVMUpdate && e.vm!.name == 'test')));
await service.setVMName('test');
});
});
test('Logging', () async {
expect(
service.streamListen(EventStreams.kLogging), completion(_isSuccess));
final stream = service.onEvent(EventStreams.kLogging);
final message = 'myMessage';
unawaited(tabConnection.runtime.evaluate("sendLog('$message');"));
final event = await stream.first;
expect(event.kind, EventKind.kLogging);
final logRecord = event.logRecord!;
expect(logRecord.message!.valueAsString, message);
expect(logRecord.loggerName!.valueAsString, 'testLogCategory');
});
});
}
final _isSuccess = isA<Success>();
TypeMatcher _libRef(uriMatcher) =>
isA<LibraryRef>().having((l) => l.uri, 'uri', uriMatcher);