blob: 5520f5bb7c9f2f0c92e2a687b29449967422fa09 [file] [log] [blame]
// Copyright (c) 2014, 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.
library trydart.incremental_compilation_update_test;
import 'dart:html' hide
Element;
import 'dart:async' show
Future;
import 'package:async_helper/async_helper.dart' show
asyncTest;
import 'package:expect/expect.dart' show
Expect;
import 'package:try/src/interaction_manager.dart' show
splitLines;
import 'package:try/poi/scope_information_visitor.dart' show
ScopeInformationVisitor;
import 'sandbox.dart' show
appendIFrame,
listener;
import 'web_compiler_test_case.dart' show
WebCompilerTestCase,
WebInputProvider;
import '../poi/compiler_test_case.dart' show
CompilerTestCase;
import 'package:compiler/src/elements/elements.dart' show
Element,
LibraryElement;
import 'package:compiler/src/dart2jslib.dart' show
Compiler;
import 'program_result.dart';
const int TIMEOUT = 100;
const List<List<ProgramResult>> tests = const <List<ProgramResult>>[
// Basic hello-world test.
const <ProgramResult>[
const ProgramResult(
"main() { print('Hello, World!'); }",
const <String> ['Hello, World!']),
const ProgramResult(
"main() { print('Hello, Brave New World!'); }",
const <String> ['Hello, Brave New World!']),
],
// Test that the test framework handles more than one update.
const <ProgramResult>[
const ProgramResult(
"main() { print('Hello darkness, my old friend'); }",
const <String> ['Hello darkness, my old friend']),
const ProgramResult(
"main() { print('I\\'ve come to talk with you again'); }",
const <String> ['I\'ve come to talk with you again']),
const ProgramResult(
"main() { print('Because a vision softly creeping'); }",
const <String> ['Because a vision softly creeping']),
],
// Test that that isolate support works.
const <ProgramResult>[
const ProgramResult(
"main(arguments) { print('Hello, Isolated World!'); }",
const <String> ['Hello, Isolated World!']),
const ProgramResult(
"main(arguments) { print(arguments); }",
const <String> ['[]']),
],
// Test that a stored closure changes behavior when updated.
const <ProgramResult>[
const ProgramResult(
r"""
var closure;
foo(a, [b = 'b']) {
print('$a $b');
}
main() {
if (closure == null) {
print('[closure] is null.');
closure = foo;
}
closure('a');
closure('a', 'c');
}
""",
const <String> ['[closure] is null.', 'a b', 'a c']),
const ProgramResult(
r"""
var closure;
foo(a, [b = 'b']) {
print('$b $a');
}
main() {
if (closure == null) {
print('[closure] is null.');
closure = foo;
}
closure('a');
closure('a', 'c');
}
""",
const <String> ['b a', 'c a']),
],
// Test modifying a static method works.
const <ProgramResult>[
const ProgramResult(
"""
class C {
static m() {
print('v1');
}
}
main() {
C.m();
}
""",
const <String> ['v1']),
const ProgramResult(
"""
class C {
static m() {
print('v2');
}
}
main() {
C.m();
}
""",
const <String> ['v2']),
],
// Test modifying an instance method works.
const <ProgramResult>[
const ProgramResult(
"""
class C {
m() {
print('v1');
}
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new C();
}
instance.m();
}
""",
const <String> ['instance is null', 'v1']),
const ProgramResult(
"""
class C {
m() {
print('v2');
}
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new C();
}
instance.m();
}
""",
const <String> ['v2']),
],
// Test that a stored instance tearoff changes behavior when updated.
const <ProgramResult>[
const ProgramResult(
"""
class C {
m() {
print('v1');
}
}
var closure;
main() {
if (closure == null) {
print('closure is null');
closure = new C().m;
}
closure();
}
""",
const <String> ['closure is null', 'v1']),
const ProgramResult(
"""
class C {
m() {
print('v2');
}
}
var closure;
main() {
if (closure == null) {
print('closure is null');
closure = new C().m;
}
closure();
}
""",
const <String> ['v2']),
],
// Test that deleting an instance method works.
const <ProgramResult>[
const ProgramResult(
"""
class C {
m() {
print('v1');
}
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new C();
}
try {
instance.m();
} catch (e) {
print('threw');
}
}
""",
const <String> ['instance is null', 'v1']),
const ProgramResult(
"""
class C {
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new C();
}
try {
instance.m();
} catch (e) {
print('threw');
}
}
""",
const <String> ['threw']),
],
// Test that deleting an instance method works, even when accessed through
// super.
const <ProgramResult>[
const ProgramResult(
"""
class A {
m() {
print('v2');
}
}
class B extends A {
m() {
print('v1');
}
}
class C extends B {
m() {
super.m();
}
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new C();
}
instance.m();
}
""",
const <String> ['instance is null', 'v1']),
const ProgramResult(
"""
class A {
m() {
print('v2');
}
}
class B extends A {
}
class C extends B {
m() {
super.m();
}
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new C();
}
instance.m();
}
""",
const <String> ['v2']),
],
// Test that deleting a top-level method works.
const <ProgramResult>[
const ProgramResult(
"""
toplevel() {
print('v1');
}
class C {
m() {
try {
toplevel();
} catch (e) {
print('threw');
}
}
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new C();
}
instance.m();
}
""",
const <String> ['instance is null', 'v1']),
const ProgramResult(
"""
class C {
m() {
try {
toplevel();
} catch (e) {
print('threw');
}
}
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new C();
}
instance.m();
}
""",
const <String> ['threw']),
],
// Test that deleting a static method works.
const <ProgramResult>[
const ProgramResult(
"""
class B {
static staticMethod() {
print('v1');
}
}
class C {
m() {
try {
B.staticMethod();
} catch (e) {
print('threw');
}
try {
// Ensure that noSuchMethod support is compiled. This test is not about
// adding new classes.
B.missingMethod();
print('bad');
} catch (e) {
}
}
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new C();
}
instance.m();
}
""",
const <String> ['instance is null', 'v1']),
const ProgramResult(
"""
class B {
}
class C {
m() {
try {
B.staticMethod();
} catch (e) {
print('threw');
}
try {
// Ensure that noSuchMethod support is compiled. This test is not about
// adding new classes.
B.missingMethod();
print('bad');
} catch (e) {
}
}
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new C();
}
instance.m();
}
""",
const <String> ['threw']),
],
// Test that a newly instantiated class is handled.
const <ProgramResult>[
const ProgramResult(
"""
class A {
m() {
print('Called A.m');
}
}
class B {
m() {
print('Called B.m');
}
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new A();
}
instance.m();
}
""",
const <String>['instance is null', 'Called A.m']),
const ProgramResult(
"""
class A {
m() {
print('Called A.m');
}
}
class B {
m() {
print('Called B.m');
}
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new A();
} else {
instance = new B();
}
instance.m();
}
""",
const <String>['Called B.m']),
],
// Test that source maps don't throw exceptions.
const <ProgramResult>[
const ProgramResult(
"""
main() {
print('a');
}
""",
const <String>['a']),
const ProgramResult(
"""
main() {
print('a');
print('b');
print('c');
}
""",
const <String>['a', 'b', 'c']),
],
// Test that a newly instantiated class is handled.
const <ProgramResult>[
const ProgramResult(
r"""
class A {
get name => 'A.m';
m() {
print('Called $name');
}
}
class B extends A {
get name => 'B.m';
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new A();
}
instance.m();
}
""",
const <String>['instance is null', 'Called A.m']),
const ProgramResult(
r"""
class A {
get name => 'A.m';
m() {
print('Called $name');
}
}
class B extends A {
get name => 'B.m';
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new A();
} else {
instance = new B();
}
instance.m();
}
""",
const <String>['Called B.m']),
],
// Test that fields of a newly instantiated class are handled.
const <ProgramResult>[
const ProgramResult(
r"""
class A {
var x;
A(this.x);
}
var instance;
foo() {
if (instance != null) {
print(instance.x);
} else {
print('v1');
}
}
main() {
foo();
}
""",
const <String>['v1']),
const ProgramResult(
r"""
class A {
var x;
A(this.x);
}
var instance;
foo() {
if (instance != null) {
print(instance.x);
} else {
print('v1');
}
}
main() {
instance = new A('v2');
foo();
}
""",
const <String>['v2']),
],
// Test that top-level functions can be added.
const <ProgramResult>[
const ProgramResult(
r"""
main() {
try {
foo();
} catch(e) {
print('threw');
}
}
""",
const <String>['threw']),
const ProgramResult(
r"""
foo() {
print('v2');
}
main() {
try {
foo();
} catch(e) {
print('threw');
}
}
""",
const <String>['v2']),
],
// Test that static methods can be added.
const <ProgramResult>[
const ProgramResult(
r"""
class C {
}
main() {
try {
C.foo();
} catch(e) {
print('threw');
}
}
""",
const <String>['threw']),
const ProgramResult(
r"""
class C {
static foo() {
print('v2');
}
}
main() {
try {
C.foo();
} catch(e) {
print('threw');
}
}
""",
const <String>['v2']),
],
// Test that instance methods can be added.
const <ProgramResult>[
const ProgramResult(
r"""
class C {
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new C();
}
try {
instance.foo();
} catch(e) {
print('threw');
}
}
""",
const <String>['instance is null', 'threw']),
const ProgramResult(
r"""
class C {
foo() {
print('v2');
}
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new C();
}
try {
instance.foo();
} catch(e) {
print('threw');
}
}
""",
const <String>['v2']),
],
// Test that top-level functions can have signature changed.
const <ProgramResult>[
const ProgramResult(
r"""
foo() {
print('v1');
}
main() {
foo();
}
""",
const <String>['v1']),
const ProgramResult(
r"""
void foo() {
print('v2');
}
main() {
foo();
}
""",
const <String>['v2']),
],
// Test that static methods can have signature changed.
const <ProgramResult>[
const ProgramResult(
r"""
class C {
static foo() {
print('v1');
}
}
main() {
C.foo();
}
""",
const <String>['v1']),
const ProgramResult(
r"""
class C {
static void foo() {
print('v2');
}
}
main() {
C.foo();
}
""",
const <String>['v2']),
],
// Test that instance methods can have signature changed.
const <ProgramResult>[
const ProgramResult(
r"""
class C {
foo() {
print('v1');
}
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new C();
}
instance.foo();
}
""",
const <String>['instance is null', 'v1']),
const ProgramResult(
r"""
class C {
void foo() {
print('v2');
}
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new C();
}
instance.foo();
}
""",
const <String>['v2']),
],
// Test that adding a class is supported.
const <ProgramResult>[
const ProgramResult(
r"""
main() {
print('v1');
}
""",
const <String>['v1']),
const ProgramResult(
r"""
class C {
void foo() {
print('v2');
}
}
main() {
new C().foo();
}
""",
const <String>['v2']),
],
// Test that removing a class is supported, using constructor.
const <ProgramResult>[
const ProgramResult(
r"""
class C {
}
main() {
try {
new C();
print('v1');
} catch (e) {
print('v2');
}
}
""",
const <String>['v1']),
const ProgramResult(
r"""
main() {
try {
new C();
print('v1');
} catch (e) {
print('v2');
}
}
""",
const <String>['v2']),
],
// Test that removing a class is supported, using a static method.
const <ProgramResult>[
const ProgramResult(
r"""
class C {
static m() {
print('v1');
}
}
main() {
try {
C.m();
} catch (e) {
print('v2');
}
}
""",
const <String>['v1']),
const ProgramResult(
r"""
main() {
try {
C.m();
} catch (e) {
print('v2');
}
}
""",
const <String>['v2']),
],
// Test that changing the supertype of a class.
const <ProgramResult>[
const ProgramResult(
r"""
class A {
m() {
print('v2');
}
}
class B extends A {
m() {
print('v1');
}
}
class C extends B {
m() {
super.m();
}
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new C();
}
instance.m();
}
""",
const <String>['instance is null', 'v1']),
const ProgramResult(
r"""
class A {
m() {
print('v2');
}
}
class B extends A {
m() {
print('v1');
}
}
class C extends A {
m() {
super.m();
}
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new C();
}
instance.m();
}
""",
const <String>['v2']),
],
// Test adding a field to a class works.
const <ProgramResult>[
const ProgramResult(
r"""
class A {
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new A();
}
try {
instance.x = 'v2';
} catch(e) {
print('setter threw');
}
try {
print(instance.x);
} catch (e) {
print('getter threw');
}
}
""",
const <String>['instance is null', 'setter threw', 'getter threw']),
const ProgramResult(
r"""
class A {
var x;
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new A();
}
try {
instance.x = 'v2';
} catch(e) {
print('setter threw');
}
try {
print(instance.x);
} catch (e) {
print('getter threw');
}
}
""",
const <String>['v2']),
],
// Test removing a field from a class works.
const <ProgramResult>[
const ProgramResult(
r"""
class A {
var x;
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new A();
}
try {
instance.x = 'v1';
} catch(e) {
print('setter threw');
}
try {
print(instance.x);
} catch (e) {
print('getter threw');
}
}
""",
const <String>['instance is null', 'v1']),
const ProgramResult(
r"""
class A {
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new A();
}
try {
instance.x = 'v1';
} catch(e) {
print('setter threw');
}
try {
print(instance.x);
} catch (e) {
print('getter threw');
}
}
""",
const <String>['setter threw', 'getter threw']),
],
// Test that named arguments can be called.
const <ProgramResult>[
const ProgramResult(
r"""
class C {
foo({a, named: 'v1', x}) {
print(named);
}
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new C();
}
instance.foo();
}
""",
const <String>['instance is null', 'v1']),
const ProgramResult(
r"""
class C {
foo({a, named: 'v1', x}) {
print(named);
}
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new C();
}
instance.foo(named: 'v2');
}
""",
const <String>['v2']),
],
// Test than named arguments can be called.
const <ProgramResult>[
const ProgramResult(
r"""
class C {
foo({a, named: 'v2', x}) {
print(named);
}
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new C();
}
instance.foo(named: 'v1');
}
""",
const <String>['instance is null', 'v1']),
const ProgramResult(
r"""
class C {
foo({a, named: 'v2', x}) {
print(named);
}
}
var instance;
main() {
if (instance == null) {
print('instance is null');
instance = new C();
}
instance.foo();
}
""",
const <String>['v2']),
],
// Test that an instance tear-off with named parameters can be called.
const <ProgramResult>[
const ProgramResult(
r"""
class C {
foo({a, named: 'v1', x}) {
print(named);
}
}
var closure;
main() {
if (closure == null) {
print('closure is null');
closure = new C().foo;
}
closure();
}
""",
const <String>['closure is null', 'v1']),
const ProgramResult(
r"""
class C {
foo({a, named: 'v1', x}) {
print(named);
}
}
var closure;
main() {
if (closure == null) {
print('closure is null');
closure = new C().foo;
}
closure(named: 'v2');
}
""",
const <String>['v2']),
],
/*
// Test that a lazy static is supported.
// TODO(ahe): This test doesn't pass yet.
const <ProgramResult>[
const ProgramResult(
r"""
var normal;
foo() {
print(normal);
}
main() {
if (normal == null) {
normal = 'v1';
} else {
normal = '';
}
foo();
}
""",
const <String>['v1']),
const ProgramResult(
r"""
var normal;
var lazy = bar();
foo() {
print(lazy);
}
bar() {
print('v2');
return 'lazy';
}
main() {
if (normal == null) {
normal = 'v1';
} else {
normal = '';
}
foo();
}
""",
const <String>['v2', 'lazy']),
],
*/
// Test that superclasses of directly instantiated classes are also
// emitted.
const <ProgramResult>[
const ProgramResult(
r"""
class A {
}
class B extends A {
}
main() {
print('v1');
}
""",
const <String>['v1']),
const ProgramResult(
r"""
class A {
}
class B extends A {
}
main() {
new B();
print('v2');
}
""",
const <String>['v2']),
],
// Test that interceptor classes are handled correctly.
const <ProgramResult>[
const ProgramResult(
r"""
main() {
// TODO(ahe): Remove next line when new constants are handled correctly.
[].map(null);
print('v1');
}
""",
const <String>['v1']),
const ProgramResult(
r"""
main() {
// TODO(ahe): Use forEach(print) when closures are computed correctly.
['v2'].forEach((e) { print(e); });
}
""",
const <String>['v2']),
],
];
void main() {
listener.start();
document.head.append(lineNumberStyle());
String query = window.location.search;
int skip = 0;
if (query != null && query.length > 1) {
query = query.substring(1);
String skipParam = Uri.splitQueryString(window.location.search)['skip'];
if (skipParam != null) {
skip = int.parse(skipParam);
}
}
return asyncTest(() => Future.forEach(tests.skip(skip), compileAndRun));
}
int testCount = 1;
Future compileAndRun(List<ProgramResult> programs) {
var status = new DivElement();
document.body.append(status);
IFrameElement iframe =
appendIFrame(
'/root_dart/tests/try/web/incremental_compilation_update.html',
document.body)
..style.width = '100%'
..style.height = '600px';
return listener.expect('iframe-ready').then((_) {
ProgramResult program = programs.first;
status.append(
new HeadingElement.h2()
..appendText("Full program #${testCount++}:"));
status.append(numberedLines(program.code));
status.style.color = 'orange';
WebCompilerTestCase test = new WebCompilerTestCase(program.code);
return test.run().then((String jsCode) {
status.style.color = 'red';
var objectUrl =
Url.createObjectUrl(new Blob([jsCode], 'application/javascript'));
iframe.contentWindow.postMessage(['add-script', objectUrl], '*');
Future future =
listener.expect(program.messagesWith('iframe-dart-main-done'));
return future.then((_) {
int version = 2;
return Future.forEach(programs.skip(1), (ProgramResult program) {
status.append(new HeadingElement.h2()..appendText("Update:"));
status.append(numberedLines(program.code));
WebInputProvider inputProvider =
test.incrementalCompiler.inputProvider;
Uri uri = test.scriptUri.resolve('?v${version++}');
inputProvider.cachedSources[uri] = new Future.value(program.code);
Future future = test.incrementalCompiler.compileUpdates(
{test.scriptUri: uri}, logVerbose: logger, logTime: logger);
return future.then((String update) {
print({'update': update});
iframe.contentWindow.postMessage(['apply-update', update], '*');
return listener.expect(
program.messagesWith('iframe-dart-updated-main-done'))
.then((_) {
return new SerializeScopeTestCase(
program.code, test.incrementalCompiler.mainApp,
test.incrementalCompiler.compiler).run();
});
});
});
});
});
}).then((_) {
status.style.color = 'limegreen';
// Remove the iframe and status to work around a bug in test.dart
// (https://code.google.com/p/dart/issues/detail?id=21691).
status.remove();
iframe.remove();
});
}
class SerializeScopeTestCase extends CompilerTestCase {
final String scopeInfo;
SerializeScopeTestCase(
String source,
LibraryElement library,
Compiler compiler)
: scopeInfo = computeScopeInfo(compiler, library),
super(source, '${library.canonicalUri}');
Future run() => loadMainApp().then(checkScopes);
void checkScopes(LibraryElement library) {
Expect.stringEquals(computeScopeInfo(compiler, library), scopeInfo);
}
static String computeScopeInfo(Compiler compiler, LibraryElement library) {
ScopeInformationVisitor visitor =
new ScopeInformationVisitor(compiler, library, 0);
visitor.ignoreImports = true;
visitor.sortMembers = true;
visitor.indented.write('[\n');
visitor.indentationLevel++;
visitor.indented;
library.accept(visitor);
library.forEachLocalMember((Element member) {
if (member.isClass) {
visitor.buffer.write(',\n');
visitor.indented;
member.accept(visitor);
}
});
visitor.buffer.write('\n');
visitor.indentationLevel--;
visitor.indented.write(']');
return '${visitor.buffer}';
}
}
void logger(x) {
print(x);
bool isCheckedMode = false;
assert(isCheckedMode = true);
int timeout = isCheckedMode ? TIMEOUT * 2 : TIMEOUT;
if (listener.elapsed > timeout) {
throw 'Test timed out.';
}
}
DivElement numberedLines(String code) {
DivElement result = new DivElement();
result.classes.add("output");
for (String text in splitLines(code)) {
PreElement line = new PreElement()
..appendText(text.trimRight())
..classes.add("line");
result.append(line);
}
return result;
}
StyleElement lineNumberStyle() {
StyleElement style = new StyleElement()..appendText('''
h2 {
color: black;
}
.output {
padding: 0px;
counter-reset: line-number;
padding-bottom: 1em;
}
.line {
white-space: pre-wrap;
padding-left: 3.5em;
margin-top: 0;
margin-bottom: 0;
}
.line::before {
counter-increment: line-number;
content: counter(line-number) " ";
position: absolute;
left: 0px;
width: 3em;
text-align: right;
background-color: lightgoldenrodyellow;
}
''');
style.type = 'text/css';
return style;
}