adb08cfc80
BUG= R=herhut@google.com Review URL: https://codereview.chromium.org//886773004 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@43275 260f80e4-7a28-3924-810f-c04153c831b5
1028 lines
33 KiB
Dart
1028 lines
33 KiB
Dart
// Copyright (c) 2012, 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 'dart:async';
|
|
import "package:expect/expect.dart";
|
|
import "package:async_helper/async_helper.dart";
|
|
import "package:compiler/src/dart2jslib.dart";
|
|
import "package:compiler/src/elements/elements.dart";
|
|
import "package:compiler/src/tree/tree.dart";
|
|
import "mock_compiler.dart";
|
|
import "mock_libraries.dart";
|
|
import 'package:compiler/src/elements/modelx.dart';
|
|
|
|
Future<Compiler> applyPatch(String script, String patch,
|
|
{bool analyzeAll: false,
|
|
bool analyzeOnly: false,
|
|
bool runCompiler: false,
|
|
String main: "",
|
|
String patchVersion}) {
|
|
Map<String, String> core = <String, String>{'script': script};
|
|
MockCompiler compiler = new MockCompiler.internal(coreSource: core,
|
|
analyzeAll: analyzeAll,
|
|
analyzeOnly: analyzeOnly,
|
|
patchVersion: patchVersion);
|
|
compiler.diagnosticHandler = createHandler(compiler, '');
|
|
var uri = Uri.parse("patch:core");
|
|
compiler.registerSource(uri, "$DEFAULT_PATCH_CORE_SOURCE\n$patch");
|
|
var future;
|
|
if (runCompiler) {
|
|
future = compiler.runCompiler(null, main);
|
|
} else {
|
|
future = compiler.init(main);
|
|
}
|
|
return future.then((_) => compiler);
|
|
}
|
|
|
|
void expectHasBody(compiler, ElementX element) {
|
|
var node = element.parseNode(compiler);
|
|
Expect.isNotNull(node, "Element isn't parseable, when a body was expected");
|
|
Expect.isNotNull(node.body);
|
|
// If the element has a body it is either a Block or a Return statement,
|
|
// both with different begin and end tokens.
|
|
Expect.isTrue(node.body is Block || node.body is Return);
|
|
Expect.notEquals(node.body.getBeginToken(), node.body.getEndToken());
|
|
}
|
|
|
|
void expectHasNoBody(compiler, ElementX element) {
|
|
var node = element.parseNode(compiler);
|
|
Expect.isNotNull(node, "Element isn't parseable, when a body was expected");
|
|
Expect.isFalse(node.hasBody());
|
|
}
|
|
|
|
Element ensure(compiler,
|
|
String name,
|
|
Element lookup(name),
|
|
{bool expectIsPatched: false,
|
|
bool expectIsPatch: false,
|
|
bool checkHasBody: false,
|
|
bool expectIsGetter: false,
|
|
bool expectIsFound: true,
|
|
bool expectIsRegular: false}) {
|
|
var element = lookup(name);
|
|
if (!expectIsFound) {
|
|
Expect.isNull(element);
|
|
return element;
|
|
}
|
|
Expect.isNotNull(element);
|
|
if (expectIsGetter) {
|
|
Expect.isTrue(element is AbstractFieldElement);
|
|
Expect.isNotNull(element.getter);
|
|
element = element.getter;
|
|
}
|
|
Expect.equals(expectIsPatched, element.isPatched,
|
|
'Unexpected: $element.isPatched = ${element.isPatched}');
|
|
if (expectIsPatched) {
|
|
Expect.isNull(element.origin);
|
|
Expect.isNotNull(element.patch);
|
|
|
|
Expect.equals(element, element.declaration);
|
|
Expect.equals(element.patch, element.implementation);
|
|
|
|
if (checkHasBody) {
|
|
expectHasNoBody(compiler, element);
|
|
expectHasBody(compiler, element.patch);
|
|
}
|
|
} else {
|
|
Expect.isTrue(element.isImplementation);
|
|
}
|
|
Expect.equals(expectIsPatch, element.isPatch);
|
|
if (expectIsPatch) {
|
|
Expect.isNotNull(element.origin);
|
|
Expect.isNull(element.patch);
|
|
|
|
Expect.equals(element.origin, element.declaration);
|
|
Expect.equals(element, element.implementation);
|
|
|
|
if (checkHasBody) {
|
|
expectHasBody(compiler, element);
|
|
expectHasNoBody(compiler, element.origin);
|
|
}
|
|
} else {
|
|
Expect.isTrue(element.isDeclaration);
|
|
}
|
|
if (expectIsRegular) {
|
|
Expect.isNull(element.origin);
|
|
Expect.isNull(element.patch);
|
|
|
|
Expect.equals(element, element.declaration);
|
|
Expect.equals(element, element.implementation);
|
|
|
|
if (checkHasBody) {
|
|
expectHasBody(compiler, element);
|
|
}
|
|
}
|
|
Expect.isFalse(element.isPatched && element.isPatch);
|
|
return element;
|
|
}
|
|
|
|
testPatchFunction() {
|
|
asyncTest(() => applyPatch(
|
|
"external test();",
|
|
"@patch test() { return 'string'; } ").then((compiler) {
|
|
ensure(compiler, "test", compiler.coreLibrary.find,
|
|
expectIsPatched: true, checkHasBody: true);
|
|
ensure(compiler, "test", compiler.coreLibrary.patch.find,
|
|
expectIsPatch: true, checkHasBody: true);
|
|
|
|
Expect.isTrue(compiler.warnings.isEmpty,
|
|
"Unexpected warnings: ${compiler.warnings}");
|
|
Expect.isTrue(compiler.errors.isEmpty,
|
|
"Unexpected errors: ${compiler.errors}");
|
|
}));
|
|
}
|
|
|
|
|
|
testPatchVersioned() {
|
|
String oldPatch = "test(){return 'string';}";
|
|
String newPatch = "test(){return 'new and improved string';}";
|
|
|
|
String patchSource =
|
|
"""
|
|
@patch_old $oldPatch
|
|
@patch_new $newPatch
|
|
""";
|
|
|
|
test(String patchVersion,
|
|
{String patchText,
|
|
bool expectIsPatched: true,
|
|
String expectedError,
|
|
String defaultPatch: '',
|
|
String expectedInternalError}) {
|
|
asyncTest(() => applyPatch(
|
|
"external test();",
|
|
"""
|
|
$defaultPatch
|
|
$patchSource
|
|
""",
|
|
patchVersion: patchVersion).then((compiler) {
|
|
Element origin =
|
|
ensure(compiler, "test", compiler.coreLibrary.find,
|
|
expectIsPatched: expectIsPatched, checkHasBody: true);
|
|
if (expectIsPatched) {
|
|
AstElement patch =
|
|
ensure(compiler, "test", compiler.coreLibrary.patch.find,
|
|
expectIsPatch: true, checkHasBody: true);
|
|
Expect.equals(origin.patch, patch);
|
|
Expect.equals(patch.origin, origin);
|
|
Expect.equals(patchText, patch.node.toString());
|
|
}
|
|
|
|
compiler.analyzeElement(origin);
|
|
compiler.enqueuer.resolution.emptyDeferredTaskQueue();
|
|
|
|
Expect.isTrue(compiler.warnings.isEmpty,
|
|
"Unexpected warnings: ${compiler.warnings}");
|
|
if (expectedError != null) {
|
|
Expect.equals(expectedError,
|
|
compiler.errors[0].message.toString());
|
|
} else {
|
|
Expect.isTrue(compiler.errors.isEmpty,
|
|
"Unexpected errors: ${compiler.errors}");
|
|
}
|
|
}).catchError((error) {
|
|
if (expectedInternalError != null) {
|
|
Expect.equals(
|
|
'Internal Error: $expectedInternalError', error.toString());
|
|
} else {
|
|
throw error;
|
|
}
|
|
}));
|
|
}
|
|
|
|
test('old', patchText: oldPatch);
|
|
test('new', patchText: newPatch);
|
|
test('unknown', expectIsPatched: false,
|
|
expectedError: 'External method without an implementation.');
|
|
test('old',
|
|
defaultPatch: "@patch test(){}",
|
|
expectedInternalError: "Trying to patch a function more than once.");
|
|
}
|
|
|
|
testPatchConstructor() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
class Class {
|
|
external Class();
|
|
}
|
|
""",
|
|
"""
|
|
@patch class Class {
|
|
@patch Class();
|
|
}
|
|
""").then((compiler) {
|
|
var classOrigin = ensure(compiler, "Class", compiler.coreLibrary.find,
|
|
expectIsPatched: true);
|
|
classOrigin.ensureResolved(compiler);
|
|
var classPatch = ensure(compiler, "Class", compiler.coreLibrary.patch.find,
|
|
expectIsPatch: true);
|
|
|
|
Expect.equals(classPatch, classOrigin.patch);
|
|
Expect.equals(classOrigin, classPatch.origin);
|
|
|
|
var constructorOrigin = ensure(compiler, "",
|
|
(name) => classOrigin.localLookup(name),
|
|
expectIsPatched: true);
|
|
var constructorPatch = ensure(compiler, "",
|
|
(name) => classPatch.localLookup(name),
|
|
expectIsPatch: true);
|
|
|
|
Expect.equals(constructorPatch, constructorOrigin.patch);
|
|
Expect.equals(constructorOrigin, constructorPatch.origin);
|
|
|
|
Expect.isTrue(compiler.warnings.isEmpty,
|
|
"Unexpected warnings: ${compiler.warnings}");
|
|
Expect.isTrue(compiler.errors.isEmpty,
|
|
"Unexpected errors: ${compiler.errors}");
|
|
}));
|
|
}
|
|
|
|
testPatchRedirectingConstructor() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
class Class {
|
|
Class(x) : this._(x, false);
|
|
|
|
external Class._(x, y);
|
|
}
|
|
""",
|
|
r"""
|
|
@patch class Class {
|
|
@patch Class._(x, y) { print('$x,$y'); }
|
|
}
|
|
""").then((compiler) {
|
|
var classOrigin = ensure(compiler, "Class", compiler.coreLibrary.find,
|
|
expectIsPatched: true);
|
|
classOrigin.ensureResolved(compiler);
|
|
|
|
var classPatch = ensure(compiler, "Class", compiler.coreLibrary.patch.find,
|
|
expectIsPatch: true);
|
|
|
|
Expect.equals(classOrigin, classPatch.origin);
|
|
Expect.equals(classPatch, classOrigin.patch);
|
|
|
|
var constructorRedirecting =
|
|
ensure(compiler, "",
|
|
(name) => classOrigin.localLookup(name));
|
|
var constructorOrigin =
|
|
ensure(compiler, "_",
|
|
(name) => classOrigin.localLookup(name),
|
|
expectIsPatched: true);
|
|
var constructorPatch =
|
|
ensure(compiler, "_",
|
|
(name) => classPatch.localLookup(name),
|
|
expectIsPatch: true);
|
|
Expect.equals(constructorOrigin, constructorPatch.origin);
|
|
Expect.equals(constructorPatch, constructorOrigin.patch);
|
|
|
|
compiler.resolver.resolve(constructorRedirecting);
|
|
|
|
Expect.isTrue(compiler.warnings.isEmpty,
|
|
"Unexpected warnings: ${compiler.warnings}");
|
|
Expect.isTrue(compiler.errors.isEmpty,
|
|
"Unexpected errors: ${compiler.errors}");
|
|
}));
|
|
}
|
|
|
|
testPatchMember() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
class Class {
|
|
external String toString();
|
|
}
|
|
""",
|
|
"""
|
|
@patch class Class {
|
|
@patch String toString() => 'string';
|
|
}
|
|
""").then((compiler) {
|
|
var container = ensure(compiler, "Class", compiler.coreLibrary.find,
|
|
expectIsPatched: true);
|
|
container.parseNode(compiler);
|
|
ensure(compiler, "Class", compiler.coreLibrary.patch.find,
|
|
expectIsPatch: true);
|
|
|
|
ensure(compiler, "toString", container.lookupLocalMember,
|
|
expectIsPatched: true, checkHasBody: true);
|
|
ensure(compiler, "toString", container.patch.lookupLocalMember,
|
|
expectIsPatch: true, checkHasBody: true);
|
|
|
|
Expect.isTrue(compiler.warnings.isEmpty,
|
|
"Unexpected warnings: ${compiler.warnings}");
|
|
Expect.isTrue(compiler.errors.isEmpty,
|
|
"Unexpected errors: ${compiler.errors}");
|
|
}));
|
|
}
|
|
|
|
testPatchGetter() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
class Class {
|
|
external int get field;
|
|
}
|
|
""",
|
|
"""
|
|
@patch class Class {
|
|
@patch int get field => 5;
|
|
}
|
|
""").then((compiler) {
|
|
var container = ensure(compiler, "Class", compiler.coreLibrary.find,
|
|
expectIsPatched: true);
|
|
container.parseNode(compiler);
|
|
ensure(compiler,
|
|
"field",
|
|
container.lookupLocalMember,
|
|
expectIsGetter: true,
|
|
expectIsPatched: true,
|
|
checkHasBody: true);
|
|
ensure(compiler,
|
|
"field",
|
|
container.patch.lookupLocalMember,
|
|
expectIsGetter: true,
|
|
expectIsPatch: true,
|
|
checkHasBody: true);
|
|
|
|
Expect.isTrue(compiler.warnings.isEmpty,
|
|
"Unexpected warnings: ${compiler.warnings}");
|
|
Expect.isTrue(compiler.errors.isEmpty,
|
|
"Unexpected errors: ${compiler.errors}");
|
|
}));
|
|
}
|
|
|
|
testRegularMember() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
class Class {
|
|
void regular() {}
|
|
}
|
|
""",
|
|
"""
|
|
@patch class Class {
|
|
}
|
|
""").then((compiler) {
|
|
var container = ensure(compiler, "Class", compiler.coreLibrary.find,
|
|
expectIsPatched: true);
|
|
container.parseNode(compiler);
|
|
ensure(compiler, "Class", compiler.coreLibrary.patch.find,
|
|
expectIsPatch: true);
|
|
|
|
ensure(compiler, "regular", container.lookupLocalMember,
|
|
checkHasBody: true, expectIsRegular: true);
|
|
ensure(compiler, "regular", container.patch.lookupLocalMember,
|
|
checkHasBody: true, expectIsRegular: true);
|
|
|
|
Expect.isTrue(compiler.warnings.isEmpty,
|
|
"Unexpected warnings: ${compiler.warnings}");
|
|
Expect.isTrue(compiler.errors.isEmpty,
|
|
"Unexpected errors: ${compiler.errors}");
|
|
}));
|
|
}
|
|
|
|
testGhostMember() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
class Class {
|
|
}
|
|
""",
|
|
"""
|
|
@patch class Class {
|
|
void ghost() {}
|
|
}
|
|
""").then((compiler) {
|
|
var container = ensure(compiler, "Class", compiler.coreLibrary.find,
|
|
expectIsPatched: true);
|
|
container.parseNode(compiler);
|
|
ensure(compiler, "Class", compiler.coreLibrary.patch.find,
|
|
expectIsPatch: true);
|
|
|
|
ensure(compiler, "ghost", container.lookupLocalMember,
|
|
expectIsFound: false);
|
|
ensure(compiler, "ghost", container.patch.lookupLocalMember,
|
|
checkHasBody: true, expectIsRegular: true);
|
|
|
|
Expect.isTrue(compiler.warnings.isEmpty,
|
|
"Unexpected warnings: ${compiler.warnings}");
|
|
Expect.isTrue(compiler.errors.isEmpty,
|
|
"Unexpected errors: ${compiler.errors}");
|
|
}));
|
|
}
|
|
|
|
testInjectFunction() {
|
|
asyncTest(() => applyPatch(
|
|
"",
|
|
"int _function() => 5;").then((compiler) {
|
|
ensure(compiler,
|
|
"_function",
|
|
compiler.coreLibrary.find,
|
|
expectIsFound: false);
|
|
ensure(compiler,
|
|
"_function",
|
|
compiler.coreLibrary.patch.find,
|
|
checkHasBody: true, expectIsRegular: true);
|
|
|
|
Expect.isTrue(compiler.warnings.isEmpty,
|
|
"Unexpected warnings: ${compiler.warnings}");
|
|
Expect.isTrue(compiler.errors.isEmpty,
|
|
"Unexpected errors: ${compiler.errors}");
|
|
}));
|
|
}
|
|
|
|
testPatchSignatureCheck() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
class Class {
|
|
external String method1();
|
|
external void method2(String str);
|
|
external void method3(String s1);
|
|
external void method4([String str]);
|
|
external void method5({String str});
|
|
external void method6({String str});
|
|
external void method7([String s1]);
|
|
external void method8({String s1});
|
|
external void method9(String str);
|
|
external void method10([String str]);
|
|
external void method11({String str});
|
|
}
|
|
""",
|
|
"""
|
|
@patch class Class {
|
|
@patch int method1() => 0;
|
|
@patch void method2() {}
|
|
@patch void method3(String s2) {}
|
|
@patch void method4([String str, int i]) {}
|
|
@patch void method5() {}
|
|
@patch void method6([String str]) {}
|
|
@patch void method7([String s2]) {}
|
|
@patch void method8({String s2}) {}
|
|
@patch void method9(int str) {}
|
|
@patch void method10([int str]) {}
|
|
@patch void method11({int str}) {}
|
|
}
|
|
""").then((compiler) {
|
|
var container = ensure(compiler, "Class", compiler.coreLibrary.find,
|
|
expectIsPatched: true);
|
|
container.ensureResolved(compiler);
|
|
container.parseNode(compiler);
|
|
|
|
void expect(String methodName, List infos, List errors) {
|
|
compiler.clearMessages();
|
|
compiler.resolver.resolveMethodElement(
|
|
ensure(compiler, methodName, container.lookupLocalMember,
|
|
expectIsPatched: true, checkHasBody: true));
|
|
Expect.equals(0, compiler.warnings.length);
|
|
Expect.equals(infos.length, compiler.infos.length,
|
|
"Unexpected infos: ${compiler.infos} on $methodName");
|
|
for (int i = 0 ; i < infos.length ; i++) {
|
|
Expect.equals(infos[i], compiler.infos[i].message.kind);
|
|
}
|
|
Expect.equals(errors.length, compiler.errors.length,
|
|
"Unexpected errors: ${compiler.errors} on $methodName");
|
|
for (int i = 0 ; i < errors.length ; i++) {
|
|
Expect.equals(errors[i], compiler.errors[i].message.kind);
|
|
}
|
|
}
|
|
|
|
expect("method1", [], [MessageKind.PATCH_RETURN_TYPE_MISMATCH]);
|
|
expect("method2", [],
|
|
[MessageKind.PATCH_REQUIRED_PARAMETER_COUNT_MISMATCH]);
|
|
expect("method3", [MessageKind.PATCH_POINT_TO_PARAMETER],
|
|
[MessageKind.PATCH_PARAMETER_MISMATCH]);
|
|
expect("method4", [],
|
|
[MessageKind.PATCH_OPTIONAL_PARAMETER_COUNT_MISMATCH]);
|
|
expect("method5", [],
|
|
[MessageKind.PATCH_OPTIONAL_PARAMETER_COUNT_MISMATCH]);
|
|
expect("method6", [],
|
|
[MessageKind.PATCH_OPTIONAL_PARAMETER_NAMED_MISMATCH]);
|
|
expect("method7", [MessageKind.PATCH_POINT_TO_PARAMETER],
|
|
[MessageKind.PATCH_PARAMETER_MISMATCH]);
|
|
expect("method8", [MessageKind.PATCH_POINT_TO_PARAMETER],
|
|
[MessageKind.PATCH_PARAMETER_MISMATCH]);
|
|
expect("method9", [MessageKind.PATCH_POINT_TO_PARAMETER],
|
|
[MessageKind.PATCH_PARAMETER_TYPE_MISMATCH]);
|
|
expect("method10", [MessageKind.PATCH_POINT_TO_PARAMETER],
|
|
[MessageKind.PATCH_PARAMETER_TYPE_MISMATCH]);
|
|
expect("method11", [MessageKind.PATCH_POINT_TO_PARAMETER],
|
|
[MessageKind.PATCH_PARAMETER_TYPE_MISMATCH]);
|
|
}));
|
|
}
|
|
|
|
testExternalWithoutImplementationTopLevel() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
external void foo();
|
|
""",
|
|
"""
|
|
// @patch void foo() {}
|
|
""").then((compiler) {
|
|
var function = ensure(compiler, "foo", compiler.coreLibrary.find);
|
|
compiler.resolver.resolve(function);
|
|
Expect.isTrue(compiler.warnings.isEmpty,
|
|
"Unexpected warnings: ${compiler.warnings}");
|
|
print('testExternalWithoutImplementationTopLevel:${compiler.errors}');
|
|
Expect.equals(1, compiler.errors.length);
|
|
Expect.isTrue(
|
|
compiler.errors[0].message.kind ==
|
|
MessageKind.PATCH_EXTERNAL_WITHOUT_IMPLEMENTATION);
|
|
Expect.stringEquals('External method without an implementation.',
|
|
compiler.errors[0].message.toString());
|
|
}));
|
|
}
|
|
|
|
testExternalWithoutImplementationMember() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
class Class {
|
|
external void foo();
|
|
}
|
|
""",
|
|
"""
|
|
@patch class Class {
|
|
// @patch void foo() {}
|
|
}
|
|
""").then((compiler) {
|
|
var container = ensure(compiler, "Class", compiler.coreLibrary.find,
|
|
expectIsPatched: true);
|
|
container.parseNode(compiler);
|
|
|
|
compiler.warnings.clear();
|
|
compiler.errors.clear();
|
|
compiler.resolver.resolveMethodElement(
|
|
ensure(compiler, "foo", container.lookupLocalMember));
|
|
Expect.isTrue(compiler.warnings.isEmpty,
|
|
"Unexpected warnings: ${compiler.warnings}");
|
|
print('testExternalWithoutImplementationMember:${compiler.errors}');
|
|
Expect.equals(1, compiler.errors.length);
|
|
Expect.isTrue(
|
|
compiler.errors[0].message.kind ==
|
|
MessageKind.PATCH_EXTERNAL_WITHOUT_IMPLEMENTATION);
|
|
Expect.stringEquals('External method without an implementation.',
|
|
compiler.errors[0].message.toString());
|
|
}));
|
|
}
|
|
|
|
testIsSubclass() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
class A {}
|
|
""",
|
|
"""
|
|
@patch class A {}
|
|
""").then((compiler) {
|
|
ClassElement cls = ensure(compiler, "A", compiler.coreLibrary.find,
|
|
expectIsPatched: true);
|
|
ClassElement patch = cls.patch;
|
|
Expect.isTrue(cls != patch);
|
|
Expect.isTrue(cls.isSubclassOf(patch));
|
|
Expect.isTrue(patch.isSubclassOf(cls));
|
|
}));
|
|
}
|
|
|
|
testPatchNonExistingTopLevel() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
// class Class {}
|
|
""",
|
|
"""
|
|
@patch class Class {}
|
|
""").then((compiler) {
|
|
Expect.isTrue(compiler.warnings.isEmpty,
|
|
"Unexpected warnings: ${compiler.warnings}");
|
|
print('testPatchNonExistingTopLevel:${compiler.errors}');
|
|
Expect.equals(1, compiler.errors.length);
|
|
Expect.isTrue(
|
|
compiler.errors[0].message.kind == MessageKind.PATCH_NON_EXISTING);
|
|
}));
|
|
}
|
|
|
|
testPatchNonExistingMember() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
class Class {}
|
|
""",
|
|
"""
|
|
@patch class Class {
|
|
@patch void foo() {}
|
|
}
|
|
""").then((compiler) {
|
|
var container = ensure(compiler, "Class", compiler.coreLibrary.find,
|
|
expectIsPatched: true);
|
|
container.parseNode(compiler);
|
|
|
|
Expect.isTrue(compiler.warnings.isEmpty,
|
|
"Unexpected warnings: ${compiler.warnings}");
|
|
print('testPatchNonExistingMember:${compiler.errors}');
|
|
Expect.equals(1, compiler.errors.length);
|
|
Expect.isTrue(
|
|
compiler.errors[0].message.kind == MessageKind.PATCH_NON_EXISTING);
|
|
}));
|
|
}
|
|
|
|
testPatchNonPatchablePatch() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
external get foo;
|
|
""",
|
|
"""
|
|
@patch var foo;
|
|
""").then((compiler) {
|
|
ensure(compiler, "foo", compiler.coreLibrary.find);
|
|
|
|
Expect.isTrue(compiler.warnings.isEmpty,
|
|
"Unexpected warnings: ${compiler.warnings}");
|
|
print('testPatchNonPatchablePatch:${compiler.errors}');
|
|
Expect.equals(1, compiler.errors.length);
|
|
Expect.isTrue(
|
|
compiler.errors[0].message.kind == MessageKind.PATCH_NONPATCHABLE);
|
|
}));
|
|
}
|
|
|
|
testPatchNonPatchableOrigin() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
external var foo;
|
|
""",
|
|
"""
|
|
@patch get foo => 0;
|
|
""").then((compiler) {
|
|
ensure(compiler, "foo", compiler.coreLibrary.find);
|
|
|
|
Expect.isTrue(compiler.warnings.isEmpty,
|
|
"Unexpected warnings: ${compiler.warnings}");
|
|
print('testPatchNonPatchableOrigin:${compiler.errors}');
|
|
Expect.equals(2, compiler.errors.length);
|
|
Expect.equals(
|
|
MessageKind.EXTRANEOUS_MODIFIER, compiler.errors[0].message.kind);
|
|
Expect.equals(
|
|
// TODO(ahe): Eventually, this error should be removed as it will be
|
|
// handled by the regular parser.
|
|
MessageKind.PATCH_NONPATCHABLE, compiler.errors[1].message.kind);
|
|
}));
|
|
}
|
|
|
|
testPatchNonExternalTopLevel() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
void foo() {}
|
|
""",
|
|
"""
|
|
@patch void foo() {}
|
|
""").then((compiler) {
|
|
print('testPatchNonExternalTopLevel.errors:${compiler.errors}');
|
|
print('testPatchNonExternalTopLevel.warnings:${compiler.warnings}');
|
|
Expect.equals(1, compiler.errors.length);
|
|
Expect.isTrue(
|
|
compiler.errors[0].message.kind == MessageKind.PATCH_NON_EXTERNAL);
|
|
Expect.equals(0, compiler.warnings.length);
|
|
Expect.equals(1, compiler.infos.length);
|
|
Expect.isTrue(compiler.infos[0].message.kind ==
|
|
MessageKind.PATCH_POINT_TO_FUNCTION);
|
|
}));
|
|
}
|
|
|
|
testPatchNonExternalMember() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
class Class {
|
|
void foo() {}
|
|
}
|
|
""",
|
|
"""
|
|
@patch class Class {
|
|
@patch void foo() {}
|
|
}
|
|
""").then((compiler) {
|
|
var container = ensure(compiler, "Class", compiler.coreLibrary.find,
|
|
expectIsPatched: true);
|
|
container.parseNode(compiler);
|
|
|
|
print('testPatchNonExternalMember.errors:${compiler.errors}');
|
|
print('testPatchNonExternalMember.warnings:${compiler.warnings}');
|
|
Expect.equals(1, compiler.errors.length);
|
|
Expect.isTrue(
|
|
compiler.errors[0].message.kind == MessageKind.PATCH_NON_EXTERNAL);
|
|
Expect.equals(0, compiler.warnings.length);
|
|
Expect.equals(1, compiler.infos.length);
|
|
Expect.isTrue(compiler.infos[0].message.kind ==
|
|
MessageKind.PATCH_POINT_TO_FUNCTION);
|
|
}));
|
|
}
|
|
|
|
testPatchNonClass() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
external void Class() {}
|
|
""",
|
|
"""
|
|
@patch class Class {}
|
|
""").then((compiler) {
|
|
print('testPatchNonClass.errors:${compiler.errors}');
|
|
print('testPatchNonClass.warnings:${compiler.warnings}');
|
|
Expect.equals(1, compiler.errors.length);
|
|
Expect.isTrue(
|
|
compiler.errors[0].message.kind == MessageKind.PATCH_NON_CLASS);
|
|
Expect.equals(0, compiler.warnings.length);
|
|
Expect.equals(1, compiler.infos.length);
|
|
Expect.isTrue(
|
|
compiler.infos[0].message.kind == MessageKind.PATCH_POINT_TO_CLASS);
|
|
}));
|
|
}
|
|
|
|
testPatchNonGetter() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
external void foo() {}
|
|
""",
|
|
"""
|
|
@patch get foo => 0;
|
|
""").then((compiler) {
|
|
print('testPatchNonClass.errors:${compiler.errors}');
|
|
print('testPatchNonClass.warnings:${compiler.warnings}');
|
|
Expect.equals(1, compiler.errors.length);
|
|
Expect.isTrue(
|
|
compiler.errors[0].message.kind == MessageKind.PATCH_NON_GETTER);
|
|
Expect.equals(0, compiler.warnings.length);
|
|
Expect.equals(1, compiler.infos.length);
|
|
Expect.isTrue(
|
|
compiler.infos[0].message.kind == MessageKind.PATCH_POINT_TO_GETTER);
|
|
}));
|
|
}
|
|
|
|
testPatchNoGetter() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
external set foo(var value) {}
|
|
""",
|
|
"""
|
|
@patch get foo => 0;
|
|
""").then((compiler) {
|
|
print('testPatchNonClass.errors:${compiler.errors}');
|
|
print('testPatchNonClass.warnings:${compiler.warnings}');
|
|
Expect.equals(1, compiler.errors.length);
|
|
Expect.isTrue(
|
|
compiler.errors[0].message.kind == MessageKind.PATCH_NO_GETTER);
|
|
Expect.equals(0, compiler.warnings.length);
|
|
Expect.equals(1, compiler.infos.length);
|
|
Expect.isTrue(
|
|
compiler.infos[0].message.kind == MessageKind.PATCH_POINT_TO_GETTER);
|
|
}));
|
|
}
|
|
|
|
testPatchNonSetter() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
external void foo() {}
|
|
""",
|
|
"""
|
|
@patch set foo(var value) {}
|
|
""").then((compiler) {
|
|
print('testPatchNonClass.errors:${compiler.errors}');
|
|
print('testPatchNonClass.warnings:${compiler.warnings}');
|
|
Expect.equals(1, compiler.errors.length);
|
|
Expect.isTrue(
|
|
compiler.errors[0].message.kind == MessageKind.PATCH_NON_SETTER);
|
|
Expect.equals(0, compiler.warnings.length);
|
|
Expect.equals(1, compiler.infos.length);
|
|
Expect.isTrue(
|
|
compiler.infos[0].message.kind == MessageKind.PATCH_POINT_TO_SETTER);
|
|
}));
|
|
}
|
|
|
|
testPatchNoSetter() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
external get foo;
|
|
""",
|
|
"""
|
|
@patch set foo(var value) {}
|
|
""").then((compiler) {
|
|
print('testPatchNonClass.errors:${compiler.errors}');
|
|
print('testPatchNonClass.warnings:${compiler.warnings}');
|
|
Expect.equals(1, compiler.errors.length);
|
|
Expect.isTrue(
|
|
compiler.errors[0].message.kind == MessageKind.PATCH_NO_SETTER);
|
|
Expect.equals(0, compiler.warnings.length);
|
|
Expect.equals(1, compiler.infos.length);
|
|
Expect.isTrue(
|
|
compiler.infos[0].message.kind == MessageKind.PATCH_POINT_TO_SETTER);
|
|
}));
|
|
}
|
|
|
|
testPatchNonFunction() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
external get foo;
|
|
""",
|
|
"""
|
|
@patch void foo() {}
|
|
""").then((compiler) {
|
|
print('testPatchNonClass.errors:${compiler.errors}');
|
|
print('testPatchNonClass.warnings:${compiler.warnings}');
|
|
Expect.equals(1, compiler.errors.length);
|
|
Expect.isTrue(
|
|
compiler.errors[0].message.kind == MessageKind.PATCH_NON_FUNCTION);
|
|
Expect.equals(0, compiler.warnings.length);
|
|
Expect.equals(1, compiler.infos.length);
|
|
Expect.isTrue(
|
|
compiler.infos[0].message.kind ==
|
|
MessageKind.PATCH_POINT_TO_FUNCTION);
|
|
}));
|
|
}
|
|
|
|
testPatchAndSelector() {
|
|
asyncTest(() => applyPatch(
|
|
"""
|
|
class A {
|
|
external void clear();
|
|
}
|
|
class B extends A {
|
|
}
|
|
""",
|
|
"""
|
|
@patch class A {
|
|
int method() => 0;
|
|
@patch void clear() {}
|
|
}
|
|
""",
|
|
main: """
|
|
main () {
|
|
new A(); // ensure A and B are instantiated
|
|
new B();
|
|
}
|
|
""",
|
|
runCompiler: true, analyzeOnly: true).then((Compiler compiler) {
|
|
World world = compiler.world;
|
|
|
|
ClassElement cls = ensure(compiler, "A", compiler.coreLibrary.find,
|
|
expectIsPatched: true);
|
|
cls.ensureResolved(compiler);
|
|
|
|
ensure(compiler, "method", cls.patch.lookupLocalMember,
|
|
checkHasBody: true, expectIsRegular: true);
|
|
|
|
ensure(compiler, "clear", cls.lookupLocalMember,
|
|
checkHasBody: true, expectIsPatched: true);
|
|
|
|
compiler.phase = Compiler.PHASE_DONE_RESOLVING;
|
|
|
|
// Check that a method just in the patch class is a target for a
|
|
// typed selector.
|
|
Selector selector = new Selector.call('method', compiler.coreLibrary, 0);
|
|
TypedSelector typedSelector = new TypedSelector.exact(cls, selector, world);
|
|
FunctionElement method = cls.implementation.lookupLocalMember('method');
|
|
method.computeSignature(compiler);
|
|
Expect.isTrue(selector.applies(method, world));
|
|
Expect.isTrue(typedSelector.applies(method, world));
|
|
|
|
// Check that the declaration method in the declaration class is a target
|
|
// for a typed selector.
|
|
selector = new Selector.call('clear', compiler.coreLibrary, 0);
|
|
typedSelector = new TypedSelector.exact(cls, selector, world);
|
|
method = cls.lookupLocalMember('clear');
|
|
method.computeSignature(compiler);
|
|
Expect.isTrue(selector.applies(method, world));
|
|
Expect.isTrue(typedSelector.applies(method, world));
|
|
|
|
// Check that the declaration method in the declaration class is a target
|
|
// for a typed selector on a subclass.
|
|
cls = ensure(compiler, "B", compiler.coreLibrary.find);
|
|
cls.ensureResolved(compiler);
|
|
typedSelector = new TypedSelector.exact(cls, selector, world);
|
|
Expect.isTrue(selector.applies(method, world));
|
|
Expect.isTrue(typedSelector.applies(method, world));
|
|
}));
|
|
}
|
|
|
|
void testAnalyzeAllInjectedMembers() {
|
|
void expect(String patchText, [expectedWarnings]) {
|
|
if (expectedWarnings == null) expectedWarnings = [];
|
|
if (expectedWarnings is! List) {
|
|
expectedWarnings = <MessageKind>[expectedWarnings];
|
|
}
|
|
|
|
asyncTest(() => applyPatch('', patchText, analyzeAll: true,
|
|
analyzeOnly: true).then((compiler) {
|
|
compiler.librariesToAnalyzeWhenRun = [Uri.parse('dart:core')];
|
|
return compiler.runCompiler(null).then((_) {
|
|
compareWarningKinds(patchText, expectedWarnings, compiler.warnings);
|
|
});
|
|
}));
|
|
}
|
|
|
|
expect('String s = 0;', MessageKind.NOT_ASSIGNABLE);
|
|
expect('void method() { String s = 0; }', MessageKind.NOT_ASSIGNABLE);
|
|
expect('''
|
|
class Class {
|
|
String s = 0;
|
|
}
|
|
''',
|
|
MessageKind.NOT_ASSIGNABLE);
|
|
expect('''
|
|
class Class {
|
|
void method() {
|
|
String s = 0;
|
|
}
|
|
}
|
|
''',
|
|
MessageKind.NOT_ASSIGNABLE);
|
|
}
|
|
|
|
void testEffectiveTarget() {
|
|
String origin = """
|
|
class A {
|
|
A() : super();
|
|
factory A.forward() = B.patchTarget;
|
|
factory A.forwardTwo() = B.reflectBack;
|
|
}
|
|
class B extends A {
|
|
B() : super();
|
|
external B.patchTarget();
|
|
external factory B.reflectBack();
|
|
B.originTarget() : super();
|
|
}
|
|
""";
|
|
String patch = """
|
|
@patch class B {
|
|
B.patchTarget() : super();
|
|
factory B.reflectBack() = B.originTarget;
|
|
}
|
|
""";
|
|
|
|
asyncTest(() => applyPatch(origin, patch, analyzeAll: true,
|
|
analyzeOnly: true, runCompiler: true).then((compiler) {
|
|
ClassElement clsA = compiler.coreLibrary.find("A");
|
|
ClassElement clsB = compiler.coreLibrary.find("B");
|
|
|
|
Selector forwardCall = new Selector.callConstructor("forward",
|
|
compiler.coreLibrary);
|
|
ConstructorElement forward = clsA.lookupConstructor(forwardCall);
|
|
ConstructorElement target = forward.effectiveTarget;
|
|
Expect.isTrue(target.isPatch);
|
|
Expect.equals("patchTarget", target.name);
|
|
|
|
Selector forwardTwoCall = new Selector.callConstructor("forwardTwo",
|
|
compiler.coreLibrary);
|
|
ConstructorElement forwardTwo = clsA.lookupConstructor(forwardTwoCall);
|
|
target = forwardTwo.effectiveTarget;
|
|
Expect.isFalse(forwardTwo.isErroneous);
|
|
Expect.isFalse(target.isPatch);
|
|
Expect.equals("originTarget", target.name);
|
|
}));
|
|
}
|
|
|
|
void testTypecheckPatchedMembers() {
|
|
String originText = "external void method();";
|
|
String patchText = """
|
|
@patch void method() {
|
|
String s = 0;
|
|
}
|
|
""";
|
|
asyncTest(() => applyPatch(originText, patchText,
|
|
analyzeAll: true, analyzeOnly: true).then((compiler) {
|
|
compiler.librariesToAnalyzeWhenRun = [Uri.parse('dart:core')];
|
|
return compiler.runCompiler(null).then((_) {
|
|
compareWarningKinds(patchText,
|
|
[MessageKind.NOT_ASSIGNABLE], compiler.warnings);
|
|
});
|
|
}));
|
|
}
|
|
|
|
main() {
|
|
testPatchConstructor();
|
|
testPatchRedirectingConstructor();
|
|
testPatchFunction();
|
|
testPatchMember();
|
|
testPatchGetter();
|
|
testRegularMember();
|
|
testGhostMember();
|
|
testInjectFunction();
|
|
testPatchSignatureCheck();
|
|
|
|
testPatchVersioned();
|
|
|
|
testExternalWithoutImplementationTopLevel();
|
|
testExternalWithoutImplementationMember();
|
|
|
|
testIsSubclass();
|
|
|
|
testPatchNonExistingTopLevel();
|
|
testPatchNonExistingMember();
|
|
testPatchNonPatchablePatch();
|
|
testPatchNonPatchableOrigin();
|
|
testPatchNonExternalTopLevel();
|
|
testPatchNonExternalMember();
|
|
testPatchNonClass();
|
|
testPatchNonGetter();
|
|
testPatchNoGetter();
|
|
testPatchNonSetter();
|
|
testPatchNoSetter();
|
|
testPatchNonFunction();
|
|
|
|
testPatchAndSelector();
|
|
|
|
testEffectiveTarget(); /// bug: ok
|
|
|
|
testAnalyzeAllInjectedMembers();
|
|
testTypecheckPatchedMembers();
|
|
}
|