Version 0.5.20.2
svn merge -r 24148:24149 https://dart.googlecode.com/svn/branches/bleeding_edge trunk
git-svn-id: http://dart.googlecode.com/svn/trunk@24160 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/pkg/analyzer_experimental/lib/src/services/runtime/coverage/coverage_impl.dart b/pkg/analyzer_experimental/lib/src/services/runtime/coverage/coverage_impl.dart
new file mode 100644
index 0000000..1898ad1
--- /dev/null
+++ b/pkg/analyzer_experimental/lib/src/services/runtime/coverage/coverage_impl.dart
@@ -0,0 +1,332 @@
+// Copyright (c) 2013, 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.
+
+/// A library for code coverage support for Dart.
+library runtime.coverage.impl;
+
+import 'dart:async';
+import 'dart:collection' show SplayTreeMap;
+import 'dart:io';
+import 'dart:json' as json;
+
+import 'package:pathos/path.dart' as pathos;
+
+import 'package:analyzer_experimental/src/generated/source.dart' show Source, SourceRange;
+import 'package:analyzer_experimental/src/generated/scanner.dart' show StringScanner;
+import 'package:analyzer_experimental/src/generated/parser.dart' show Parser;
+import 'package:analyzer_experimental/src/generated/ast.dart';
+import 'package:analyzer_experimental/src/generated/engine.dart' show RecordingErrorListener;
+
+import '../log.dart' as log;
+import 'models.dart';
+import 'utils.dart';
+
+/// Run the [targetPath] with code coverage rewriting.
+/// Redirects stdandard process streams.
+/// On process exit dumps coverage statistics into the [outPath].
+void runServerApplication(String targetPath, String outPath) {
+ var targetFolder = pathos.dirname(targetPath);
+ var targetName = pathos.basename(targetPath);
+ new CoverageServer(targetFolder, targetPath, outPath)
+ .start()
+ .then((port) {
+ var options = new Options();
+ var targetArgs = ['http://127.0.0.1:$port/$targetName'];
+ var dartExecutable = options.executable;
+ Process.start(dartExecutable, targetArgs).then((Process process) {
+ process.exitCode.then(exit);
+ // Redirect process streams.
+ stdin.pipe(process.stdin);
+ process.stdout.pipe(stdout);
+ process.stderr.pipe(stderr);
+ });
+ });
+}
+
+
+/// Abstract server to listen requests and serve files, may be rewriting them.
+abstract class RewriteServer {
+ final String basePath;
+ int port;
+
+ RewriteServer(this.basePath);
+
+ /// Runs the HTTP server on the ephemeral port and returns [Future] with it.
+ Future<int> start() {
+ return HttpServer.bind('127.0.0.1', 0).then((server) {
+ port = server.port;
+ log.info('RewriteServer is listening at: $port.');
+ server.listen((request) {
+ if (request.method == 'GET') {
+ handleGetRequest(request);
+ }
+ if (request.method == 'POST') {
+ handlePostRequest(request);
+ }
+ });
+ return port;
+ });
+ }
+
+ handlePostRequest(HttpRequest request);
+
+ handleGetRequest(HttpRequest request) {
+ var response = request.response;
+ // Prepare path.
+ var path = basePath + '/' + request.uri.path;
+ path = pathos.normalize(path);
+ log.info('[$path] Requested.');
+ // May be serve using just path.
+ {
+ var content = rewritePathContent(path);
+ if (content != null) {
+ log.info('[$path] Request served by path.');
+ response.write(content);
+ response.close();
+ return;
+ }
+ }
+ // Serve from file.
+ log.info('[$path] Serving file.');
+ var file = new File(path);
+ file.exists().then((found) {
+ if (found) {
+ // May be this files should be sent as is.
+ if (!shouldRewriteFile(path)) {
+ sendFile(request, file);
+ return;
+ }
+ // Rewrite content of the file.
+ file.readAsString().then((content) {
+ log.finest('[$path] Done reading ${content.length} characters.');
+ content = rewriteFileContent(path, content);
+ log.fine('[$path] Rewritten.');
+ response.write(content);
+ response.close();
+ });
+ } else {
+ log.severe('[$path] File not found.');
+ response.statusCode = HttpStatus.NOT_FOUND;
+ response.close();
+ }
+ });
+ }
+
+ void sendFile(HttpRequest request, File file) {
+ file.fullPath().then((fullPath) {
+ file.openRead()
+ .pipe(request.response)
+ .catchError((e) {});
+ });
+ }
+
+ bool shouldRewriteFile(String path);
+
+ /// Subclasses implement this method to rewrite the provided [code] of the
+ /// file with [path]. Returns some content or `null` if file content
+ /// should be requested.
+ String rewritePathContent(String path);
+
+ /// Subclasses implement this method to rewrite the provided [code] of the
+ /// file with [path].
+ String rewriteFileContent(String path, String code);
+}
+
+
+/// Server that rewrites Dart code so that it reports execution of statements
+/// and other nodes.
+class CoverageServer extends RewriteServer {
+ final appInfo = new AppInfo();
+ final String targetPath;
+ final String outPath;
+
+ CoverageServer(String basePath, this.targetPath, this.outPath)
+ : super(basePath);
+
+ void handlePostRequest(HttpRequest request) {
+ var id = 0;
+ var executedIds = new Set<int>();
+ request.listen((data) {
+ log.fine('Received statistics, ${data.length} bytes.');
+ while (true) {
+ var listIndex = id ~/ 8;
+ if (listIndex >= data.length) break;
+ var bitIndex = id % 8;
+ if ((data[listIndex] & (1 << bitIndex)) != 0) {
+ executedIds.add(id);
+ }
+ id++;
+ }
+ }).onDone(() {
+ log.fine('Received all statistics.');
+ var sb = new StringBuffer();
+ appInfo.write(sb, executedIds);
+ new File(outPath).writeAsString(sb.toString());
+ log.fine('Results are written to $outPath.');
+ request.response.close();
+ });
+ }
+
+ String rewritePathContent(String path) {
+ if (path.endsWith('__coverage_lib.dart')) {
+ String implPath = pathos.joinAll([
+ pathos.dirname(new Options().script),
+ '..', 'lib', 'src', 'services', 'runtime', 'coverage',
+ 'coverage_lib.dart']);
+ var content = new File(implPath).readAsStringSync();
+ content = content.replaceAll('0; // replaced during rewrite', '$port;');
+ return content;
+ }
+ return null;
+ }
+
+ bool shouldRewriteFile(String path) {
+ if (pathos.extension(path).toLowerCase() != '.dart') return false;
+ // Rewrite target itself, only to send statistics.
+ if (path == targetPath) {
+ return true;
+ }
+ // TODO(scheglov) use configuration
+ if (path.contains('/packages/analyzer_experimental/')) {
+ return true;
+ }
+ return false;
+ }
+
+ String rewriteFileContent(String path, String code) {
+ var unit = _parseCode(code);
+ log.finest('[$path] Parsed.');
+ var injector = new CodeInjector(code);
+ // Inject imports.
+ var directives = unit.directives;
+ if (directives.isNotEmpty && directives[0] is LibraryDirective) {
+ injector.inject(directives[0].end,
+ 'import "package:unittest/unittest.dart" as __cc_ut;'
+ 'import "http://127.0.0.1:$port/__coverage_lib.dart" as __cc;');
+ }
+ // Inject statistics sender.
+ var isTargetScript = path == targetPath;
+ if (isTargetScript) {
+ for (var node in unit.declarations) {
+ if (node is FunctionDeclaration) {
+ var body = node.functionExpression.body;
+ if (node.name.name == 'main' && body is BlockFunctionBody) {
+ injector.inject(node.offset,
+ 'class __CCC extends __cc_ut.Configuration {'
+ ' void onDone(bool success) {'
+ ' __cc.postStatistics();'
+ ' super.onDone(success);'
+ ' }'
+ '}');
+ injector.inject(
+ body.offset + 1,
+ '__cc_ut.unittestConfiguration = new __CCC();');
+ }
+ }
+ }
+ }
+ // Inject touch() invocations.
+ if (!isTargetScript) {
+ appInfo.enterUnit(path, code);
+ unit.accept(new InsertTouchInvocationsVisitor(appInfo, injector));
+ }
+ // Done.
+ return injector.getResult();
+ }
+
+ CompilationUnit _parseCode(String code) {
+ var source = null;
+ var errorListener = new RecordingErrorListener();
+ var parser = new Parser(source, errorListener);
+ var scanner = new StringScanner(source, code, errorListener);
+ var token = scanner.tokenize();
+ return parser.parseCompilationUnit(token);
+ }
+}
+
+
+/// The visitor that inserts `touch` method invocations.
+class InsertTouchInvocationsVisitor extends GeneralizingASTVisitor {
+ final AppInfo appInfo;
+ final CodeInjector injector;
+
+ InsertTouchInvocationsVisitor(this.appInfo, this.injector);
+
+ visitClassDeclaration(ClassDeclaration node) {
+ appInfo.enter('class', node.name.name);
+ super.visitClassDeclaration(node);
+ appInfo.leave();
+ }
+
+ visitConstructorDeclaration(ConstructorDeclaration node) {
+ var className = (node.parent as ClassDeclaration).name.name;
+ var constructorName;
+ if (node.name == null) {
+ constructorName = className;
+ } else {
+ constructorName = className + '.' + node.name.name;
+ }
+ appInfo.enter('constructor', constructorName);
+ super.visitConstructorDeclaration(node);
+ appInfo.leave();
+ }
+
+ visitMethodDeclaration(MethodDeclaration node) {
+ if (node.isAbstract) {
+ super.visitMethodDeclaration(node);
+ } else {
+ var kind;
+ if (node.isGetter) {
+ kind = 'getter';
+ } else if (node.isSetter) {
+ kind = 'setter';
+ } else {
+ kind = 'method';
+ }
+ appInfo.enter(kind, node.name.name);
+ super.visitMethodDeclaration(node);
+ appInfo.leave();
+ }
+ }
+
+ visitStatement(Statement node) {
+ insertTouch(node);
+ super.visitStatement(node);
+ }
+
+ void insertTouch(Statement node) {
+ if (node is Block) return;
+ if (node.parent is LabeledStatement) return;
+ if (node.parent is! Block) return;
+ // Inject 'touch' invocation.
+ var offset = node.offset;
+ var id = appInfo.addNode(node);
+ injector.inject(offset, '__cc.touch($id);');
+ }
+}
+
+
+/// Helper for injecting fragments into some existing code.
+class CodeInjector {
+ final String _code;
+ final offsetFragmentMap = new SplayTreeMap<int, String>();
+
+ CodeInjector(this._code);
+
+ void inject(int offset, String fragment) {
+ offsetFragmentMap[offset] = fragment;
+ }
+
+ String getResult() {
+ var sb = new StringBuffer();
+ var lastOffset = 0;
+ offsetFragmentMap.forEach((offset, fragment) {
+ sb.write(_code.substring(lastOffset, offset));
+ sb.write(fragment);
+ lastOffset = offset;
+ });
+ sb.write(_code.substring(lastOffset, _code.length));
+ return sb.toString();
+ }
+}
diff --git a/pkg/analyzer_experimental/lib/src/services/runtime/coverage/coverage_lib.dart b/pkg/analyzer_experimental/lib/src/services/runtime/coverage/coverage_lib.dart
new file mode 100644
index 0000000..8f1bac8
--- /dev/null
+++ b/pkg/analyzer_experimental/lib/src/services/runtime/coverage/coverage_lib.dart
@@ -0,0 +1,27 @@
+// Copyright (c) 2013, 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.
+
+/// This library is injected into the applications under coverage.
+library coverage_lib;
+
+import 'dart:io';
+import 'dart:typed_data';
+
+const PORT = 0; // replaced during rewrite
+final _executedIds = new Uint8List(1024 * 64);
+
+touch(int id) {
+ int listIndex = id ~/ 8;
+ int bitIndex = id % 8;
+ _executedIds[listIndex] |= 1 << bitIndex;
+}
+
+postStatistics() {
+ var httpClient = new HttpClient();
+ return httpClient.post('127.0.0.1', PORT, '/statistics')
+ .then((request) {
+ request.add(_executedIds);
+ return request.close();
+ });
+}
diff --git a/pkg/analyzer_experimental/lib/src/services/runtime/coverage/models.dart b/pkg/analyzer_experimental/lib/src/services/runtime/coverage/models.dart
new file mode 100644
index 0000000..463a0cd
--- /dev/null
+++ b/pkg/analyzer_experimental/lib/src/services/runtime/coverage/models.dart
@@ -0,0 +1,168 @@
+// Copyright (c) 2013, 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.
+
+/// A library with code coverage models.
+library runtime.coverage.model;
+
+import 'dart:collection' show SplayTreeMap;
+
+import 'package:analyzer_experimental/src/generated/source.dart' show Source, SourceRange;
+import 'package:analyzer_experimental/src/generated/ast.dart' show ASTNode;
+
+import 'utils.dart';
+
+
+/// Contains information about the application.
+class AppInfo {
+ final nodeStack = new List<NodeInfo>();
+ final units = new List<UnitInfo>();
+ final pathToFile = new Map<String, UnitInfo>();
+ NodeInfo currentNode;
+ int nextId = 0;
+
+ void enterUnit(String path, String content) {
+ var unit = new UnitInfo(this, path, content);
+ units.add(unit);
+ currentNode = unit;
+ }
+
+ void enter(String kind, String name) {
+ nodeStack.add(currentNode);
+ currentNode = new NodeInfo(this, currentNode, kind, name);
+ }
+
+ void leave() {
+ currentNode = nodeStack.removeLast();
+ }
+
+ int addNode(ASTNode node) {
+ return currentNode.addNode(node);
+ }
+
+ void write(StringSink sink, Set<int> executedIds) {
+ sink.writeln('{');
+ units.fold(null, (prev, unit) {
+ if (prev != null) sink.writeln(',');
+ return unit..write(sink, executedIds, ' ');
+ });
+ sink.writeln();
+ sink.writeln('}');
+ }
+}
+
+/// Information about some node - unit, class, method, function.
+class NodeInfo {
+ final AppInfo appInfo;
+ final NodeInfo parent;
+ final String kind;
+ final String name;
+ final idToRange = new SplayTreeMap<int, SourceRange>();
+ final children = <NodeInfo>[];
+
+ NodeInfo(this.appInfo, this.parent, this.kind, this.name) {
+ if (parent != null) {
+ parent.children.add(this);
+ }
+ }
+
+ int addNode(ASTNode node) {
+ var id = appInfo.nextId++;
+ var range = new SourceRange(node.offset, node.length);
+ idToRange[id] = range;
+ return id;
+ }
+
+ void write(StringSink sink, Set<int> executedIds, String prefix) {
+ sink.writeln('$prefix"$name": {');
+ // Kind.
+ sink.writeln('$prefix "kind": "$kind",');
+ // Print children.
+ if (children.isNotEmpty) {
+ sink.writeln('$prefix "children": {');
+ children.fold(null, (prev, child) {
+ if (prev != null) sink.writeln(',');
+ return child..write(sink, executedIds, '$prefix ');
+ });
+ sink.writeln();
+ sink.writeln('$prefix }');
+ }
+ // Print source and line ranges.
+ if (children.isEmpty) {
+ sink.write('${prefix} "ranges": [');
+ var rangePrinter = new RangePrinter(unit, sink, executedIds);
+ idToRange.forEach(rangePrinter.handle);
+ rangePrinter.printRange();
+ sink.writeln(']');
+ }
+ // Close this node.
+ sink.write('$prefix}');
+ }
+
+ UnitInfo get unit => parent.unit;
+}
+
+/// Helper for printing merged source/line intervals.
+class RangePrinter {
+ final UnitInfo unit;
+ final StringSink sink;
+ final Set<int> executedIds;
+
+ bool first = true;
+ int startId = -1;
+ int startOffset = -1;
+ int endId = -1;
+ int endOffset = -1;
+
+ RangePrinter(this.unit, this.sink, this.executedIds);
+
+ handle(int id, SourceRange range) {
+ if (executedIds.contains(id)) {
+ printRange();
+ } else {
+ if (endId == id - 1) {
+ endId = id;
+ endOffset = range.end;
+ } else {
+ startId = id;
+ endId = id;
+ startOffset = range.offset;
+ endOffset = range.end;
+ }
+ }
+ }
+
+ void printRange() {
+ if (endId == -1) return;
+ printSeparator();
+ var startLine = unit.getLine(startOffset);
+ var endLine = unit.getLine(endOffset);
+ sink.write('$startOffset,$endOffset,$startLine,$endLine');
+ startId = startOffset = startLine = -1;
+ endId = endOffset = endLine = -1;
+ }
+
+ void printSeparator() {
+ if (first) {
+ first = false;
+ } else {
+ sink.write(', ');
+ }
+ }
+}
+
+/// Contains information about the single unit of the application.
+class UnitInfo extends NodeInfo {
+ List<int> lineOffsets;
+
+ UnitInfo(AppInfo appInfo, String path, String content)
+ : super(appInfo, null, 'unit', path) {
+ lineOffsets = getLineOffsets(content);
+ }
+
+ UnitInfo get unit => this;
+
+ int getLine(int offset) {
+ return binarySearch(lineOffsets, (x) => x >= offset);
+ }
+}
diff --git a/pkg/analyzer_experimental/lib/src/services/runtime/coverage/utils.dart b/pkg/analyzer_experimental/lib/src/services/runtime/coverage/utils.dart
new file mode 100644
index 0000000..3af2276
--- /dev/null
+++ b/pkg/analyzer_experimental/lib/src/services/runtime/coverage/utils.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2013, 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 utils;
+
+
+List<int> getLineOffsets(String text) {
+ var offsets = <int> [0];
+ var wasSR = false;
+ text.codeUnits.asMap().forEach((int i, int codeUnit) {
+ if (codeUnit == 13) {
+ wasSR = true;
+ return;
+ }
+ if (codeUnit == 10) {
+ if (wasSR) {
+ offsets.add(i - 1);
+ } else {
+ offsets.add(i);
+ }
+ }
+ wasSR = false;
+ });
+ return offsets;
+}
+
+/// Find the first entry in a sorted [list] that matches a monotonic predicate.
+/// Given a result `n`, that all items before `n` will not match, `n` matches,
+/// and all items after `n` match too. The result is -1 when there are no
+/// items, 0 when all items match, and list.length when none does.
+// TODO(scheglov) remove this function after dartbug.com/5624 is fixed.
+int binarySearch(List list, bool matches(item)) {
+ if (list.length == 0) return -1;
+ if (matches(list.first)) return 0;
+ if (!matches(list.last)) return list.length;
+ var min = 0;
+ var max = list.length - 1;
+ while (min < max) {
+ var half = min + ((max - min) ~/ 2);
+ if (matches(list[half])) {
+ max = half;
+ } else {
+ min = half + 1;
+ }
+ }
+ return max;
+}
diff --git a/pkg/analyzer_experimental/lib/src/services/runtime/log.dart b/pkg/analyzer_experimental/lib/src/services/runtime/log.dart
new file mode 100644
index 0000000..48d66d7
--- /dev/null
+++ b/pkg/analyzer_experimental/lib/src/services/runtime/log.dart
@@ -0,0 +1,49 @@
+// Copyright (c) 2013, 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.
+
+/// Simple wrapper for [Logger] library.
+library runtime.log;
+
+import "package:logging/logging.dart";
+
+/** Log message at level [Level.FINEST]. */
+void finest(String message) => _logger.log(Level.FINEST, message);
+
+/** Log message at level [Level.FINER]. */
+void finer(String message) => _logger.log(Level.FINER, message);
+
+/** Log message at level [Level.FINE]. */
+void fine(String message) => _logger.log(Level.FINE, message);
+
+/** Log message at level [Level.CONFIG]. */
+void config(String message) => _logger.log(Level.CONFIG, message);
+
+/** Log message at level [Level.INFO]. */
+void info(String message) => _logger.log(Level.INFO, message);
+
+/** Log message at level [Level.WARNING]. */
+void warning(String message) => _logger.log(Level.WARNING, message);
+
+/** Log message at level [Level.SEVERE]. */
+void severe(String message) => _logger.log(Level.SEVERE, message);
+
+/** Log message at level [Level.SHOUT]. */
+void shout(String message) => _logger.log(Level.SHOUT, message);
+
+/// Specifies that all log records should be logged.
+void everything() {
+ _logger.level = Level.ALL;
+}
+
+/// Sends all log record to the console.
+void toConsole() {
+ _logger.onRecord.listen((LogRecord record) {
+ String levelString = record.level.toString();
+ while (levelString.length < 6) levelString += ' ';
+ print('${record.time}: ${levelString} ${record.message}');
+ });
+}
+
+/// The root [Logger].
+final Logger _logger = Logger.root;
diff --git a/pkg/args/lib/src/options.dart b/pkg/args/lib/src/options.dart
new file mode 100644
index 0000000..00c0ad8
--- /dev/null
+++ b/pkg/args/lib/src/options.dart
@@ -0,0 +1,47 @@
+library options;
+
+/**
+ * A command-line option. Includes both flags and options which take a value.
+ */
+class Option {
+ final String name;
+ final String abbreviation;
+ final List<String> allowed;
+ final defaultValue;
+ final Function callback;
+ final String help;
+ final Map<String, String> allowedHelp;
+ final bool isFlag;
+ final bool negatable;
+ final bool allowMultiple;
+
+ Option(this.name, this.abbreviation, this.help, this.allowed,
+ this.allowedHelp, this.defaultValue, this.callback, {this.isFlag,
+ this.negatable, this.allowMultiple: false}) {
+
+ if (name.isEmpty) {
+ throw new ArgumentError('Name cannot be empty.');
+ } else if (name.startsWith('-')) {
+ throw new ArgumentError('Name $name cannot start with "-".');
+ }
+
+ // Ensure name does not contain any invalid characters.
+ if (_invalidChars.hasMatch(name)) {
+ throw new ArgumentError('Name "$name" contains invalid characters.');
+ }
+
+ if (abbreviation != null) {
+ if (abbreviation.length != 1) {
+ throw new ArgumentError('Abbreviation must be null or have length 1.');
+ } else if(abbreviation == '-') {
+ throw new ArgumentError('Abbreviation cannot be "-".');
+ }
+
+ if (_invalidChars.hasMatch(abbreviation)) {
+ throw new ArgumentError('Abbreviation is an invalid character.');
+ }
+ }
+ }
+
+ static final _invalidChars = new RegExp(r'''[ \t\r\n"'\\/]''');
+}
diff --git a/tests/corelib/uri_http_test.dart b/tests/corelib/uri_http_test.dart
new file mode 100644
index 0000000..354e052
--- /dev/null
+++ b/tests/corelib/uri_http_test.dart
@@ -0,0 +1,68 @@
+// 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 "package:expect/expect.dart";
+
+testHttpUri() {
+ void check(Uri uri, String expected) {
+ Expect.equals(expected, uri.toString());
+ }
+
+ check(new Uri.http("", ""), "http:");
+ check(new Uri.http("@:", ""), "http:");
+ check(new Uri.http("@:8080", ""), "http:");
+ check(new Uri.http("@host:", ""), "http://host");
+ check(new Uri.http("@host:", ""), "http://host");
+ check(new Uri.http("xxx:yyy@host:8080", ""), "http://xxx:yyy@host:8080");
+ check(new Uri.http("host", "a"), "http://host/a");
+ check(new Uri.http("host", "/a"), "http://host/a");
+ check(new Uri.http("host", "a/"), "http://host/a/");
+ check(new Uri.http("host", "/a/"), "http://host/a/");
+ check(new Uri.http("host", "a/b"), "http://host/a/b");
+ check(new Uri.http("host", "/a/b"), "http://host/a/b");
+ check(new Uri.http("host", "a/b/"), "http://host/a/b/");
+ check(new Uri.http("host", "/a/b/"), "http://host/a/b/");
+ check(new Uri.http("host", "a b"), "http://host/a%20b");
+ check(new Uri.http("host", "/a b"), "http://host/a%20b");
+ check(new Uri.http("host", "/a b/"), "http://host/a%20b/");
+ check(new Uri.http("host", "/a%2F"), "http://host/a%252F");
+ check(new Uri.http("host", "/a%2F/"), "http://host/a%252F/");
+ check(new Uri.http("host", "/a/b", { "c": "d" }), "http://host/a/b?c=d");
+ check(new Uri.http("host",
+ "/a/b", { "c=": "&d" }), "http://host/a/b?c%3D=%26d");
+}
+
+testHttpsUri() {
+ void check(Uri uri, String expected) {
+ Expect.equals(expected, uri.toString());
+ }
+
+ check(new Uri.https("", ""), "https:");
+ check(new Uri.https("@:", ""), "https:");
+ check(new Uri.https("@:8080", ""), "https:");
+ check(new Uri.https("@host:", ""), "https://host");
+ check(new Uri.https("@host:", ""), "https://host");
+ check(new Uri.https("xxx:yyy@host:8080", ""), "https://xxx:yyy@host:8080");
+ check(new Uri.https("host", "a"), "https://host/a");
+ check(new Uri.https("host", "/a"), "https://host/a");
+ check(new Uri.https("host", "a/"), "https://host/a/");
+ check(new Uri.https("host", "/a/"), "https://host/a/");
+ check(new Uri.https("host", "a/b"), "https://host/a/b");
+ check(new Uri.https("host", "/a/b"), "https://host/a/b");
+ check(new Uri.https("host", "a/b/"), "https://host/a/b/");
+ check(new Uri.https("host", "/a/b/"), "https://host/a/b/");
+ check(new Uri.https("host", "a b"), "https://host/a%20b");
+ check(new Uri.https("host", "/a b"), "https://host/a%20b");
+ check(new Uri.https("host", "/a b/"), "https://host/a%20b/");
+ check(new Uri.https("host", "/a%2F"), "https://host/a%252F");
+ check(new Uri.https("host", "/a%2F/"), "https://host/a%252F/");
+ check(new Uri.https("host", "/a/b", { "c": "d" }), "https://host/a/b?c=d");
+ check(new Uri.https("host",
+ "/a/b", { "c=": "&d" }), "https://host/a/b?c%3D=%26d");
+}
+
+main() {
+ testHttpUri();
+ testHttpsUri();
+}
diff --git a/tests/lib/mirrors/intercepted_class_test.dart b/tests/lib/mirrors/intercepted_class_test.dart
new file mode 100644
index 0000000..a17b7e9
--- /dev/null
+++ b/tests/lib/mirrors/intercepted_class_test.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2013, 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.
+
+// Ensure that classes handled specially by dart2js can be reflected on.
+
+library test.intercepted_class_test;
+
+import 'dart:mirrors';
+
+import 'reflect_model_test.dart' show stringify, expect;
+
+checkClassMirror(ClassMirror cls, String name) {
+ expect('s($name)', cls.simpleName);
+ var variables = new Map();
+ cls.variables.forEach((Symbol key, VariableMirror value) {
+ if (!value.isStatic && !value.isPrivate) {
+ variables[key] = value;
+ }
+ });
+ expect('{}', variables);
+}
+
+main() {
+ checkClassMirror(reflectClass(String), 'String');
+ checkClassMirror(reflectClass(int), 'int');
+ checkClassMirror(reflectClass(double), 'double');
+ checkClassMirror(reflectClass(num), 'num');
+ checkClassMirror(reflectClass(bool), 'bool');
+ checkClassMirror(reflectClass(List), 'List');
+}
diff --git a/tests/lib/mirrors/intercepted_object_test.dart b/tests/lib/mirrors/intercepted_object_test.dart
new file mode 100644
index 0000000..26016e1
--- /dev/null
+++ b/tests/lib/mirrors/intercepted_object_test.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2013, 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.
+
+// Ensure that objects handled specially by dart2js can be reflected on.
+
+library test.intercepted_class_test;
+
+import 'dart:mirrors';
+
+import 'reflect_model_test.dart' show stringify, expect;
+
+import 'intercepted_class_test.dart' show checkClassMirror;
+
+checkObject(object, String name) {
+ checkClassMirror(reflect(object).type, name);
+}
+
+main() {
+ checkObject('', 'String');
+ checkObject(1, 'int');
+ checkObject(1.5, 'double');
+ checkObject(true, 'bool');
+ checkObject(false, 'bool');
+ checkObject([], 'List');
+}
diff --git a/tests/lib/mirrors/is_odd_test.dart b/tests/lib/mirrors/is_odd_test.dart
new file mode 100644
index 0000000..c9fdde1
--- /dev/null
+++ b/tests/lib/mirrors/is_odd_test.dart
@@ -0,0 +1,16 @@
+// Copyright (c) 2013, 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.
+
+/// Test that otherwise unused intercepted methods are reified correctly. This
+/// was a bug in dart2js.
+library test.is_odd_test;
+
+import 'dart:mirrors';
+
+import 'package:expect/expect.dart';
+
+main() {
+ Expect.isTrue(reflect(1).getField(new Symbol('isOdd')).reflectee);
+ Expect.isFalse(reflect(2).getField(new Symbol('isOdd')).reflectee);
+}
diff --git a/tools/VERSION b/tools/VERSION
index 1f38ac9..c06980b 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -1,4 +1,4 @@
MAJOR 0
MINOR 5
BUILD 20
-PATCH 1
+PATCH 2