blob: c740bc2826fa4ae9ee3c6e106c89000ce24c5f58 [file] [log] [blame]
// Copyright (c) 2021, 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.
import 'package:test/test.dart';
import 'test_client.dart';
import 'test_scripts.dart';
import 'test_support.dart';
main() {
late DapTestSession dap;
setUp(() async {
dap = await DapTestSession.setUp();
});
tearDown(() => dap.tearDown());
group('debug mode variables', () {
test('provides variable list for frames', () async {
final client = dap.client;
final testFile = dap.createTestFile('''
void main(List<String> args) {
final myVariable = 1;
foo();
}
void foo() {
final b = 2;
print('Hello!'); $breakpointMarker
}
''');
final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
final stack = await client.getValidStack(
stop.threadId!,
startFrame: 0,
numFrames: 2,
);
// Check top two frames (in `foo` and in `main`).
await client.expectScopeVariables(
stack.stackFrames[0].id, // Top frame: foo
'Locals',
'''
b: 2, eval: b
''',
);
await client.expectScopeVariables(
stack.stackFrames[1].id, // Second frame: main
'Locals',
'''
args: List (0 items), eval: args
myVariable: 1, eval: myVariable
''',
);
});
test('provides simple exception types for frames', () async {
final client = dap.client;
final testFile = dap.createTestFile(r'''
void main(List<String> args) {
throw 'my error';
}
''');
final stop = await client.hitException(testFile);
final topFrameId = await client.getTopFrameId(stop.threadId!);
// Check for an additional Scope named "Exceptions" that includes the
// exception.
await client.expectScopeVariables(
topFrameId,
'Exceptions',
r'''
String: "my error", eval: $_threadException
''',
);
});
test('provides complex exception types for frames', () async {
final client = dap.client;
final testFile = dap.createTestFile(r'''
void main(List<String> args) {
throw ArgumentError.notNull('args');
}
''');
final stop = await client.hitException(testFile);
final topFrameId = await client.getTopFrameId(stop.threadId!);
// Check for an additional Scope named "Exceptions" that includes the
// exception.
await client.expectScopeVariables(
topFrameId,
'Exceptions',
r'''
invalidValue: null, eval: $_threadException.invalidValue
message: "Must not be null", eval: $_threadException.message
name: "args", eval: $_threadException.name
''',
);
});
test('includes simple variable fields', () async {
final client = dap.client;
final testFile = dap.createTestFile('''
void main(List<String> args) {
final myVariable = DateTime(2000, 1, 1);
print('Hello!'); $breakpointMarker
}
''');
final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
await client.expectLocalVariable(
stop.threadId!,
expectedName: 'myVariable',
expectedDisplayString: 'DateTime',
expectedVariables: '''
isUtc: false, eval: myVariable.isUtc
''',
);
});
test('includes variable getters when evaluateGettersInDebugViews=true',
() async {
final client = dap.client;
final testFile = dap.createTestFile('''
void main(List<String> args) {
final myVariable = DateTime(2000, 1, 1);
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: 'DateTime',
expectedVariables: '''
day: 1, eval: myVariable.day
hour: 0, eval: myVariable.hour
isUtc: false, eval: myVariable.isUtc
microsecond: 0, eval: myVariable.microsecond
millisecond: 0, eval: myVariable.millisecond
minute: 0, eval: myVariable.minute
month: 1, eval: myVariable.month
runtimeType: Type (DateTime), eval: myVariable.runtimeType
second: 0, eval: myVariable.second
timeZoneOffset: Duration, eval: myVariable.timeZoneOffset
weekday: 6, eval: myVariable.weekday
year: 2000, eval: myVariable.year
''',
ignore: {
// Don't check fields that may very based on timezone as it'll make
// these tests fragile, and this isn't really what's being tested.
'timeZoneName',
'microsecondsSinceEpoch',
'millisecondsSinceEpoch',
},
);
});
test('renders a simple list', () async {
final client = dap.client;
final testFile = dap.createTestFile('''
void main(List<String> args) {
final myVariable = ["first", "second", "third"];
print('Hello!'); $breakpointMarker
}
''');
final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
await client.expectLocalVariable(
stop.threadId!,
expectedName: 'myVariable',
expectedDisplayString: 'List (3 items)',
expectedVariables: '''
[0]: "first", eval: myVariable[0]
[1]: "second", eval: myVariable[1]
[2]: "third", eval: myVariable[2]
''',
);
});
test('renders a simple list subset', () async {
final client = dap.client;
final testFile = dap.createTestFile('''
void main(List<String> args) {
final myVariable = ["first", "second", "third"];
print('Hello!'); $breakpointMarker
}
''');
final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
await client.expectLocalVariable(
stop.threadId!,
expectedName: 'myVariable',
expectedDisplayString: 'List (3 items)',
expectedVariables: '''
[1]: "second", eval: myVariable[1]
''',
start: 1,
count: 1,
);
});
test('renders a simple map with keys/values', () async {
final client = dap.client;
final testFile = dap.createTestFile('''
void main(List<String> args) {
final myVariable = {
'zero': 0,
'one': 1,
'two': 2
};
print('Hello!'); $breakpointMarker
}
''');
final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
final variables = await client.expectLocalVariable(
stop.threadId!,
expectedName: 'myVariable',
expectedDisplayString: 'Map (3 items)',
// For maps, we render a level of MapAssociates first, which show
// their index numbers. Expanding them has a Key and a Value "field"
// which correspond to the items.
expectedVariables: '''
0: "zero" -> 0
1: "one" -> 1
2: "two" -> 2
''',
);
// Check one of the MapAssociation variables has the correct Key/Value
// inside.
expect(variables.variables, hasLength(3));
final variableOne = variables.variables[1];
expect(variableOne.variablesReference, isPositive);
await client.expectVariables(
variableOne.variablesReference,
'''
key: "one"
value: 1, eval: myVariable["one"]
''',
);
});
test('renders a simple map subset', () async {
final client = dap.client;
final testFile = dap.createTestFile('''
void main(List<String> args) {
final myVariable = {
'zero': 0,
'one': 1,
'two': 2
};
print('Hello!'); $breakpointMarker
}
''');
final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
await client.expectLocalVariable(
stop.threadId!,
expectedName: 'myVariable',
expectedDisplayString: 'Map (3 items)',
// For maps, we render a level of MapAssociates first, which show
// their index numbers. Expanding them has a Key and a Value "field"
// which correspond to the items.
expectedVariables: '''
1: "one" -> 1
''',
start: 1,
count: 1,
);
});
test('renders a complex map with keys/values', () async {
final client = dap.client;
final testFile = dap.createTestFile('''
void main(List<String> args) {
final myVariable = {
DateTime(2000, 1, 1): Exception("my error")
};
print('Hello!'); $breakpointMarker
}
''');
final breakpointLine = lineWith(testFile, breakpointMarker);
final stop = await client.hitBreakpoint(testFile, breakpointLine);
final mapVariables = await client.expectLocalVariable(
stop.threadId!,
expectedName: 'myVariable',
expectedDisplayString: 'Map (1 item)',
expectedVariables: '''
0: DateTime -> _Exception
''',
);
// Check one of the MapAssociation variables has the correct Key/Value
// inside.
expect(mapVariables.variables, hasLength(1));
final mapVariable = mapVariables.variables[0];
expect(mapVariable.variablesReference, isPositive);
final variables = await client.expectVariables(
mapVariable.variablesReference,
// We don't expect an evaluateName because the key is not a simple type.
'''
key: DateTime
value: _Exception
''',
);
// Check the Key can be drilled into.
expect(variables.variables, hasLength(2));
final keyVariable = variables.variables[0];
expect(keyVariable.variablesReference, isPositive);
await client.expectVariables(
keyVariable.variablesReference,
'''
isUtc: false
''',
);
// Check the Value can be drilled into.
final valueVariable = variables.variables[1];
expect(valueVariable.variablesReference, isPositive);
await client.expectVariables(
valueVariable.variablesReference,
'''
message: "my error"
''',
);
});
test('calls toString() on custom classes', () async {
final client = dap.client;
final testFile = 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
// calling 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 = 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 = 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);
}