cd2c566bcf
This reverts commit ff5f391c0a.
Reason for revert: The expect library is used by Flutter engine, and some of its tests use assertStatementsEnabled. There should be a migration path that doesn't require an atomic change, like adding the replacement api before removing the old one.
Original change's description:
> Tweak `expect.dart` library.
>
> Make API more consistent for a few methods.
> Reduce the number of language features used in tests:
> * Never iterating an iterable, always converting it
> using `.toList()` first and iterating using indices
> (fx `setEquals`).
> Also require a `List` in places where an `Iterable`
> wasn't necessary.
> * Avoid doing complicated computations that are also
> used for the error message. Do simple check first,
> then recompute to get better error messages
> (fx `allDistinct`).
>
> Renamed some rarely used members for consistency
> (`stringContainsInOrder`->`containsInOrder`,
> where other string-contains functions just start
> with `contains`, and `containsOneOf` -> `containsAny`
> to match `Iterable.any` phrasing, and also it accepts
> if containing at least one, not precisely one.)
>
> Removed a function that wasn't used anywhere.
>
> Moved `assertStatementsEnabled` to `variations.dart` as `asserts`.
> Removed `typeAssertionsEnabled` and `checkedModeEnabled`. The former used in one place, where it was replaced with `checkedImplicitDowncasts` from `variations.dart`, the latter wasn't used anywhere.
>
> Deprecates `package:expect/minitest.dart`. It was never intended
> to be used for new tests, only as a help to convert existing tests
> written against `package:unit_test`.
> All existing imports marked as `// ignore: deprecated_member_use`.
>
> Change-Id: I07e21d4c0f3ccf11b82ee34af2668fdbb22264d2
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/352360
> Reviewed-by: Slava Egorov <vegorov@google.com>
> Reviewed-by: Ömer Ağacan <omersa@google.com>
> Reviewed-by: Nate Bosch <nbosch@google.com>
> Reviewed-by: Stephen Adams <sra@google.com>
> Commit-Queue: Lasse Nielsen <lrn@google.com>
Change-Id: I360b4347470a0bb2b63c3108e2b83ee2a771bf3f
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/362020
Reviewed-by: Nate Bosch <nbosch@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
Reviewed-by: Stephen Adams <sra@google.com>
Reviewed-by: Leaf Petersen <leafp@google.com>
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Commit-Queue: William Hesse <whesse@google.com>
366 lines
11 KiB
Dart
366 lines
11 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:js/js.dart';
|
|
import 'package:js/js_util.dart' as js_util;
|
|
|
|
import 'package:expect/minitest.dart';
|
|
|
|
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 {
|
|
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);
|
|
});
|
|
}
|