8bd3690ab0
This is a reland of commit d84f908641
Includes fixes:
--- Catch scope fix ---
Each catch block should define its own scope, but the compiler was not treating the body of these catch blocks as a scope. This was leading to incorrect variable renaming.
--- Duplicate label fix ---
Labels weren't being removed from the _labelNames Map after being used so multiple nodes were getting tagged with the same label. Note: This didn't actually cause any bugs because the label closer to the break statements using those labels was the correct one.
--- addAsyncCallback cleanup ---
The new async semantics actually don't require managing addAsyncCallback and removeAsyncCallback calls (proxies for async_helper.asyncStart and async_helper.asyncEnd). None of the other backends manage these themselves. It's up to tests to ensure they call async_helper.asyncStart and async_helper.asyncEnd.
Original change's description:
> [ddc] Update DDC compiler to start using the new async transform.
>
> Updates compiler.dart to use the new async transformation.
>
> Some key things to note:
> - Dart Let and BlockExpression expressions are represented as IIFEs in DDC compiled code. For non-async code this works fine but this doesn't work when they contain "await" expressions. When these expressions contain awaits we use the same lowering as we would for an async function, but instead apply it to the IIFE function. Then we simply await the IIFE Call expression as the IIFE will return a future after the transform.
> - For async/sync*/async* functions we want to make sure parameter initialization happens synchronously before any of the async logic is hit. To do this we first apply the async transform the user-code function body. We then prepend the paramter initialization logic to the body of the transformed function.
> - We add support for JS_RAW_EXCEPTION which allows the machinery in async_patch to access the wrapped JS exception in a catch block rather than the unwrapped Dart exception.
> - Stacktraces and sourcemaps have some differences. There is still room for improvement in these but they should at least allow users to reasonably step through parts of the async code.
>
>
> There are also several test fixes/updates associated with this change. The test_runner wrapper no longer has to inject in asyncStart/asyncEnd calls, these are handled by the new async logic.
>
> Change-Id: I0f9f547cd9eb52ff7d850d277876d4d57568a14e
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/374444
> Reviewed-by: Bob Nystrom <rnystrom@google.com>
> Reviewed-by: Mark Zhou <markzipan@google.com>
> Reviewed-by: Johnni Winther <johnniwinther@google.com>
> Reviewed-by: Nicholas Shahan <nshahan@google.com>
Change-Id: Idfe05c0628b2b91f474d08d99427961381debeb5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/376000
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Bob Nystrom <rnystrom@google.com>
Reviewed-by: Mark Zhou <markzipan@google.com>
369 lines
12 KiB
Dart
369 lines
12 KiB
Dart
/// Debugger custom formatter tests.
|
|
/// If the tests fail, paste the expected output into the [expectedGolden]
|
|
/// string literal in this file and audit the diff to ensure changes are
|
|
/// expected.
|
|
///
|
|
/// Currently only DDC supports debugging objects with custom formatters
|
|
/// but it is reasonable to add support to Dart2JS in the future.
|
|
@JS()
|
|
library debugger_test;
|
|
|
|
import 'dart:html';
|
|
import 'package:async_helper/async_helper.dart';
|
|
import 'package:js/js.dart';
|
|
import 'package:js/js_util.dart' as js_util;
|
|
|
|
import 'package:expect/minitest.dart'; // ignore: deprecated_member_use_from_same_package
|
|
|
|
import 'dart:_debugger' as _debugger;
|
|
|
|
class TestClass {
|
|
String name = 'test class';
|
|
int date;
|
|
static List<int> foo = [1, 2, 3, 4];
|
|
static String greeting = 'Hello world';
|
|
static Object bar = new Object();
|
|
|
|
static exampleStaticMethod(x) => x * 2;
|
|
|
|
TestClass(this.date);
|
|
|
|
String nameAndDate() => '$name on day $date';
|
|
|
|
int last(List<int> list) => list.last;
|
|
|
|
void addOne(String name) {
|
|
name = '${name}1';
|
|
}
|
|
|
|
get someInt => 42;
|
|
get someString => "Hello world";
|
|
get someObject => this;
|
|
|
|
Object returnObject() => bar;
|
|
}
|
|
|
|
class TestGenericClass<X, Y> {
|
|
TestGenericClass(this.x);
|
|
X x;
|
|
}
|
|
|
|
@JS('Object.getOwnPropertyNames')
|
|
external List getOwnPropertyNames(obj);
|
|
|
|
@JS('devtoolsFormatters')
|
|
external List get _devtoolsFormatters;
|
|
List get devtoolsFormatters => _devtoolsFormatters;
|
|
|
|
@JS('JSON.stringify')
|
|
external stringify(value, [Function replacer, int space]);
|
|
|
|
// TODO(jacobr): this is only valid if the DDC library loader is used.
|
|
// We need a solution that works with all library loaders.
|
|
@JS('dart_library.import')
|
|
external importDartLibrary(String path);
|
|
|
|
@JS('ExampleJSClass')
|
|
class ExampleJSClass<T> {
|
|
external factory ExampleJSClass(T x);
|
|
external T get x;
|
|
}
|
|
|
|
// Replacer normalizes file names that could vary depending on the test runner.
|
|
// styles.
|
|
replacer(String key, value) {
|
|
// The values for keys with name 'object' may be arbitrary Dart nested
|
|
// Objects so are not safe to stringify.
|
|
if (key == 'object') return '<OBJECT>';
|
|
if (value is String) {
|
|
if (value.contains('dart_sdk.js')) return '<DART_SDK>';
|
|
if (new RegExp(r'[.](js|dart|html)').hasMatch(value)) return '<FILE>';
|
|
// Normalize the name of the `Event` type as it appears in this test.
|
|
// The new type system preserves the original name from the Dart source.
|
|
// TODO(48585): Remove when no longer running with the old type system.
|
|
value = value.replaceAll(r'Event$', 'Event');
|
|
}
|
|
return value;
|
|
}
|
|
|
|
String? format(value) {
|
|
// Avoid double-escaping strings.
|
|
if (value is String) return value;
|
|
if (value is Function) value = allowInterop(value);
|
|
return stringify(value, allowInterop(replacer), 4);
|
|
}
|
|
|
|
class FormattedObject {
|
|
FormattedObject(this.object, this.config);
|
|
|
|
Object? object;
|
|
Object? config;
|
|
}
|
|
|
|
/// Extract all object tags from a json ml expression to enable
|
|
/// calling the custom formatter on the extracted object tag.
|
|
List<FormattedObject> extractNestedFormattedObjects(json) {
|
|
var ret = <FormattedObject>[];
|
|
if (json is String || json is bool || json is num) return ret;
|
|
if (json is List) {
|
|
for (var e in json) {
|
|
ret.addAll(extractNestedFormattedObjects(e));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
for (var name in getOwnPropertyNames(json)) {
|
|
if (name == 'object') {
|
|
// Found a nested formatted object.
|
|
ret.add(new FormattedObject(js_util.getProperty(json, 'object'),
|
|
js_util.getProperty(json, 'config')));
|
|
return ret;
|
|
}
|
|
ret.addAll(extractNestedFormattedObjects(js_util.getProperty(json, name)));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
main() async {
|
|
asyncStart();
|
|
if (devtoolsFormatters == null) {
|
|
print("Warning: no devtools custom formatters specified. Skipping tests.");
|
|
return;
|
|
}
|
|
|
|
// Cache blocker is a workaround for:
|
|
// https://code.google.com/p/dart/issues/detail?id=11834
|
|
var cacheBlocker = new DateTime.now().millisecondsSinceEpoch;
|
|
var goldenUrl = '/root_dart/tests/dartdevc/debugger/'
|
|
'debugger_test_golden.txt?cacheBlock=$cacheBlocker';
|
|
|
|
String? golden;
|
|
try {
|
|
golden = (await HttpRequest.getString(goldenUrl)).trim();
|
|
} catch (e) {
|
|
print("Warning: couldn't load golden file from $goldenUrl");
|
|
}
|
|
|
|
document.body!.append(new ScriptElement()
|
|
..type = 'text/javascript'
|
|
..innerHtml = r"""
|
|
window.ExampleJSClass = function ExampleJSClass(x) {
|
|
this.x = x;
|
|
};
|
|
""");
|
|
|
|
var _devtoolsFormatter = devtoolsFormatters.first;
|
|
|
|
var actual = new StringBuffer();
|
|
|
|
// Accumulate the entire expected custom formatted data as a single
|
|
// massive string buffer so it is simple to update expectations when
|
|
// modifying the formatting code.
|
|
// Otherwise a small formatting change would result in tweaking lots
|
|
// of expectations.
|
|
// The verify golden match test cases does the final comparison of golden
|
|
// to expected output.
|
|
addGolden(String name, value) {
|
|
var text = format(value);
|
|
actual.write('Test: $name\n'
|
|
'Value:\n'
|
|
'$text\n'
|
|
'-----------------------------------\n');
|
|
}
|
|
|
|
addFormatterGoldens(String name, object, [config]) {
|
|
addGolden(
|
|
'$name formatting header', _devtoolsFormatter.header(object, config));
|
|
addGolden('$name formatting body', _devtoolsFormatter.body(object, config));
|
|
}
|
|
|
|
// Include goldens for the nested [[class]] definition field.
|
|
addNestedFormatterGoldens(String name, obj) {
|
|
addGolden('$name instance header', _devtoolsFormatter.header(obj, null));
|
|
var body = _devtoolsFormatter.body(obj, null);
|
|
addGolden('$name instance body', body);
|
|
|
|
var nestedObjects = extractNestedFormattedObjects(body);
|
|
var clazz = nestedObjects.last;
|
|
// By convention assume last nested object is the [[class]] definition
|
|
// describing the object's static members and inheritance hierarchy
|
|
addFormatterGoldens('$name definition', clazz.object, clazz.config);
|
|
}
|
|
|
|
// Include goldens for the nested [[class]] definition field.
|
|
addAllNestedFormatterGoldens(String name, obj) {
|
|
addGolden('$name header', _devtoolsFormatter.header(obj, null));
|
|
var body = _devtoolsFormatter.body(obj, null);
|
|
addGolden('$name body', body);
|
|
|
|
var nestedObjects = extractNestedFormattedObjects(body);
|
|
var i = 0;
|
|
for (var nested in nestedObjects) {
|
|
addFormatterGoldens('$name child $i', nested.object, nested.config);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
group('Iterable formatting', () {
|
|
var list = ['foo', 'bar', 'baz'];
|
|
var iterable = list.map((x) => x * 5);
|
|
addFormatterGoldens('List<String>', list);
|
|
|
|
var listOfObjects = <Object>[42, 'bar', true];
|
|
|
|
addNestedFormatterGoldens('List<Object>', listOfObjects);
|
|
|
|
var largeList = <int>[];
|
|
for (var i = 0; i < 200; ++i) {
|
|
largeList.add(i * 10);
|
|
}
|
|
addNestedFormatterGoldens('List<int> large', largeList);
|
|
|
|
addNestedFormatterGoldens('Iterable', iterable);
|
|
|
|
var s = new Set()
|
|
..add("foo")
|
|
..add(42)
|
|
..add(true);
|
|
addNestedFormatterGoldens('Set', s);
|
|
});
|
|
|
|
group('Map formatting', () {
|
|
Map<String, int> foo = new Map();
|
|
foo = {'1': 2, 'x': 4, '5': 6};
|
|
|
|
addFormatterGoldens('Map<String, int>', foo);
|
|
test('hasBody', () {
|
|
expect(_devtoolsFormatter.hasBody(foo, null), isTrue);
|
|
});
|
|
|
|
Map<dynamic, dynamic> dynamicMap = new Map();
|
|
dynamicMap = {1: 2, 'x': 4, true: "truthy"};
|
|
|
|
addNestedFormatterGoldens('Map<dynamic, dynamic>', dynamicMap);
|
|
});
|
|
|
|
group('Function formatting', () {
|
|
adder(int a, int b) => a + b;
|
|
|
|
addFormatterGoldens('Function', adder);
|
|
|
|
test('hasBody', () {
|
|
expect(_devtoolsFormatter.hasBody(adder, null), isTrue);
|
|
});
|
|
|
|
addEventListener(String name, bool callback(Event e)) => null;
|
|
|
|
addFormatterGoldens('Function with function arguments', addEventListener);
|
|
|
|
// Closure
|
|
addGolden('dart:html method', window.addEventListener);
|
|
|
|
// Get a reference to the JS constructor for a Dart class.
|
|
// This tracks a regression bug where overly verbose and confusing output
|
|
// was shown for this case.
|
|
var testClass = new TestClass(17);
|
|
var dartConstructor = js_util.getProperty(
|
|
js_util.objectGetPrototypeOf(testClass)!, 'constructor');
|
|
addFormatterGoldens('Raw reference to dart constructor', dartConstructor);
|
|
});
|
|
|
|
group('Object formatting', () {
|
|
var object = new Object();
|
|
addFormatterGoldens('Object', object);
|
|
test('hasBody', () {
|
|
expect(_devtoolsFormatter.hasBody(object, null), isTrue);
|
|
});
|
|
});
|
|
|
|
group('Type formatting', () {
|
|
addFormatterGoldens('Type TestClass', TestClass);
|
|
addFormatterGoldens('Type HttpRequest', HttpRequest);
|
|
});
|
|
|
|
group('JS interop object formatting', () {
|
|
var object = js_util.newObject();
|
|
js_util.setProperty(object, 'foo', 'bar');
|
|
// Make sure we don't apply the Dart custom formatter to JS interop objects.
|
|
expect(_devtoolsFormatter.header(object, null), isNull);
|
|
});
|
|
|
|
group('Module formatting', () {
|
|
var moduleNames = _debugger.getModuleNames();
|
|
var testModuleName = "debugger_test";
|
|
expect(moduleNames.contains(testModuleName), isTrue);
|
|
|
|
addAllNestedFormatterGoldens(
|
|
'Test library Module', _debugger.getModuleLibraries(testModuleName));
|
|
});
|
|
|
|
group('StackTrace formatting', () {
|
|
StackTrace stack;
|
|
try {
|
|
throw new Error();
|
|
} catch (exception, stackTrace) {
|
|
stack = stackTrace;
|
|
}
|
|
addFormatterGoldens('StackTrace', stack);
|
|
test('hasBody', () {
|
|
expect(_devtoolsFormatter.hasBody(stack, null), isTrue);
|
|
});
|
|
});
|
|
|
|
group('Class formatting', () {
|
|
addNestedFormatterGoldens('TestClass', new TestClass(17));
|
|
// TODO(jmesserly): this includes a timeStamp, so it varies each run.
|
|
//addNestedFormatterGoldens('MouseEvent', new MouseEvent("click"));
|
|
// This is a good class to test as it has statics and a deep inheritance hierarchy
|
|
addNestedFormatterGoldens('HttpRequest', new HttpRequest());
|
|
});
|
|
|
|
group('Generics formatting', () {
|
|
addNestedFormatterGoldens(
|
|
'TestGenericClass', new TestGenericClass<int, List>(42));
|
|
addNestedFormatterGoldens(
|
|
'TestGenericClassJSInterop',
|
|
new TestGenericClass<ExampleJSClass<String>, int>(
|
|
new ExampleJSClass("Hello")));
|
|
});
|
|
|
|
test('verify golden match', () {
|
|
// Warning: all other test groups must have run for this test to be meaningful
|
|
var actualStr = actual.toString().trim();
|
|
|
|
if (actualStr != golden) {
|
|
var helpMessage =
|
|
'Debugger output does not match the golden data found in:\n'
|
|
'tests/dartdevc/debugger/debugger_test_golden.txt\n'
|
|
'The new golden data is copied to the clipboard when you click on '
|
|
'this window.\n'
|
|
'Please update the golden file with the following output and review '
|
|
'the diff using your favorite diff tool to make sure the custom '
|
|
'formatting output has not regressed.';
|
|
print(helpMessage);
|
|
// Copy text to clipboard on page click. We can't copy to the clipboard
|
|
// without a click due to Chrome security.
|
|
var textField = new Element.tag('textarea') as TextAreaElement;
|
|
textField.maxLength = 100000000;
|
|
textField.text = actualStr;
|
|
textField.style
|
|
..width = '800px'
|
|
..height = '400px';
|
|
document.body!.append(new Element.tag('h3')
|
|
..innerHtml = helpMessage.replaceAll('\n', '<br>'));
|
|
document.body!.append(textField);
|
|
document.body!.onClick.listen((_) {
|
|
textField.select();
|
|
var result = document.execCommand('copy');
|
|
if (result) {
|
|
print("Copy to clipboard successful");
|
|
} else {
|
|
print("Copy to clipboard failed");
|
|
}
|
|
});
|
|
}
|
|
expect(actualStr == golden, isTrue);
|
|
asyncEnd();
|
|
});
|
|
}
|