blob: 6ee625a9b6161a5aebe2b6495fe083e1c8139900 [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))
library;
import 'dart:async';
import 'package:dwds/src/debugging/debugger.dart';
import 'package:dwds/src/debugging/frame_computer.dart';
import 'package:dwds/src/debugging/inspector.dart';
import 'package:dwds/src/debugging/location.dart';
import 'package:dwds/src/debugging/skip_list.dart';
import 'package:logging/logging.dart';
import 'package:test/test.dart';
import 'package:vm_service/vm_service.dart' hide LogRecord;
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'
show CallFrame, DebuggerPausedEvent, StackTrace, WipCallFrame, WipScript;
import 'fixtures/debugger_data.dart';
import 'fixtures/fakes.dart';
import 'fixtures/utilities.dart';
late AppInspector inspector;
late Debugger debugger;
late FakeWebkitDebugger webkitDebugger;
late StreamController<DebuggerPausedEvent> pausedController;
late Locations locations;
late SkipLists skipLists;
class TestStrategy extends FakeStrategy {
TestStrategy(super.assetReader);
@override
Future<String> moduleForServerPath(String entrypoint, String appUri) async =>
'foo.ddc.js';
@override
Future<String> serverPathForModule(String entrypoint, String module) async =>
'foo/ddc';
}
final sourceMapContents =
'{"version":3,"sourceRoot":"","sources":["main.dart"],"names":[],'
'"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAUwB,IAAtB,WAAM;AAKJ,'
'IAHF,4BAAkB,aAAa,SAAC,GAAG;AACb,MAApB,WAAM;AACN,YAAgC,+CAAO,AAAK,oBAAO,'
'yCAAC,WAAW;IAChE;AAC0D,IAA3D,AAAS,AAAK,0DAAO;AAAe,kBAAO;;;AAEvC,gBAAQ;'
'AAGV,IAFI,kCAAqC,QAAC;AACX,MAA/B,WAAM,AAAwB,0BAAP,QAAF,AAAE,KAAK,GAAP;'
';EAEzB","file":"main.ddc.js"}';
final sampleSyncFrame = WipCallFrame({
'callFrameId': '{"ordinal":0,"injectedScriptId":2}',
'functionName': '',
'functionLocation': {
'scriptId': '69',
'lineNumber': 88,
'columnNumber': 72,
},
'location': {'scriptId': '69', 'lineNumber': 37, 'columnNumber': 0},
'url': '',
'scopeChain': [],
'this': {'type': 'undefined'},
});
final sampleAsyncFrame = CallFrame({
'functionName': 'myFunc',
'url': '',
'scriptId': '71',
'lineNumber': 40,
'columnNumber': 1,
});
final Map<String, WipScript> scripts = {
'69': WipScript(<String, dynamic>{
'url': 'http://127.0.0.1:8081/foo.ddc.js',
}),
'71': WipScript(<String, dynamic>{
'url': 'http://127.0.0.1:8081/bar.ddc.js',
}),
};
void main() async {
setUpAll(() async {
webkitDebugger = FakeWebkitDebugger(scripts: scripts);
pausedController = StreamController<DebuggerPausedEvent>();
webkitDebugger.onPaused = pausedController.stream;
final toolConfiguration = TestToolConfiguration.withLoadStrategy(
loadStrategy: TestStrategy(FakeAssetReader()),
);
setGlobalsForTesting(
toolConfiguration: toolConfiguration,
);
final root = 'fakeRoot';
locations = Locations(
FakeAssetReader(sourceMap: sourceMapContents),
FakeModules(),
root,
);
locations.initialize('fake_entrypoint');
skipLists = SkipLists();
debugger = await Debugger.create(
webkitDebugger,
(_, __) {},
locations,
skipLists,
root,
);
inspector = FakeInspector(webkitDebugger, fakeIsolate: simpleIsolate);
debugger.updateInspector(inspector);
});
/// Test that we get expected variable values from a hard-coded
/// stack frame.
test('frames 1', () async {
final stackComputer = FrameComputer(debugger, frames1);
final frames = await stackComputer.calculateFrames();
expect(frames, isNotNull);
expect(frames, isNotEmpty);
final firstFrameVars = frames[0].vars!;
final frame1Variables = firstFrameVars.map((each) => each.name).toList();
expect(frame1Variables, ['a', 'b']);
});
test('creates async frames', () async {
final stackComputer = FrameComputer(
debugger,
[sampleSyncFrame],
asyncStackTrace: StackTrace({
'callFrames': [sampleAsyncFrame.json],
'parent': StackTrace({
'callFrames': [sampleAsyncFrame.json],
}).json,
}),
);
final frames = await stackComputer.calculateFrames();
expect(frames, hasLength(5));
expect(frames[0].kind, FrameKind.kRegular);
expect(frames[1].kind, FrameKind.kAsyncSuspensionMarker);
expect(frames[2].kind, FrameKind.kAsyncCausal);
expect(frames[3].kind, FrameKind.kAsyncSuspensionMarker);
expect(frames[4].kind, FrameKind.kAsyncCausal);
});
test('elides multiple async frames', () async {
final stackComputer = FrameComputer(
debugger,
[sampleSyncFrame],
asyncStackTrace: StackTrace({
'callFrames': [sampleAsyncFrame.json],
'parent': StackTrace({
'callFrames': [],
'parent': StackTrace({
'callFrames': [sampleAsyncFrame.json],
'parent': StackTrace({
'callFrames': [],
}).json,
}).json,
}).json,
}),
);
final frames = await stackComputer.calculateFrames();
expect(frames, hasLength(5));
expect(frames[0].kind, FrameKind.kRegular);
expect(frames[1].kind, FrameKind.kAsyncSuspensionMarker);
expect(frames[2].kind, FrameKind.kAsyncCausal);
expect(frames[3].kind, FrameKind.kAsyncSuspensionMarker);
expect(frames[4].kind, FrameKind.kAsyncCausal);
});
setUp(() {
// We need to provide an Isolate so that the code doesn't bail out on a null
// check before it has a chance to throw.
inspector = FakeInspector(webkitDebugger, fakeIsolate: simpleIsolate);
debugger.updateInspector(inspector);
});
group('errors', () {
setUp(() {
// We need to provide an Isolate so that the code doesn't bail out on a null
// check before it has a chance to throw.
inspector = FakeInspector(webkitDebugger, fakeIsolate: simpleIsolate);
debugger.updateInspector(inspector);
});
test('errors in the zone are caught and logged', () async {
// Add a DebuggerPausedEvent with a null parameter to provoke an error.
pausedController.sink.add(
DebuggerPausedEvent({
'method': '',
'params': {
'reason': 'other',
'callFrames': [
{'callFrameId': '', 'functionName': ''},
],
},
}),
);
expect(
Debugger.logger.onRecord,
emitsThrough(
predicate(
(LogRecord log) =>
log.message.contains('Error calculating sync frame'),
),
),
);
});
});
}