| // 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/config/tool_configuration.dart'; |
| import 'package:dwds/src/debugging/inspector.dart'; |
| import 'package:dwds/src/utilities/conversions.dart'; |
| import 'package:test/test.dart'; |
| import 'package:test_common/test_sdk_configuration.dart'; |
| import 'package:test_common/utilities.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) => |
| '${globalToolConfiguration.loadStrategy.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 = [ |
| if (dartSdkIsAtLeast( |
| newDdcTypeSystemVersion, |
| )) |
| '\$ti', |
| '_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> |
| '''; |