[dds] Improve error handling of calling getters + toString() display in DAP variables
Change-Id: I5b93667925a7f4a6da9edd8f5c94e56b1dbff6d9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/209963
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
diff --git a/pkg/dds/lib/src/dap/adapters/dart.dart b/pkg/dds/lib/src/dap/adapters/dart.dart
index 27ad157..ed80060 100644
--- a/pkg/dds/lib/src/dap/adapters/dart.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart.dart
@@ -51,6 +51,9 @@
/// Pattern for extracting useful error messages from an evaluation exception.
final _evalErrorMessagePattern = RegExp('Error: (.*)');
+/// Pattern for extracting useful error messages from an unhandled exception.
+final _exceptionMessagePattern = RegExp('Unhandled exception:\n(.*)');
+
/// Pattern for a trailing semicolon.
final _trailingSemicolonPattern = RegExp(r';$');
@@ -538,10 +541,7 @@
//
// So in the case of a Watch context, try to extract the useful message.
if (args.context == 'watch') {
- final match = _evalErrorMessagePattern.firstMatch(rawMessage);
- final shortError = match != null ? match.group(1)! : null;
-
- throw DebugAdapterException(shortError ?? rawMessage);
+ throw DebugAdapterException(extractEvaluationErrorMessage(rawMessage));
}
throw DebugAdapterException(rawMessage);
@@ -576,6 +576,24 @@
}
}
+ /// Tries to extract the useful part from an evaluation exception message.
+ ///
+ /// If no message could be extracted, returns the whole original error.
+ String extractEvaluationErrorMessage(String rawError) {
+ final match = _evalErrorMessagePattern.firstMatch(rawError);
+ final shortError = match != null ? match.group(1)! : null;
+ return shortError ?? rawError;
+ }
+
+ /// Tries to extract the useful part from an unhandled exception message.
+ ///
+ /// If no message could be extracted, returns the whole original error.
+ String extractUnhandledExceptionMessage(String rawError) {
+ final match = _exceptionMessagePattern.firstMatch(rawError);
+ final shortError = match != null ? match.group(1)! : null;
+ return shortError ?? rawError;
+ }
+
/// Sends a [TerminatedEvent] if one has not already been sent.
void handleSessionTerminate() {
if (_hasSentTerminatedEvent) {
diff --git a/pkg/dds/lib/src/dap/protocol_converter.dart b/pkg/dds/lib/src/dap/protocol_converter.dart
index 2d2f7240..a3e836e 100644
--- a/pkg/dds/lib/src/dap/protocol_converter.dart
+++ b/pkg/dds/lib/src/dap/protocol_converter.dart
@@ -99,7 +99,11 @@
ref,
includeQuotesAroundString: false,
);
- stringValue += ' ($toStringValue)';
+ // Include the toString() result only if it's not the default (which
+ // duplicates the type name we're already showing).
+ if (toStringValue != "Instance of '${ref.classRef?.name}'") {
+ stringValue += ' ($toStringValue)';
+ }
}
return stringValue;
} else if (ref.kind == 'List') {
@@ -220,21 +224,29 @@
/// Helper to evaluate each getter and convert the response to a
/// variable.
Future<dap.Variable> evaluate(int index, String getterName) async {
- final response = await service.evaluate(
- thread.isolate.id!,
- instance.id!,
- getterName,
- );
- // Convert results to variables.
- return convertVmResponseToVariable(
- thread,
- response,
- name: getterName,
- evaluateName:
- _adapter.combineEvaluateName(evaluateName, '.$getterName'),
- allowCallingToString:
- allowCallingToString && index <= maxToStringsPerEvaluation,
- );
+ try {
+ final response = await service.evaluate(
+ thread.isolate.id!,
+ instance.id!,
+ getterName,
+ );
+ // Convert results to variables.
+ return convertVmResponseToVariable(
+ thread,
+ response,
+ name: getterName,
+ evaluateName:
+ _adapter.combineEvaluateName(evaluateName, '.$getterName'),
+ allowCallingToString:
+ allowCallingToString && index <= maxToStringsPerEvaluation,
+ );
+ } catch (e) {
+ return dap.Variable(
+ name: getterName,
+ value: _adapter.extractEvaluationErrorMessage('$e'),
+ variablesReference: 0,
+ );
+ }
}
variables.addAll(await Future.wait(getterNames.mapIndexed(evaluate)));
@@ -309,13 +321,21 @@
);
} else if (response is vm.Sentinel) {
return dap.Variable(
- name: '<sentinel>',
+ name: name ?? '<sentinel>',
value: response.valueAsString.toString(),
variablesReference: 0,
);
+ } else if (response is vm.ErrorRef) {
+ final errorMessage = _adapter
+ .extractUnhandledExceptionMessage(response.message ?? '<error>');
+ return dap.Variable(
+ name: name ?? '<error>',
+ value: '<$errorMessage>',
+ variablesReference: 0,
+ );
} else {
return dap.Variable(
- name: '<error>',
+ name: name ?? '<error>',
value: response.runtimeType.toString(),
variablesReference: 0,
);
diff --git a/pkg/dds/test/dap/integration/debug_breakpoints_test.dart b/pkg/dds/test/dap/integration/debug_breakpoints_test.dart
index a9e8b85..c67890b 100644
--- a/pkg/dds/test/dap/integration/debug_breakpoints_test.dart
+++ b/pkg/dds/test/dap/integration/debug_breakpoints_test.dart
@@ -19,7 +19,7 @@
test('stops at a line breakpoint', () async {
final client = dap.client;
final testFile = dap.createTestFile(simpleBreakpointProgram);
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
await client.hitBreakpoint(testFile, breakpointLine);
});
@@ -27,7 +27,7 @@
test('stops at a line breakpoint and can be resumed', () async {
final client = dap.client;
final testFile = dap.createTestFile(simpleBreakpointProgram);
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
// Hit the initial breakpoint.
final stop = await client.hitBreakpoint(testFile, breakpointLine);
@@ -40,14 +40,14 @@
});
test('stops at a line breakpoint and can step over (next)', () async {
- final testFile = dap.createTestFile(r'''
+ final testFile = dap.createTestFile('''
void main(List<String> args) async {
- print('Hello!'); // BREAKPOINT
- print('Hello!'); // STEP
+ print('Hello!'); $breakpointMarker
+ print('Hello!'); $stepMarker
}
''');
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
- final stepLine = lineWith(testFile, '// STEP');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
+ final stepLine = lineWith(testFile, stepMarker);
// Hit the initial breakpoint.
final stop = await dap.client.hitBreakpoint(testFile, breakpointLine);
@@ -63,18 +63,18 @@
'stops at a line breakpoint and can step over (next) an async boundary',
() async {
final client = dap.client;
- final testFile = dap.createTestFile(r'''
+ final testFile = dap.createTestFile('''
Future<void> main(List<String> args) async {
- await asyncPrint('Hello!'); // BREAKPOINT
- await asyncPrint('Hello!'); // STEP
+ await asyncPrint('Hello!'); $breakpointMarker
+ await asyncPrint('Hello!'); $stepMarker
}
Future<void> asyncPrint(String message) async {
await Future.delayed(const Duration(milliseconds: 1));
}
''');
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
- final stepLine = lineWith(testFile, '// STEP');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
+ final stepLine = lineWith(testFile, stepMarker);
// Hit the initial breakpoint.
final stop = await dap.client.hitBreakpoint(testFile, breakpointLine);
@@ -96,17 +96,17 @@
test('stops at a line breakpoint and can step in', () async {
final client = dap.client;
- final testFile = dap.createTestFile(r'''
+ final testFile = dap.createTestFile('''
void main(List<String> args) async {
- log('Hello!'); // BREAKPOINT
+ log('Hello!'); $breakpointMarker
}
-void log(String message) { // STEP
+void log(String message) { $stepMarker
print(message);
}
''');
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
- final stepLine = lineWith(testFile, '// STEP');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
+ final stepLine = lineWith(testFile, stepMarker);
// Hit the initial breakpoint.
final stop = await client.hitBreakpoint(testFile, breakpointLine);
@@ -120,18 +120,18 @@
test('stops at a line breakpoint and can step out', () async {
final client = dap.client;
- final testFile = dap.createTestFile(r'''
+ final testFile = dap.createTestFile('''
void main(List<String> args) async {
log('Hello!');
- log('Hello!'); // STEP
+ log('Hello!'); $stepMarker
}
void log(String message) {
- print(message); // BREAKPOINT
+ print(message); $breakpointMarker
}
''');
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
- final stepLine = lineWith(testFile, '// STEP');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
+ final stepLine = lineWith(testFile, stepMarker);
// Hit the initial breakpoint.
final stop = await client.hitBreakpoint(testFile, breakpointLine);
@@ -145,14 +145,14 @@
test('does not step into SDK code with debugSdkLibraries=false', () async {
final client = dap.client;
- final testFile = dap.createTestFile(r'''
+ final testFile = dap.createTestFile('''
void main(List<String> args) async {
- print('Hello!'); // BREAKPOINT
- print('Hello!'); // STEP
+ print('Hello!'); $breakpointMarker
+ print('Hello!'); $stepMarker
}
''');
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
- final stepLine = lineWith(testFile, '// STEP');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
+ final stepLine = lineWith(testFile, stepMarker);
// Hit the initial breakpoint.
final stop = await client.hitBreakpoint(
@@ -173,13 +173,13 @@
test('steps into SDK code with debugSdkLibraries=true', () async {
final client = dap.client;
- final testFile = dap.createTestFile(r'''
+ final testFile = dap.createTestFile('''
void main(List<String> args) async {
- print('Hello!'); // BREAKPOINT
+ print('Hello!'); $breakpointMarker
print('Hello!');
}
''');
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
// Hit the initial breakpoint.
final stop = await dap.client.hitBreakpoint(
@@ -207,12 +207,12 @@
import '$otherPackageUri';
void main(List<String> args) async {
- foo(); // BREAKPOINT
- foo(); // STEP
+ foo(); $breakpointMarker
+ foo(); $stepMarker
}
''');
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
- final stepLine = lineWith(testFile, '// STEP');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
+ final stepLine = lineWith(testFile, stepMarker);
// Hit the initial breakpoint.
final stop = await client.hitBreakpoint(
@@ -240,11 +240,11 @@
import '$otherPackageUri';
void main(List<String> args) async {
- foo(); // BREAKPOINT
+ foo(); $breakpointMarker
foo();
}
''');
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
// Hit the initial breakpoint.
final stop = await dap.client.hitBreakpoint(
@@ -272,11 +272,11 @@
import '$otherPackageUri';
void main(List<String> args) async {
- foo(); // BREAKPOINT
+ foo(); $breakpointMarker
foo();
}
''');
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
// Hit the initial breakpoint.
final stop = await client.hitBreakpoint(
@@ -300,14 +300,14 @@
test('allows changing debug settings during session', () async {
final client = dap.client;
- final testFile = dap.createTestFile(r'''
+ final testFile = dap.createTestFile('''
void main(List<String> args) async {
- print('Hello!'); // BREAKPOINT
- print('Hello!'); // STEP
+ print('Hello!'); $breakpointMarker
+ print('Hello!'); $stepMarker
}
''');
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
- final stepLine = lineWith(testFile, '// STEP');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
+ final stepLine = lineWith(testFile, stepMarker);
// Start with debugSdkLibraryes _enabled_ and hit the breakpoint.
final stop = await client.hitBreakpoint(
@@ -337,7 +337,7 @@
test('stops with condition evaluating to true', () async {
final client = dap.client;
final testFile = dap.createTestFile(simpleBreakpointProgram);
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
await client.hitBreakpoint(
testFile,
@@ -349,7 +349,7 @@
test('does not stop with condition evaluating to false', () async {
final client = dap.client;
final testFile = dap.createTestFile(simpleBreakpointProgram);
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
await client.doNotHitBreakpoint(
testFile,
@@ -361,7 +361,7 @@
test('stops with condition evaluating to non-zero', () async {
final client = dap.client;
final testFile = dap.createTestFile(simpleBreakpointProgram);
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
await client.hitBreakpoint(
testFile,
@@ -373,7 +373,7 @@
test('does not stop with condition evaluating to zero', () async {
final client = dap.client;
final testFile = dap.createTestFile(simpleBreakpointProgram);
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
await client.doNotHitBreakpoint(
testFile,
@@ -385,7 +385,7 @@
test('reports evaluation errors for conditions', () async {
final client = dap.client;
final testFile = dap.createTestFile(simpleBreakpointProgram);
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
final outputEventsFuture = client.outputEvents.toList();
@@ -422,7 +422,7 @@
) async {
final client = dap.client;
final testFile = dap.createTestFile(simpleBreakpointProgram);
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
final outputEventsFuture = client.outputEvents.toList();
diff --git a/pkg/dds/test/dap/integration/debug_eval_test.dart b/pkg/dds/test/dap/integration/debug_eval_test.dart
index 301e180..9d6ff1b 100644
--- a/pkg/dds/test/dap/integration/debug_eval_test.dart
+++ b/pkg/dds/test/dap/integration/debug_eval_test.dart
@@ -19,14 +19,14 @@
group('debug mode evaluation', () {
test('evaluates expressions with simple results', () async {
final client = dap.client;
- final testFile = await dap.createTestFile(r'''
+ final testFile = await dap.createTestFile('''
void main(List<String> args) {
var a = 1;
var b = 2;
var c = 'test';
- print('Hello!'); // BREAKPOINT
+ print('Hello!'); $breakpointMarker
}''');
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
final topFrameId = await client.getTopFrameId(stop.threadId!);
@@ -38,7 +38,7 @@
test('evaluates expressions with complex results', () async {
final client = dap.client;
final testFile = await dap.createTestFile(simpleBreakpointProgram);
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
final topFrameId = await client.getTopFrameId(stop.threadId!);
@@ -60,13 +60,13 @@
test('evaluates expressions ending with semicolons', () async {
final client = dap.client;
- final testFile = await dap.createTestFile(r'''
+ final testFile = await dap.createTestFile('''
void main(List<String> args) {
var a = 1;
var b = 2;
- print('Hello!'); // BREAKPOINT
+ print('Hello!'); $breakpointMarker
}''');
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
final topFrameId = await client.getTopFrameId(stop.threadId!);
@@ -78,7 +78,7 @@
() async {
final client = dap.client;
final testFile = await dap.createTestFile(simpleBreakpointProgram);
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(
testFile,
@@ -154,16 +154,16 @@
test('can evaluate expressions in non-top frames', () async {
final client = dap.client;
- final testFile = await dap.createTestFile(r'''
+ final testFile = await dap.createTestFile('''
void main(List<String> args) {
var a = 999;
foo();
}
void foo() {
- var a = 111; // BREAKPOINT
+ var a = 111; $breakpointMarker
}''');
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
final stack = await client.getValidStack(stop.threadId!,
@@ -176,7 +176,7 @@
test('returns the full message for evaluation errors', () async {
final client = dap.client;
final testFile = await dap.createTestFile(simpleBreakpointProgram);
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
final topFrameId = await client.getTopFrameId(stop.threadId!);
@@ -199,7 +199,7 @@
test('returns short errors for evaluation in "watch" context', () async {
final client = dap.client;
final testFile = await dap.createTestFile(simpleBreakpointProgram);
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
final topFrameId = await client.getTopFrameId(stop.threadId!);
diff --git a/pkg/dds/test/dap/integration/debug_stack_test.dart b/pkg/dds/test/dap/integration/debug_stack_test.dart
index 2a348e8..72f2cb5 100644
--- a/pkg/dds/test/dap/integration/debug_stack_test.dart
+++ b/pkg/dds/test/dap/integration/debug_stack_test.dart
@@ -19,7 +19,7 @@
test('includes expected names and async boundaries', () async {
final client = dap.client;
final testFile = await dap.createTestFile(simpleAsyncProgram);
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
final stack = await client.getValidStack(
@@ -61,7 +61,7 @@
test('only sets canRestart where VM can rewind', () async {
final client = dap.client;
final testFile = await dap.createTestFile(simpleAsyncProgram);
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
final stack = await client.getValidStack(
@@ -92,7 +92,7 @@
test('deemphasizes SDK frames when debugSdk=false', () async {
final client = dap.client;
final testFile = await dap.createTestFile(sdkStackFrameProgram);
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(
testFile,
@@ -124,7 +124,7 @@
test('does not deemphasize SDK frames when debugSdk=true', () async {
final client = dap.client;
final testFile = await dap.createTestFile(sdkStackFrameProgram);
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(
testFile,
diff --git a/pkg/dds/test/dap/integration/debug_test.dart b/pkg/dds/test/dap/integration/debug_test.dart
index 3a58c37..8c6a42a 100644
--- a/pkg/dds/test/dap/integration/debug_test.dart
+++ b/pkg/dds/test/dap/integration/debug_test.dart
@@ -101,7 +101,7 @@
test('provides a list of threads', () async {
final client = dap.client;
final testFile = dap.createTestFile(simpleBreakpointProgram);
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
await client.hitBreakpoint(testFile, breakpointLine);
final response = await client.getValidThreads();
@@ -113,7 +113,7 @@
test('runs with DDS by default', () async {
final client = dap.client;
final testFile = dap.createTestFile(simpleBreakpointProgram);
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
await client.hitBreakpoint(testFile, breakpointLine);
expect(await client.ddsAvailable, isTrue);
@@ -130,7 +130,7 @@
test('can download source code from the VM', () async {
final client = dap.client;
final testFile = dap.createTestFile(simpleBreakpointProgram);
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
// Hit the initial breakpoint.
final stop = await dap.client.hitBreakpoint(
@@ -175,7 +175,7 @@
final client = dap.client;
final testFile = dap.createTestFile(simpleBreakpointProgram);
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
await client.hitBreakpoint(testFile, breakpointLine);
diff --git a/pkg/dds/test/dap/integration/debug_variables_test.dart b/pkg/dds/test/dap/integration/debug_variables_test.dart
index 8acecb6..86f4412 100644
--- a/pkg/dds/test/dap/integration/debug_variables_test.dart
+++ b/pkg/dds/test/dap/integration/debug_variables_test.dart
@@ -5,6 +5,7 @@
import 'package:test/test.dart';
import 'test_client.dart';
+import 'test_scripts.dart';
import 'test_support.dart';
main() {
@@ -17,7 +18,7 @@
group('debug mode variables', () {
test('provides variable list for frames', () async {
final client = dap.client;
- final testFile = await dap.createTestFile(r'''
+ final testFile = await dap.createTestFile('''
void main(List<String> args) {
final myVariable = 1;
foo();
@@ -25,10 +26,10 @@
void foo() {
final b = 2;
- print('Hello!'); // BREAKPOINT
+ print('Hello!'); $breakpointMarker
}
''');
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
final stack = await client.getValidStack(
@@ -64,12 +65,7 @@
''');
final stop = await client.hitException(testFile);
- final stack = await client.getValidStack(
- stop.threadId!,
- startFrame: 0,
- numFrames: 1,
- );
- final topFrameId = stack.stackFrames.first.id;
+ final topFrameId = await client.getTopFrameId(stop.threadId!);
// Check for an additional Scope named "Exceptions" that includes the
// exception.
@@ -82,7 +78,7 @@
);
});
- test('provides complex exception types frames', () async {
+ test('provides complex exception types for frames', () async {
final client = dap.client;
final testFile = await dap.createTestFile(r'''
void main(List<String> args) {
@@ -91,12 +87,7 @@
''');
final stop = await client.hitException(testFile);
- final stack = await client.getValidStack(
- stop.threadId!,
- startFrame: 0,
- numFrames: 1,
- );
- final topFrameId = stack.stackFrames.first.id;
+ final topFrameId = await client.getTopFrameId(stop.threadId!);
// Check for an additional Scope named "Exceptions" that includes the
// exception.
@@ -113,13 +104,13 @@
test('includes simple variable fields', () async {
final client = dap.client;
- final testFile = await dap.createTestFile(r'''
+ final testFile = await dap.createTestFile('''
void main(List<String> args) {
final myVariable = DateTime(2000, 1, 1);
- print('Hello!'); // BREAKPOINT
+ print('Hello!'); $breakpointMarker
}
''');
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
await client.expectLocalVariable(
@@ -135,13 +126,13 @@
test('includes variable getters when evaluateGettersInDebugViews=true',
() async {
final client = dap.client;
- final testFile = await dap.createTestFile(r'''
+ final testFile = await dap.createTestFile('''
void main(List<String> args) {
final myVariable = DateTime(2000, 1, 1);
- print('Hello!'); // BREAKPOINT
+ print('Hello!'); $breakpointMarker
}
''');
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(
testFile,
@@ -181,13 +172,13 @@
test('renders a simple list', () async {
final client = dap.client;
- final testFile = await dap.createTestFile(r'''
+ final testFile = await dap.createTestFile('''
void main(List<String> args) {
final myVariable = ["first", "second", "third"];
- print('Hello!'); // BREAKPOINT
+ print('Hello!'); $breakpointMarker
}
''');
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
await client.expectLocalVariable(
@@ -204,13 +195,13 @@
test('renders a simple list subset', () async {
final client = dap.client;
- final testFile = await dap.createTestFile(r'''
+ final testFile = await dap.createTestFile('''
void main(List<String> args) {
final myVariable = ["first", "second", "third"];
- print('Hello!'); // BREAKPOINT
+ print('Hello!'); $breakpointMarker
}
''');
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
await client.expectLocalVariable(
@@ -227,17 +218,17 @@
test('renders a simple map with keys/values', () async {
final client = dap.client;
- final testFile = await dap.createTestFile(r'''
+ final testFile = await dap.createTestFile('''
void main(List<String> args) {
final myVariable = {
'zero': 0,
'one': 1,
'two': 2
};
- print('Hello!'); // BREAKPOINT
+ print('Hello!'); $breakpointMarker
}
''');
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
final variables = await client.expectLocalVariable(
@@ -270,17 +261,17 @@
test('renders a simple map subset', () async {
final client = dap.client;
- final testFile = await dap.createTestFile(r'''
+ final testFile = await dap.createTestFile('''
void main(List<String> args) {
final myVariable = {
'zero': 0,
'one': 1,
'two': 2
};
- print('Hello!'); // BREAKPOINT
+ print('Hello!'); $breakpointMarker
}
''');
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
await client.expectLocalVariable(
@@ -300,15 +291,15 @@
test('renders a complex map with keys/values', () async {
final client = dap.client;
- final testFile = await dap.createTestFile(r'''
+ final testFile = await dap.createTestFile('''
void main(List<String> args) {
final myVariable = {
DateTime(2000, 1, 1): Exception("my error")
};
- print('Hello!'); // BREAKPOINT
+ print('Hello!'); $breakpointMarker
}
''');
- final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
final mapVariables = await client.expectLocalVariable(
@@ -355,6 +346,109 @@
''',
);
});
+
+ test('calls toString() on custom classes', () async {
+ final client = dap.client;
+ final testFile = await dap.createTestFile('''
+class Foo {
+ toString() => 'Bar!';
+}
+
+void main() {
+ final myVariable = Foo();
+ print('Hello!'); $breakpointMarker
+}
+ ''');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
+
+ final stop = await client.hitBreakpoint(
+ testFile,
+ breakpointLine,
+ launch: () => client.launch(
+ testFile.path,
+ evaluateToStringInDebugViews: true,
+ ),
+ );
+
+ await client.expectScopeVariables(
+ await client.getTopFrameId(stop.threadId!),
+ 'Locals',
+ r'''
+ myVariable: Foo (Bar!), eval: myVariable
+ ''',
+ );
+ });
+
+ test('does not use toString() result if "Instance of Foo"', () async {
+ // When evaluateToStringInDebugViews=true, we should discard the result of
+ // caling toString() when it's just 'Instance of Foo' because we're already
+ // showing the type, and otherwise we show:
+ //
+ // myVariable: Foo (Instance of Foo)
+ final client = dap.client;
+ final testFile = await dap.createTestFile('''
+class Foo {}
+
+void main() {
+ final myVariable = Foo();
+ print('Hello!'); $breakpointMarker
+}
+ ''');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
+
+ final stop = await client.hitBreakpoint(
+ testFile,
+ breakpointLine,
+ launch: () => client.launch(
+ testFile.path,
+ evaluateToStringInDebugViews: true,
+ ),
+ );
+
+ await client.expectScopeVariables(
+ await client.getTopFrameId(stop.threadId!),
+ 'Locals',
+ r'''
+ myVariable: Foo, eval: myVariable
+ ''',
+ );
+ });
+
+ test('handles errors in getters', () async {
+ final client = dap.client;
+ final testFile = await dap.createTestFile('''
+class Foo {
+ String get doesNotThrow => "success";
+ String get throws => throw Exception('err');
+}
+
+void main() {
+ final myVariable = Foo();
+ print('Hello!'); $breakpointMarker
+}
+ ''');
+ final breakpointLine = lineWith(testFile, breakpointMarker);
+
+ final stop = await client.hitBreakpoint(
+ testFile,
+ breakpointLine,
+ launch: () => client.launch(
+ testFile.path,
+ evaluateGettersInDebugViews: true,
+ ),
+ );
+
+ await client.expectLocalVariable(
+ stop.threadId!,
+ expectedName: 'myVariable',
+ expectedDisplayString: 'Foo',
+ expectedVariables: '''
+ doesNotThrow: "success", eval: myVariable.doesNotThrow
+ throws: <Exception: err>
+ ''',
+ ignore: {'runtimeType'},
+ );
+ });
// These tests can be slow due to starting up the external server process.
}, timeout: Timeout.none);
}
diff --git a/pkg/dds/test/dap/integration/test_scripts.dart b/pkg/dds/test/dap/integration/test_scripts.dart
index 5582f34..df3ed5d 100644
--- a/pkg/dds/test/dap/integration/test_scripts.dart
+++ b/pkg/dds/test/dap/integration/test_scripts.dart
@@ -2,6 +2,9 @@
// 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.
+/// A marker used in some test scripts/tests for where to set breakpoints.
+const breakpointMarker = '// BREAKPOINT';
+
/// A simple empty Dart script that should run with no output and no errors.
const emptyProgram = '''
void main(List<String> args) {}
@@ -9,17 +12,17 @@
/// A simple async Dart script that when stopped at the line of '// BREAKPOINT'
/// will contain SDK frames in the call stack.
-const sdkStackFrameProgram = r'''
+const sdkStackFrameProgram = '''
void main() {
[0].where((i) {
- return i == 0; // BREAKPOINT
+ return i == 0; $breakpointMarker
}).toList();
}
''';
/// A simple async Dart script that when stopped at the line of '// BREAKPOINT'
/// will contain multiple stack frames across some async boundaries.
-const simpleAsyncProgram = r'''
+const simpleAsyncProgram = '''
import 'dart:async';
Future<void> main() async {
@@ -40,16 +43,16 @@
}
void four() {
- print('!'); // BREAKPOINT
+ print('!'); $breakpointMarker
}
''';
/// A simple Dart script that should run with no errors and contains a comment
/// marker '// BREAKPOINT' for use in tests that require stopping at a breakpoint
/// but require no other context.
-const simpleBreakpointProgram = r'''
+const simpleBreakpointProgram = '''
void main(List<String> args) async {
- print('Hello!'); // BREAKPOINT
+ print('Hello!'); $breakpointMarker
}
''';
@@ -70,3 +73,6 @@
throw 'error';
}
''';
+
+/// A marker used in some test scripts/tests for where to expected steps.
+const stepMarker = '// STEP';