// Copyright (c) 2020, 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)) library; import 'dart:async'; import 'package:dwds/src/debugging/debugger.dart'; import 'package:dwds/src/debugging/location.dart'; import 'package:dwds/src/debugging/skip_list.dart'; import 'package:dwds/src/services/batched_expression_evaluator.dart'; import 'package:dwds/src/services/expression_evaluator.dart'; import 'package:dwds/src/utilities/shared.dart'; import 'package:test/test.dart'; import 'package:vm_service/vm_service.dart' hide LogRecord; import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; import 'fixtures/context.dart'; import 'fixtures/fakes.dart'; import 'fixtures/utilities.dart'; late ExpressionEvaluator? _evaluator; late ExpressionEvaluator? _batchedEvaluator; void main() async { group('expression compiler service with fake asset server', () { Future stop() async { _evaluator?.close(); _evaluator = null; _batchedEvaluator?.close(); _batchedEvaluator = null; } late StreamController pausedController; late StreamController debugEventController; late FakeChromeAppInspector inspector; setUp(() async { final assetReader = FakeAssetReader(sourceMap: ''); final toolConfiguration = TestToolConfiguration.withLoadStrategy( loadStrategy: FakeStrategy(assetReader), ); setGlobalsForTesting(toolConfiguration: toolConfiguration); final modules = FakeModules(); final webkitDebugger = FakeWebkitDebugger(); pausedController = StreamController(); debugEventController = StreamController(); webkitDebugger.onPaused = pausedController.stream; final root = 'fakeRoot'; final entry = 'fake_entrypoint'; final locations = Locations(assetReader, modules, root); await locations.initialize(entry); final skipLists = SkipLists(root); final debugger = await Debugger.create( webkitDebugger, (_, e) => debugEventController.sink.add(e), locations, skipLists, root, ); inspector = FakeChromeAppInspector( webkitDebugger, fakeIsolate: simpleIsolate, ); debugger.updateInspector(inspector); _evaluator = ExpressionEvaluator( entry, inspector, debugger, locations, modules, FakeExpressionCompiler(), ); _batchedEvaluator = BatchedExpressionEvaluator( entry, inspector, debugger, locations, modules, FakeExpressionCompiler(), ); }); tearDown(() async { await stop(); }); group('evaluator', () { late ExpressionEvaluator evaluator; setUp(() async { evaluator = _evaluator!; }); test('can evaluate expression', () async { final result = await evaluator.evaluateExpression( '1', 'main.dart', 'true', {}, ); expect( result, const TypeMatcher().having( (o) => o.value, 'value', 'true', ), ); }); test('can evaluate expression in frame with null scope', () async { // Verify that we don't get the internal error. // More extensive testing of 'evaluateExpressionInFrame' is done in // evaluation tests for frontend server and build daemon. await expectLater( evaluator.evaluateExpressionInFrame('1', 0, 'true', null), throwsRPCErrorWithMessage( 'Cannot evaluate on a call frame when the program is not paused', ), ); }); test('cannot evaluate expression in async frame ', () async { // Add a DebuggerPausedEvent with no frames provoke an error. pausedController.sink.add( DebuggerPausedEvent({ 'method': '', 'params': {'reason': 'other', 'callFrames': []}, }), ); await debugEventController.stream.firstWhere( (e) => e.kind == EventKind.kPauseInterrupted, ); // Verify that we get the internal error. final result = await evaluator.evaluateExpressionInFrame( '20', 0, 'true', null, ); expect( result, isA() .having((o) => o.json['type'], 'type', 'AsyncFrameError') .having( (o) => o.json['value'], 'value', 'Expression evaluation in async frames is not supported. ' 'No frame with index 0.', ), ); }); test('can evaluate expression in frame with empty scope', () async { // Verify that we don't get the internal error. // More extensive testing of 'evaluateExpressionInFrame' is done in // evaluation tests for frontend server and build daemon. await expectLater( evaluator.evaluateExpressionInFrame('1', 0, 'true', {}), throwsRPCErrorWithMessage( 'Cannot evaluate on a call frame when the program is not paused', ), ); }); test('returns error if closed', () async { evaluator.close(); final result = await evaluator.evaluateExpression( '1', 'main.dart', 'true', {}, ); expect( result, const TypeMatcher() .having((o) => o.type, 'type', 'InternalError') .having((o) => o.value, 'value', contains('evaluator closed')), ); }); }); group('batched evaluator', () { late ExpressionEvaluator evaluator; setUp(() async { evaluator = _batchedEvaluator!; }); test('can evaluate expression', () async { final result = await evaluator.evaluateExpression( '1', 'main.dart', 'true', {}, ); expect( result, const TypeMatcher().having( (o) => o.value, 'value', 'true', ), ); }); test('retries failed batched expression', () async { safeUnawaited( evaluator.evaluateExpression('2', 'main.dart', 'true', {}), ); await evaluator.evaluateExpression('2', 'main.dart', 'false', {}); expect(inspector.functionsCalled.length, 3); expect( inspector.functionsCalled[0].contains('return [ true, false ];'), true, ); expect(inspector.functionsCalled[1].contains('return true;'), true); expect(inspector.functionsCalled[2].contains('return false;'), true); }); test('returns error if closed', () async { evaluator.close(); final result = await evaluator.evaluateExpression( '1', 'main.dart', 'true', {}, ); expect( result, const TypeMatcher() .having((o) => o.type, 'type', 'InternalError') .having((o) => o.value, 'value', contains('evaluator closed')), ); }); }); }); }