blob: 553e66431f55da23bbf5f806e17715d7942eca4c [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')
@Timeout(Duration(minutes: 2))
import 'package:dwds/dwds.dart';
import 'package:dwds/src/debugging/inspector.dart';
import 'package:dwds/src/utilities/conversions.dart';
import 'package:dwds/src/utilities/globals.dart';
import 'package:test/test.dart';
import 'package:test_common/test_sdk_configuration.dart';
import 'package:vm_service/vm_service.dart';
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
import 'fixtures/context.dart';
import 'fixtures/project.dart';
void main() {
final provider = TestSdkConfigurationProvider();
tearDownAll(provider.dispose);
final context =
TestContext(TestProject.testScopesWithSoundNullSafety, provider);
late AppInspector inspector;
setUpAll(() async {
await context.setUp();
final service = context.service;
inspector = service.inspector;
});
tearDownAll(() async {
await context.tearDown();
});
final url = 'org-dartlang-app:///example/scopes/main.dart';
/// A convenient way to get a library variable without boilerplate.
String libraryVariableExpression(String variable) =>
'${globalLoadStrategy.loadModuleSnippet}("dart_sdk").dart.getModuleLibraries("example/scopes/main")["$url"]["$variable"];';
Future<RemoteObject> libraryPublicFinal() =>
inspector.jsEvaluate(libraryVariableExpression('libraryPublicFinal'));
Future<RemoteObject> libraryPrivate() =>
inspector.jsEvaluate(libraryVariableExpression('_libraryPrivate'));
group('jsEvaluate', () {
test('no error', () async {
await expectLater(
inspector.jsEvaluate('42'),
completion(isA<RemoteObject>().having((e) => e.value, 'value', 42)),
);
});
test('syntax error', () async {
await expectLater(
() => inspector.jsEvaluate('<'),
throwsA(
isA<ChromeDebugException>()
.having((e) => e.text, 'text', 'Uncaught')
.having(
(e) => e.exception?.description,
'description',
contains('SyntaxError'),
)
.having((e) => e.stackTrace, 'stackTrace', isNull)
.having((e) => e.evalContents, 'evalContents', '<'),
),
);
});
test('evaluation error', () async {
await expectLater(
() => inspector.jsEvaluate('''
(function() {
let foo = (function() {
console.log(boo);
});
foo();
})();
'''),
throwsA(
isA<ChromeDebugException>()
.having((e) => e.text, 'text', 'Uncaught')
.having(
(e) => e.exception?.description,
'description',
contains('ReferenceError'),
)
.having(
(e) => e.stackTrace?.printFrames()[0],
'stackTrace',
contains('foo()'),
)
.having((e) => e.evalContents, 'evalContents', contains('foo')),
),
);
});
});
group('mapExceptionStackTrace', () {
test('multi-line exception with a stack trace', () async {
final result = await inspector
.mapExceptionStackTrace(jsMultiLineExceptionWithStackTrace);
expect(result, equals(formattedMultiLineExceptionWithStackTrace));
});
test('multi-line exception without a stack trace', () async {
final result = await inspector
.mapExceptionStackTrace(jsMultiLineExceptionNoStackTrace);
expect(result, equals(formattedMultiLineExceptionNoStackTrace));
});
test('single-line exception with a stack trace', () async {
final result = await inspector
.mapExceptionStackTrace(jsSingleLineExceptionWithStackTrace);
expect(result, equals(formattedSingleLineExceptionWithStackTrace));
});
});
test('send toString', () async {
final remoteObject = await libraryPublicFinal();
final toString =
await inspector.invoke(remoteObject.objectId!, 'toString', []);
expect(toString.value, 'A test class with message world');
});
group('getObject', () {
test('for class with generic', () async {
final remoteObject = await libraryPublicFinal();
final instance =
await inspector.getObject(remoteObject.objectId!) as Instance;
final classRef = instance.classRef!;
final clazz = await inspector.getObject(classRef.id!) as Class;
expect(clazz.name, 'MyTestClass<dynamic>');
});
});
group('loadField', () {
test('for string', () async {
final remoteObject = await libraryPublicFinal();
final message = await inspector.loadField(remoteObject, 'message');
expect(message.value, 'world');
});
test('for num', () async {
final remoteObject = await libraryPublicFinal();
final count = await inspector.loadField(remoteObject, 'count');
expect(count.value, 0);
});
});
test('properties', () async {
final remoteObject = await libraryPublicFinal();
final properties = await inspector.getProperties(remoteObject.objectId!);
final names =
properties.map((p) => p.name).where((x) => x != '__proto__').toList();
final expected = [
'_privateField',
'abstractField',
'closure',
'count',
'message',
'myselfField',
'notFinal',
'tornOff',
];
names.sort();
expect(names, expected);
});
group('invoke', () {
// We test these here because the test fixture has more complicated members
// to exercise.
LibraryRef? bootstrapLibrary;
late RemoteObject instance;
late String objectId;
setUp(() async {
bootstrapLibrary = inspector.isolate.rootLib;
instance = await libraryPublicFinal();
objectId = instance.objectId!;
});
test('expect rootLib to be the main.dart file', () {
expect(inspector.isolate.rootLib?.uri, equals(url));
});
test('invoke top-level private', () async {
final remote = await inspector.invoke(
bootstrapLibrary!.id!,
'_libraryPrivateFunction',
[dartIdFor(2), dartIdFor(3)],
);
expect(
remote,
const TypeMatcher<RemoteObject>()
.having((instance) => instance.value, 'result', 5),
);
});
test('invoke instance private', () async {
final remote = await inspector
.invoke(objectId, 'privateMethod', [dartIdFor('some string')]);
expect(
remote,
const TypeMatcher<RemoteObject>().having(
(instance) => instance.value,
'result',
'some string : a private field',
),
);
});
test('invoke instance method with object parameter', () async {
final remote = await inspector.invoke(objectId, 'equals', [objectId]);
expect(
remote,
const TypeMatcher<RemoteObject>()
.having((instance) => instance.value, 'result', true),
);
});
test('invoke instance method with object parameter 2', () async {
final libraryPrivateList = await libraryPrivate();
final remote = await inspector
.invoke(objectId, 'equals', [libraryPrivateList.objectId]);
expect(
remote,
const TypeMatcher<RemoteObject>()
.having((instance) => instance.value, 'result', false),
);
});
test('invoke closure stored in an instance field', () async {
final remote = await inspector.invoke(objectId, 'closure', []);
expect(
remote,
const TypeMatcher<RemoteObject>()
.having((instance) => instance.value, 'result', null),
);
});
test('invoke a torn-off method', () async {
final toString = await inspector.loadField(instance, 'tornOff');
final result = await inspector.invoke(toString.objectId!, 'call', []);
expect(
result,
const TypeMatcher<RemoteObject>().having(
(instance) => instance.value,
'toString',
'A test class with message world',
),
);
});
});
}
final jsMultiLineExceptionWithStackTrace = '''
Error: Assertion failed: org-dartlang-app:///example/scopes/main.dart:4:11
false
"THIS IS THE ASSERT MESSAGE"
at Object.assertFailed (org-dartlang-app:///example/scopes/main.dart.js:5297:15)
''';
final formattedMultiLineExceptionWithStackTrace = '''
Error: Assertion failed: org-dartlang-app:///example/scopes/main.dart:4:11
false
"THIS IS THE ASSERT MESSAGE"
org-dartlang-app:///example/scopes/main.dart.js 5297:15 assertFailed
''';
final jsMultiLineExceptionNoStackTrace = '''
Error: Assertion failed: org-dartlang-app:///example/scopes/main.dart:4:11
false
"THIS IS THE ASSERT MESSAGE"
''';
final formattedMultiLineExceptionNoStackTrace = '''
Error: Assertion failed: org-dartlang-app:///example/scopes/main.dart:4:11
false
"THIS IS THE ASSERT MESSAGE"
''';
final jsSingleLineExceptionWithStackTrace = '''
Error: Unexpected null value.
at Object.throw_ [as throw] (http://localhost:63236/dart_sdk.js:5379:11)
at Object.nullCheck (http://localhost:63236/dart_sdk.js:5696:30)
at main (http://localhost:63236/packages/tmpapp/main.dart.lib.js:374:10)
at http://localhost:63236/web_entrypoint.dart.lib.js:41:33
''';
final formattedSingleLineExceptionWithStackTrace = '''
Error: Unexpected null value.
http://localhost:63236/dart_sdk.js 5379:11 throw_
http://localhost:63236/dart_sdk.js 5696:30 nullCheck
http://localhost:63236/packages/tmpapp/main.dart.lib.js 374:10 main
http://localhost:63236/web_entrypoint.dart.lib.js 41:33 <fn>
''';