Update packages and remove dead dependencies.

diff --git a/packages/args/.analysis_options b/packages/args/.analysis_options
deleted file mode 100644
index a10d4c5..0000000
--- a/packages/args/.analysis_options
+++ /dev/null
@@ -1,2 +0,0 @@
-analyzer:
-  strong-mode: true
diff --git a/packages/args/.gitignore b/packages/args/.gitignore
deleted file mode 100644
index 7dbf035..0000000
--- a/packages/args/.gitignore
+++ /dev/null
@@ -1,15 +0,0 @@
-# Don’t commit the following directories created by pub.
-.buildlog
-.pub/
-build/
-packages
-.packages
-
-# Or the files created by dart2js.
-*.dart.js
-*.js_
-*.js.deps
-*.js.map
-
-# Include when developing application packages.
-pubspec.lock
\ No newline at end of file
diff --git a/packages/args/CHANGELOG.md b/packages/args/CHANGELOG.md
deleted file mode 100644
index 5f5c8c6..0000000
--- a/packages/args/CHANGELOG.md
+++ /dev/null
@@ -1,160 +0,0 @@
-## 0.13.7
-
-* Add explicit support for forwarding the value returned by `Command.run()` to
-  `CommandRunner.run()`. This worked unintentionally prior to 0.13.6+1.
-
-* Add type arguments to `CommandRunner` and `Command` to indicate the return
-  values of the `run()` functions.
-
-## 0.13.6+1
-
-* When a `CommandRunner` is passed `--help` before any commands, it now prints
-  the usage of the chosen command.
-
-## 0.13.6
-
-* `ArgParser.parse()` now throws an `ArgParserException`, which implements
-  `FormatException` and has a field that lists the commands that were parsed.
-
-* If `CommandRunner.run()` encounters a parse error for a subcommand, it now
-  prints the subcommand's usage rather than the global usage.
-
-## 0.13.5
-
-* Allow `CommandRunner.argParser` and `Command.argParser` to be overridden in
-  strong mode.
-
-## 0.13.4+2
-
-* Fix a minor documentation error.
-
-## 0.13.4+1
-
-* Ensure that multiple-value arguments produce reified `List<String>`s.
-
-## 0.13.4
-
-* By default, only the first line of a command's description is included in its
-  parent runner's usage string. This returns to the default behavior from
-  before 0.13.3+1.
-
-* A `Command.summary` getter has been added to explicitly control the summary
-  that appears in the parent runner's usage string. This getter defaults to the
-  first line of the description, but can be overridden if the user wants a
-  multi-line summary.
-
-## 0.13.3+6
-
-* README fixes.
-
-## 0.13.3+5
-
-* Make strong mode clean.
-
-## 0.13.3+4
-
-* Use the proper `usage` getter in the README.
-
-## 0.13.3+3
-
-* Add an explicit default value for the `allowTrailingOptions` parameter to `new
-  ArgParser()`. This doesn't change the behavior at all; the option already
-  defaulted to `false`, and passing in `null` still works.
-
-## 0.13.3+2
-
-* Documentation fixes.
-
-## 0.13.3+1
-
-* Print all lines of multi-line command descriptions.
-
-## 0.13.2
-
-* Allow option values that look like options. This more closely matches the
-  behavior of [`getopt`][getopt], the *de facto* standard for option parsing.
-
-[getopt]: http://man7.org/linux/man-pages/man3/getopt.3.html
-
-## 0.13.1
-
-* Add `ArgParser.addSeparator()`. Separators allow users to group their options
-  in the usage text.
-
-## 0.13.0
-
-* **Breaking change**: An option that allows multiple values will now
-  automatically split apart comma-separated values. This can be controlled with
-  the `splitCommas` option.
-
-## 0.12.2+6
-
-* Remove the dependency on the `collection` package.
-
-## 0.12.2+5
-
-* Add syntax highlighting to the README.
-
-## 0.12.2+4
-
-* Add an example of using command-line arguments to the README.
-
-## 0.12.2+3
-
-* Fixed implementation of ArgResults.options to really use Iterable<String>
-  instead of Iterable<dynamic> cast to Iterable<String>.
-
-## 0.12.2+2
-
-* Updated dependency constraint on `unittest`.
-
-* Formatted source code.
-
-* Fixed use of deprecated API in example.
-
-## 0.12.2+1
-
-* Fix the built-in `help` command for `CommandRunner`.
-
-## 0.12.2
-
-* Add `CommandRunner` and `Command` classes which make it easy to build a
-  command-based command-line application.
-
-* Add an `ArgResults.arguments` field, which contains the original argument list.
-
-## 0.12.1
-
-* Replace `ArgParser.getUsage()` with `ArgParser.usage`, a getter.
-  `ArgParser.getUsage()` is now deprecated, to be removed in args version 1.0.0.
-
-## 0.12.0+2
-
-* Widen the version constraint on the `collection` package.
-
-## 0.12.0+1
-
-* Remove the documentation link from the pubspec so this is linked to
-  pub.dartlang.org by default.
-
-## 0.12.0
-
-* Removed public constructors for `ArgResults` and `Option`.
-
-* `ArgResults.wasParsed()` can be used to determine if an option was actually
-  parsed or the default value is being returned.
-
-* Replaced `isFlag` and `allowMultiple` fields in the `Option` class with a
-  three-value `OptionType` enum.
-
-* Options may define `valueHelp` which will then be shown in the usage.
-
-## 0.11.0
-
-* Move handling trailing options from `ArgParser.parse()` into `ArgParser`
-  itself. This lets subcommands have different behavior for how they handle
-  trailing options.
-
-## 0.10.0+2
-
-* Usage ignores hidden options when determining column widths.
diff --git a/packages/args/README.md b/packages/args/README.md
deleted file mode 100644
index 36e931c..0000000
--- a/packages/args/README.md
+++ /dev/null
@@ -1,401 +0,0 @@
-Parses raw command-line arguments into a set of options and values.
-
-This library supports [GNU][] and [POSIX][] style options, and it works
-in both server-side and client-side apps.
-
-## Defining options
-
-First create an [ArgParser][]:
-
-    var parser = new ArgParser();
-
-Then define a set of options on that parser using [addOption()][addOption] and
-[addFlag()][addFlag]. Here's the minimal way to create an option named "name":
-
-    parser.addOption('name');
-
-When an option can only be set or unset (as opposed to taking a string value),
-use a flag:
-
-```dart
-parser.addFlag('name');
-```
-
-Flag options, by default, accept a 'no-' prefix to negate the option. You can
-disable the 'no-' prefix using the `negatable` parameter:
-
-```dart
-parser.addFlag('name', negatable: false);
-```
-
-*Note:* From here on out, "option" refers to both regular options and flags. In
-cases where the distinction matters, we'll use "non-flag option."
-
-Options can have an optional single-character abbreviation, specified with the
-`abbr` parameter:
-
-```dart
-parser.addOption('mode', abbr: 'm');
-parser.addFlag('verbose', abbr: 'v');
-```
-
-Options can also have a default value, specified with the `defaultsTo`
-parameter. The default value is used when arguments don't specify the option.
-
-```dart
-parser.addOption('mode', defaultsTo: 'debug');
-parser.addFlag('verbose', defaultsTo: false);
-```
-
-The default value for non-flag options can be any string. For flags, it must
-be a `bool`.
-
-To validate a non-flag option, you can use the `allowed` parameter to provide an
-allowed set of values. When you do, the parser throws an
-[`ArgParserException`][ArgParserException] if the value for an option is not in
-the allowed set. Here's an example of specifying allowed values:
-
-[ArgParserException]: https://www.dartdocs.org/documentation/args/latest/args/ArgParserException-class.html
-
-```dart
-parser.addOption('mode', allowed: ['debug', 'release']);
-```
-
-You can use the `callback` parameter to associate a function with an option.
-Later, when parsing occurs, the callback function is invoked with the value of
-the option:
-
-```dart
-parser.addOption('mode', callback: (mode) => print('Got mode $mode'));
-parser.addFlag('verbose', callback: (verbose) {
-  if (verbose) print('Verbose');
-});
-```
-
-The callbacks for all options are called whenever a set of arguments is parsed.
-If an option isn't provided in the args, its callback is passed the default
-value, or `null` if no default value is set.
-
-## Parsing arguments
-
-Once you have an [ArgParser][] set up with some options and flags, you use it by
-calling [ArgParser.parse()][parse] with a set of arguments:
-
-```dart
-var results = parser.parse(['some', 'command', 'line', 'args']);
-```
-
-These arguments usually come from the arguments to `main()`. For example:
-
-    main(List<String> args) {
-      // ...
-      var results = parser.parse(args);
-    }
-
-However, you can pass in any list of strings. The `parse()` method returns an
-instance of [ArgResults][], a map-like object that contains the values of the
-parsed options.
-
-```dart
-var parser = new ArgParser();
-parser.addOption('mode');
-parser.addFlag('verbose', defaultsTo: true);
-var results = parser.parse(['--mode', 'debug', 'something', 'else']);
-
-print(results['mode']); // debug
-print(results['verbose']); // true
-```
-
-By default, the `parse()` method stops as soon as it reaches `--` by itself or
-anything that the parser doesn't recognize as an option, flag, or option value.
-If arguments still remain, they go into [ArgResults.rest][rest].
-
-```dart
-print(results.rest); // ['something', 'else']
-```
-
-To continue to parse options found after non-option arguments, pass
-`allowTrailingOptions: true` when creating the [ArgParser][].
-
-## Specifying options
-
-To actually pass in options and flags on the command line, use GNU or POSIX
-style. Consider this option:
-
-```dart
-parser.addOption('name', abbr: 'n');
-```
-
-You can specify its value on the command line using any of the following:
-
-```
---name=somevalue
---name somevalue
--nsomevalue
--n somevalue
-```
-
-Consider this flag:
-
-```dart
-parser.addFlag('name', abbr: 'n');
-```
-
-You can set it to true using one of the following:
-
-```
---name
--n
-```
-
-You can set it to false using the following:
-
-```
---no-name
-```
-
-Multiple flag abbreviations can be collapsed into a single argument. Say you
-define these flags:
-
-```dart
-parser
-  ..addFlag('verbose', abbr: 'v')
-  ..addFlag('french', abbr: 'f')
-  ..addFlag('iambic-pentameter', abbr: 'i');
-```
-
-You can set all three flags at once:
-
-```
--vfi
-```
-
-By default, an option has only a single value, with later option values
-overriding earlier ones; for example:
-
-```dart
-var parser = new ArgParser();
-parser.addOption('mode');
-var results = parser.parse(['--mode', 'on', '--mode', 'off']);
-print(results['mode']); // prints 'off'
-```
-
-If you need multiple values, set the `allowMultiple` parameter. In that case the
-option can occur multiple times, and the `parse()` method returns a list of
-values:
-
-```dart
-var parser = new ArgParser();
-parser.addOption('mode', allowMultiple: true);
-var results = parser.parse(['--mode', 'on', '--mode', 'off']);
-print(results['mode']); // prints '[on, off]'
-```
-
-By default, values for a multi-valued option may also be separated with commas:
-
-```dart
-var parser = new ArgParser();
-parser.addOption('mode', allowMultiple: true);
-var results = parser.parse(['--mode', 'on,off']);
-print(results['mode']); // prints '[on, off]'
-```
-
-This can be disabled by passing `splitCommas: false`.
-
-## Defining commands ##
-
-In addition to *options*, you can also define *commands*. A command is a named
-argument that has its own set of options. For example, consider this shell
-command:
-
-```
-$ git commit -a
-```
-
-The executable is `git`, the command is `commit`, and the `-a` option is an
-option passed to the command. You can add a command using the [addCommand][]
-method:
-
-```dart
-var parser = new ArgParser();
-var command = parser.addCommand('commit');
-```
-
-It returns another [ArgParser][], which you can then use to define options
-specific to that command. If you already have an [ArgParser][] for the command's
-options, you can pass it in:
-
-```dart
-var parser = new ArgParser();
-var command = new ArgParser();
-parser.addCommand('commit', command);
-```
-
-The [ArgParser][] for a command can then define options or flags:
-
-```dart
-command.addFlag('all', abbr: 'a');
-```
-
-You can add multiple commands to the same parser so that a user can select one
-from a range of possible commands. When parsing an argument list, you can then
-determine which command was entered and what options were provided for it.
-
-```dart
-var results = parser.parse(['commit', '-a']);
-print(results.command.name);   // "commit"
-print(results.command['all']); // true
-```
-
-Options for a command must appear after the command in the argument list. For
-example, given the above parser, `"git -a commit"` is *not* valid. The parser
-tries to find the right-most command that accepts an option. For example:
-
-```dart
-var parser = new ArgParser();
-parser.addFlag('all', abbr: 'a');
-var command = parser.addCommand('commit');
-command.addFlag('all', abbr: 'a');
-
-var results = parser.parse(['commit', '-a']);
-print(results.command['all']); // true
-```
-
-Here, both the top-level parser and the `"commit"` command can accept a `"-a"`
-(which is probably a bad command line interface, admittedly). In that case, when
-`"-a"` appears after `"commit"`, it is applied to that command. If it appears to
-the left of `"commit"`, it is given to the top-level parser.
-
-## Dispatching Commands
-
-If you're writing a command-based application, you can use the [CommandRunner][]
-and [Command][] classes to help structure it. [CommandRunner][] has built-in
-support for dispatching to [Command][]s based on command-line arguments, as well
-as handling `--help` flags and invalid arguments. For example:
-
-```dart
-var runner = new CommandRunner("git", "Distributed version control.")
-  ..addCommand(new CommitCommand())
-  ..addCommand(new StashCommand())
-  ..run(['commit', '-a']); // Calls [CommitCommand.run()]
-```
-
-Custom commands are defined by extending the [Command][] class. For example:
-
-```dart
-class CommitCommand extends Command {
-  // The [name] and [description] properties must be defined by every
-  // subclass.
-  final name = "commit";
-  final description = "Record changes to the repository.";
-
-  CommitCommand() {
-    // [argParser] is automatically created by the parent class.
-    argParser.addFlag('all', abbr: 'a');
-  }
-
-  // [run] may also return a Future.
-  void run() {
-    // [argResults] is set before [run()] is called and contains the options
-    // passed to this command.
-    print(argResults['all']);
-  }
-}
-```
-
-Commands can also have subcommands, which are added with [addSubcommand][]. A
-command with subcommands can't run its own code, so [run][] doesn't need to be
-implemented. For example:
-
-```dart
-class StashCommand extends Command {
-  final String name = "stash";
-  final String description = "Stash changes in the working directory.";
-
-  StashCommand() {
-    addSubcommand(new StashSaveCommand());
-    addSubcommand(new StashListCommand());
-  }
-}
-```
-
-[CommandRunner][] automatically adds a `help` command that displays usage
-information for commands, as well as support for the `--help` flag for all
-commands. If it encounters an error parsing the arguments or processing a
-command, it throws a [UsageException][]; your `main()` method should catch these and
-print them appropriately. For example:
-
-```dart
-runner.run(arguments).catchError((error) {
-  if (error is! UsageException) throw error;
-  print(error);
-  exit(64); // Exit code 64 indicates a usage error.
-});
-```
-
-## Displaying usage
-
-You can automatically generate nice help text, suitable for use as the output of
-`--help`. To display good usage information, you should provide some help text
-when you create your options.
-
-To define help text for an entire option, use the `help:` parameter:
-
-```dart
-parser.addOption('mode', help: 'The compiler configuration',
-    allowed: ['debug', 'release']);
-parser.addFlag('verbose', help: 'Show additional diagnostic info');
-```
-
-For non-flag options, you can also provide a help string for the parameter:
-
-```dart
-parser.addOption('out', help: 'The output path', valueHelp: 'path',
-    allowed: ['debug', 'release']);
-```
-
-For non-flag options, you can also provide detailed help for each expected value
-by using the `allowedHelp:` parameter:
-
-```dart
-parser.addOption('arch', help: 'The architecture to compile for',
-    allowedHelp: {
-      'ia32': 'Intel x86',
-      'arm': 'ARM Holding 32-bit chip'
-    });
-```
-
-To display the help, use the [usage][usage] getter:
-
-```dart
-print(parser.usage);
-```
-
-The resulting string looks something like this:
-
-```
---mode            The compiler configuration
-                  [debug, release]
-
---out=<path>      The output path
---[no-]verbose    Show additional diagnostic info
---arch            The architecture to compile for
-      [arm]       ARM Holding 32-bit chip
-      [ia32]      Intel x86
-```
-
-[posix]: http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02
-[gnu]: http://www.gnu.org/prep/standards/standards.html#Command_002dLine-Interfaces
-[ArgParser]: http://www.dartdocs.org/documentation/args/latest/index.html#args/args.ArgParser
-[ArgResults]: http://www.dartdocs.org/documentation/args/latest/index.html#args/args.ArgResults
-[CommandRunner]: http://www.dartdocs.org/documentation/args/latest/index.html#args/command_runner.CommandRunner
-[Command]: http://www.dartdocs.org/documentation/args/latest/index.html#args/command_runner.Command
-[UsageException]: http://www.dartdocs.org/documentation/args/latest/index.html#args/command_runner.UsageException
-[addOption]: http://www.dartdocs.org/documentation/args/latest/index.html#args/args.ArgParser@id_addOption
-[addFlag]: http://www.dartdocs.org/documentation/args/latest/index.html#args/args.ArgParser@id_addFlag
-[parse]: http://www.dartdocs.org/documentation/args/latest/index.html#args/args.ArgParser@id_parse
-[rest]: http://www.dartdocs.org/documentation/args/latest/index.html#args/args.ArgResults@id_rest
-[addCommand]: http://www.dartdocs.org/documentation/args/latest/index.html#args/args.ArgParser@id_addCommand
-[getUsage]: http://www.dartdocs.org/documentation/args/latest/index.html#args/args.ArgParser@id_getUsage
-[addSubcommand]: http://www.dartdocs.org/documentation/args/latest/index.html#args/command_runner.Command@id_addSubcommand
-[run]: http://www.dartdocs.org/documentation/args/latest/index.html#args/command_runner.Command@id_run
diff --git a/packages/args/codereview.settings b/packages/args/codereview.settings
deleted file mode 100644
index da6054d..0000000
--- a/packages/args/codereview.settings
+++ /dev/null
@@ -1,3 +0,0 @@
-CODE_REVIEW_SERVER: http://codereview.chromium.org/
-VIEW_VC: https://github.com/dart-lang/args/commit/
-CC_LIST: reviews@dartlang.org
\ No newline at end of file
diff --git a/packages/args/example/test_runner.dart b/packages/args/example/test_runner.dart
deleted file mode 100644
index 0d15e83..0000000
--- a/packages/args/example/test_runner.dart
+++ /dev/null
@@ -1,165 +0,0 @@
-// 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.
-
-/// This is an example of converting the args in test.dart to use this API.
-/// It shows what it looks like to build an [ArgParser] and then, when the code
-/// is run, demonstrates what the generated usage text looks like.
-library example;
-
-import 'dart:io';
-
-import 'package:args/args.dart';
-
-main() {
-  var parser = new ArgParser();
-
-  parser.addSeparator('===== Platform');
-
-  parser.addOption('compiler',
-      abbr: 'c',
-      defaultsTo: 'none',
-      help: 'Specify any compilation step (if needed).',
-      allowed: ['none', 'dart2js', 'dartc'],
-      allowedHelp: {
-    'none': 'Do not compile the Dart code (run native Dart code on the'
-        ' VM).\n(only valid with the following runtimes: vm, drt)',
-    'dart2js': 'Compile dart code to JavaScript by running dart2js.\n'
-        '(only valid with the following runtimes: d8, drt, chrome\n'
-        'safari, ie, firefox, opera, none (compile only))',
-    'dartc': 'Perform static analysis on Dart code by running dartc.\n'
-        '(only valid with the following runtimes: none)',
-  });
-
-  parser.addOption('runtime',
-      abbr: 'r',
-      defaultsTo: 'vm',
-      help: 'Where the tests should be run.',
-      allowed: [
-    'vm',
-    'd8',
-    'drt',
-    'dartium',
-    'ff',
-    'firefox',
-    'chrome',
-    'safari',
-    'ie',
-    'opera',
-    'none'
-  ],
-      allowedHelp: {
-    'vm': 'Run Dart code on the standalone dart vm.',
-    'd8': 'Run JavaScript from the command line using v8.',
-    // TODO(antonm): rename flag.
-    'drt': 'Run Dart or JavaScript in the headless version of Chrome,\n'
-        'content shell.',
-    'dartium': 'Run Dart or JavaScript in Dartium.',
-    'ff': 'Run JavaScript in Firefox',
-    'chrome': 'Run JavaScript in Chrome',
-    'safari': 'Run JavaScript in Safari',
-    'ie': 'Run JavaScript in Internet Explorer',
-    'opera': 'Run JavaScript in Opera',
-    'none': 'No runtime, compile only (for example, used for dartc static\n'
-        'analysis tests).',
-  });
-
-  parser.addOption('arch',
-      abbr: 'a',
-      defaultsTo: 'ia32',
-      help: 'The architecture to run tests for',
-      allowed: ['all', 'ia32', 'x64', 'simarm']);
-
-  parser.addOption('system',
-      abbr: 's',
-      defaultsTo: Platform.operatingSystem,
-      help: 'The operating system to run tests on',
-      allowed: ['linux', 'macos', 'windows']);
-
-  parser.addSeparator('===== Runtime');
-
-  parser.addOption('mode',
-      abbr: 'm',
-      defaultsTo: 'debug',
-      help: 'Mode in which to run the tests',
-      allowed: ['all', 'debug', 'release']);
-
-  parser.addFlag('checked',
-      defaultsTo: false, help: 'Run tests in checked mode');
-
-  parser.addFlag('host-checked',
-      defaultsTo: false, help: 'Run compiler in checked mode');
-
-  parser.addOption('timeout', abbr: 't', help: 'Timeout in seconds');
-
-  parser.addOption('tasks',
-      abbr: 'j',
-      defaultsTo: Platform.numberOfProcessors.toString(),
-      help: 'The number of parallel tasks to run');
-
-  parser.addOption('shards',
-      defaultsTo: '1',
-      help: 'The number of instances that the tests will be sharded over');
-
-  parser.addOption('shard',
-      defaultsTo: '1',
-      help: 'The index of this instance when running in sharded mode');
-
-  parser.addFlag('valgrind',
-      defaultsTo: false, help: 'Run tests through valgrind');
-
-  parser.addSeparator('===== Output');
-
-  parser.addOption('progress',
-      abbr: 'p',
-      defaultsTo: 'compact',
-      help: 'Progress indication mode',
-      allowed: [
-    'compact',
-    'color',
-    'line',
-    'verbose',
-    'silent',
-    'status',
-    'buildbot'
-  ]);
-
-  parser.addFlag('report',
-      defaultsTo: false,
-      help: 'Print a summary report of the number of tests, by expectation');
-
-  parser.addFlag('verbose',
-      abbr: 'v', defaultsTo: false, help: 'Verbose output');
-
-  parser.addFlag('list',
-      defaultsTo: false, help: 'List tests only, do not run them');
-
-  parser.addFlag('time',
-      help: 'Print timing information after running tests', defaultsTo: false);
-
-  parser.addFlag('batch',
-      abbr: 'b', help: 'Run browser tests in batch mode', defaultsTo: true);
-
-  parser.addSeparator('===== Miscellaneous');
-
-  parser.addFlag('keep-generated-tests',
-      defaultsTo: false,
-      help: 'Keep the generated files in the temporary directory');
-
-  parser.addOption('special-command', help: """
-Special command support. Wraps the command line in
-a special command. The special command should contain
-an '@' character which will be replaced by the normal
-command.
-
-For example if the normal command that will be executed
-is 'dart file.dart' and you specify special command
-'python -u valgrind.py @ suffix' the final command will be
-'python -u valgrind.py dart file.dart suffix'""");
-
-  parser.addOption('dart', help: 'Path to dart executable');
-  parser.addOption('drt', help: 'Path to content shell executable');
-  parser.addOption('dartium', help: 'Path to Dartium Chrome executable');
-
-  print(parser.usage);
-}
diff --git a/packages/args/lib/args.dart b/packages/args/lib/args.dart
deleted file mode 100644
index 97b47b5..0000000
--- a/packages/args/lib/args.dart
+++ /dev/null
@@ -1,8 +0,0 @@
-// 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.
-
-export 'src/arg_parser.dart';
-export 'src/arg_parser_exception.dart';
-export 'src/arg_results.dart' hide newArgResults;
-export 'src/option.dart' hide newOption;
diff --git a/packages/args/lib/command_runner.dart b/packages/args/lib/command_runner.dart
deleted file mode 100644
index 6a6d282..0000000
--- a/packages/args/lib/command_runner.dart
+++ /dev/null
@@ -1,412 +0,0 @@
-// 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.
-
-import 'dart:async';
-import 'dart:collection';
-import 'dart:math' as math;
-
-import 'src/arg_parser.dart';
-import 'src/arg_parser_exception.dart';
-import 'src/arg_results.dart';
-import 'src/help_command.dart';
-import 'src/usage_exception.dart';
-import 'src/utils.dart';
-
-export 'src/usage_exception.dart';
-
-/// A class for invoking [Commands] based on raw command-line arguments.
-///
-/// The type argument `T` represents the type returned by [Command.run] and
-/// [CommandRunner.run]; it can be ommitted if you're not using the return
-/// values.
-class CommandRunner<T> {
-  /// The name of the executable being run.
-  ///
-  /// Used for error reporting and [usage].
-  final String executableName;
-
-  /// A short description of this executable.
-  final String description;
-
-  /// A single-line template for how to invoke this executable.
-  ///
-  /// Defaults to "$executableName <command> [arguments]". Subclasses can
-  /// override this for a more specific template.
-  String get invocation => "$executableName <command> [arguments]";
-
-  /// Generates a string displaying usage information for the executable.
-  ///
-  /// This includes usage for the global arguments as well as a list of
-  /// top-level commands.
-  String get usage => "$description\n\n$_usageWithoutDescription";
-
-  /// An optional footer for [usage].
-  ///
-  /// If a subclass overrides this to return a string, it will automatically be
-  /// added to the end of [usage].
-  String get usageFooter => null;
-
-  /// Returns [usage] with [description] removed from the beginning.
-  String get _usageWithoutDescription {
-    var usage = '''
-Usage: $invocation
-
-Global options:
-${argParser.usage}
-
-${_getCommandUsage(_commands)}
-
-Run "$executableName help <command>" for more information about a command.''';
-
-    if (usageFooter != null) usage += "\n$usageFooter";
-    return usage;
-  }
-
-  /// An unmodifiable view of all top-level commands defined for this runner.
-  Map<String, Command<T>> get commands => new UnmodifiableMapView(_commands);
-  final _commands = <String, Command<T>>{};
-
-  /// The top-level argument parser.
-  ///
-  /// Global options should be registered with this parser; they'll end up
-  /// available via [Command.globalResults]. Commands should be registered with
-  /// [addCommand] rather than directly on the parser.
-  ArgParser get argParser => _argParser;
-  final _argParser = new ArgParser();
-
-  CommandRunner(this.executableName, this.description) {
-    argParser.addFlag('help',
-        abbr: 'h', negatable: false, help: 'Print this usage information.');
-    addCommand(new HelpCommand<T>());
-  }
-
-  /// Prints the usage information for this runner.
-  ///
-  /// This is called internally by [run] and can be overridden by subclasses to
-  /// control how output is displayed or integrate with a logging system.
-  void printUsage() => print(usage);
-
-  /// Throws a [UsageException] with [message].
-  void usageException(String message) =>
-      throw new UsageException(message, _usageWithoutDescription);
-
-  /// Adds [Command] as a top-level command to this runner.
-  void addCommand(Command<T> command) {
-    var names = [command.name]..addAll(command.aliases);
-    for (var name in names) {
-      _commands[name] = command;
-      argParser.addCommand(name, command.argParser);
-    }
-    command._runner = this;
-  }
-
-  /// Parses [args] and invokes [Command.run] on the chosen command.
-  ///
-  /// This always returns a [Future] in case the command is asynchronous. The
-  /// [Future] will throw a [UsageException] if [args] was invalid.
-  Future<T> run(Iterable<String> args) =>
-      new Future.sync(() => runCommand(parse(args)));
-
-  /// Parses [args] and returns the result, converting an [ArgParserException]
-  /// to a [UsageException].
-  ///
-  /// This is notionally a protected method. It may be overridden or called from
-  /// subclasses, but it shouldn't be called externally.
-  ArgResults parse(Iterable<String> args) {
-    try {
-      return argParser.parse(args);
-    } on ArgParserException catch (error) {
-      if (error.commands.isEmpty) usageException(error.message);
-
-      var command = commands[error.commands.first];
-      for (var commandName in error.commands.skip(1)) {
-        command = command.subcommands[commandName];
-      }
-
-      command.usageException(error.message);
-      return null;
-    }
-  }
-
-  /// Runs the command specified by [topLevelResults].
-  ///
-  /// This is notionally a protected method. It may be overridden or called from
-  /// subclasses, but it shouldn't be called externally.
-  ///
-  /// It's useful to override this to handle global flags and/or wrap the entire
-  /// command in a block. For example, you might handle the `--verbose` flag
-  /// here to enable verbose logging before running the command.
-  ///
-  /// This returns the return value of [Command.run]. 
-  Future<T> runCommand(ArgResults topLevelResults) async {
-    var argResults = topLevelResults;
-    var commands = _commands;
-    Command command;
-    var commandString = executableName;
-
-    while (commands.isNotEmpty) {
-      if (argResults.command == null) {
-        if (argResults.rest.isEmpty) {
-          if (command == null) {
-            // No top-level command was chosen.
-            printUsage();
-            return null;
-          }
-
-          command.usageException('Missing subcommand for "$commandString".');
-        } else {
-          if (command == null) {
-            usageException(
-                'Could not find a command named "${argResults.rest[0]}".');
-          }
-
-          command.usageException('Could not find a subcommand named '
-              '"${argResults.rest[0]}" for "$commandString".');
-        }
-      }
-
-      // Step into the command.
-      argResults = argResults.command;
-      command = commands[argResults.name];
-      command._globalResults = topLevelResults;
-      command._argResults = argResults;
-      commands = command._subcommands;
-      commandString += " ${argResults.name}";
-
-      if (argResults['help']) {
-        command.printUsage();
-        return null;
-      }
-    }
-
-    if (topLevelResults['help']) {
-      command.printUsage();
-      return null;
-    }
-
-    // Make sure there aren't unexpected arguments.
-    if (!command.takesArguments && argResults.rest.isNotEmpty) {
-      command.usageException(
-          'Command "${argResults.name}" does not take any arguments.');
-    }
-
-    return (await command.run()) as T;
-  }
-}
-
-/// A single command.
-///
-/// A command is known as a "leaf command" if it has no subcommands and is meant
-/// to be run. Leaf commands must override [run].
-///
-/// A command with subcommands is known as a "branch command" and cannot be run
-/// itself. It should call [addSubcommand] (often from the constructor) to
-/// register subcommands.
-abstract class Command<T> {
-  /// The name of this command.
-  String get name;
-
-  /// A description of this command, included in [usage].
-  String get description;
-
-  /// A short description of this command, included in [parent]'s
-  /// [CommandRunner.usage].
-  ///
-  /// This defaults to the first line of [description].
-  String get summary => description.split("\n").first;
-
-  /// A single-line template for how to invoke this command (e.g. `"pub get
-  /// [package]"`).
-  String get invocation {
-    var parents = [name];
-    for (var command = parent; command != null; command = command.parent) {
-      parents.add(command.name);
-    }
-    parents.add(runner.executableName);
-
-    var invocation = parents.reversed.join(" ");
-    return _subcommands.isNotEmpty
-        ? "$invocation <subcommand> [arguments]"
-        : "$invocation [arguments]";
-  }
-
-  /// The command's parent command, if this is a subcommand.
-  ///
-  /// This will be `null` until [Command.addSubcommmand] has been called with
-  /// this command.
-  Command<T> get parent => _parent;
-  Command<T> _parent;
-
-  /// The command runner for this command.
-  ///
-  /// This will be `null` until [CommandRunner.addCommand] has been called with
-  /// this command or one of its parents.
-  CommandRunner<T> get runner {
-    if (parent == null) return _runner;
-    return parent.runner;
-  }
-  CommandRunner<T> _runner;
-
-  /// The parsed global argument results.
-  ///
-  /// This will be `null` until just before [Command.run] is called.
-  ArgResults get globalResults => _globalResults;
-  ArgResults _globalResults;
-
-  /// The parsed argument results for this command.
-  ///
-  /// This will be `null` until just before [Command.run] is called.
-  ArgResults get argResults => _argResults;
-  ArgResults _argResults;
-
-  /// The argument parser for this command.
-  ///
-  /// Options for this command should be registered with this parser (often in
-  /// the constructor); they'll end up available via [argResults]. Subcommands
-  /// should be registered with [addSubcommand] rather than directly on the
-  /// parser.
-  ArgParser get argParser => _argParser;
-  final _argParser = new ArgParser();
-
-  /// Generates a string displaying usage information for this command.
-  ///
-  /// This includes usage for the command's arguments as well as a list of
-  /// subcommands, if there are any.
-  String get usage => "$description\n\n$_usageWithoutDescription";
-
-  /// An optional footer for [usage].
-  ///
-  /// If a subclass overrides this to return a string, it will automatically be
-  /// added to the end of [usage].
-  String get usageFooter => null;
-
-  /// Returns [usage] with [description] removed from the beginning.
-  String get _usageWithoutDescription {
-    var buffer = new StringBuffer()
-    ..writeln('Usage: $invocation')
-    ..writeln(argParser.usage);
-
-    if (_subcommands.isNotEmpty) {
-      buffer.writeln();
-      buffer.writeln(_getCommandUsage(_subcommands, isSubcommand: true));
-    }
-
-    buffer.writeln();
-    buffer.write('Run "${runner.executableName} help" to see global options.');
-
-    if (usageFooter != null) {
-      buffer.writeln();
-      buffer.write(usageFooter);
-    }
-
-    return buffer.toString();
-  }
-
-  /// An unmodifiable view of all sublevel commands of this command.
-  Map<String, Command<T>> get subcommands =>
-      new UnmodifiableMapView(_subcommands);
-  final _subcommands = <String, Command<T>>{};
-
-  /// Whether or not this command should be hidden from help listings.
-  ///
-  /// This is intended to be overridden by commands that want to mark themselves
-  /// hidden.
-  ///
-  /// By default, leaf commands are always visible. Branch commands are visible
-  /// as long as any of their leaf commands are visible.
-  bool get hidden {
-    // Leaf commands are visible by default.
-    if (_subcommands.isEmpty) return false;
-
-    // Otherwise, a command is hidden if all of its subcommands are.
-    return _subcommands.values.every((subcommand) => subcommand.hidden);
-  }
-
-  /// Whether or not this command takes positional arguments in addition to
-  /// options.
-  ///
-  /// If false, [CommandRunner.run] will throw a [UsageException] if arguments
-  /// are provided. Defaults to true.
-  ///
-  /// This is intended to be overridden by commands that don't want to receive
-  /// arguments. It has no effect for branch commands.
-  bool get takesArguments => true;
-
-  /// Alternate names for this command.
-  ///
-  /// These names won't be used in the documentation, but they will work when
-  /// invoked on the command line.
-  ///
-  /// This is intended to be overridden.
-  List<String> get aliases => const [];
-
-  Command() {
-    argParser.addFlag('help',
-        abbr: 'h', negatable: false, help: 'Print this usage information.');
-  }
-
-  /// Runs this command.
-  ///
-  /// This must return a `T`, a `Future<T>`, or `null`. The value is returned by
-  /// [CommandRunner.runCommand]. Subclasses must explicitly declare a return
-  /// type for `run()`, and may not use `void` if `T` is defined.
-  run() {
-    throw new UnimplementedError("Leaf command $this must implement run().");
-  }
-
-  /// Adds [Command] as a subcommand of this.
-  void addSubcommand(Command<T> command) {
-    var names = [command.name]..addAll(command.aliases);
-    for (var name in names) {
-      _subcommands[name] = command;
-      argParser.addCommand(name, command.argParser);
-    }
-    command._parent = this;
-  }
-
-  /// Prints the usage information for this command.
-  ///
-  /// This is called internally by [run] and can be overridden by subclasses to
-  /// control how output is displayed or integrate with a logging system.
-  void printUsage() => print(usage);
-
-  /// Throws a [UsageException] with [message].
-  void usageException(String message) =>
-    throw new UsageException(message, _usageWithoutDescription);
-}
-
-/// Returns a string representation of [commands] fit for use in a usage string.
-///
-/// [isSubcommand] indicates whether the commands should be called "commands" or
-/// "subcommands".
-String _getCommandUsage(Map<String, Command> commands,
-    {bool isSubcommand: false}) {
-  // Don't include aliases.
-  var names =
-    commands.keys.where((name) => !commands[name].aliases.contains(name));
-
-  // Filter out hidden ones, unless they are all hidden.
-  var visible = names.where((name) => !commands[name].hidden);
-  if (visible.isNotEmpty) names = visible;
-
-  // Show the commands alphabetically.
-  names = names.toList()..sort();
-  var length = names.map((name) => name.length).reduce(math.max);
-
-  var buffer =
-    new StringBuffer('Available ${isSubcommand ? "sub" : ""}commands:');
-  for (var name in names) {
-    var lines = commands[name].summary.split("\n");
-    buffer.writeln();
-    buffer.write('  ${padRight(name, length)}   ${lines.first}');
-
-    for (var line in lines.skip(1)) {
-      buffer.writeln();
-      buffer.write(' ' * (length + 5));
-      buffer.write(line);
-    }
-  }
-
-  return buffer.toString();
-}
diff --git a/packages/args/lib/src/arg_parser.dart b/packages/args/lib/src/arg_parser.dart
deleted file mode 100644
index fa80031..0000000
--- a/packages/args/lib/src/arg_parser.dart
+++ /dev/null
@@ -1,161 +0,0 @@
-// 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.
-
-import 'dart:collection';
-
-import 'arg_results.dart';
-import 'option.dart';
-import 'parser.dart';
-import 'usage.dart';
-
-/// A class for taking a list of raw command line arguments and parsing out
-/// options and flags from them.
-class ArgParser {
-  final Map<String, Option> _options;
-  final Map<String, ArgParser> _commands;
-
-  /// The options that have been defined for this parser.
-  final Map<String, Option> options;
-
-  /// The commands that have been defined for this parser.
-  final Map<String, ArgParser> commands;
-
-  /// A list of the [Option]s in [options] intermingled with [String]
-  /// separators.
-  final _optionsAndSeparators = [];
-
-  /// Whether or not this parser parses options that appear after non-option
-  /// arguments.
-  final bool allowTrailingOptions;
-
-  /// Creates a new ArgParser.
-  ///
-  /// If [allowTrailingOptions] is set, the parser will continue parsing even
-  /// after it finds an argument that is neither an option nor a command.
-  /// This allows options to be specified after regular arguments. Defaults to
-  /// `false`.
-  factory ArgParser({bool allowTrailingOptions: false}) => new ArgParser._(
-      <String, Option>{}, <String, ArgParser>{},
-      allowTrailingOptions: allowTrailingOptions);
-
-  ArgParser._(Map<String, Option> options, Map<String, ArgParser> commands,
-      {bool allowTrailingOptions: false})
-      : this._options = options,
-        this.options = new UnmodifiableMapView(options),
-        this._commands = commands,
-        this.commands = new UnmodifiableMapView(commands),
-        this.allowTrailingOptions = allowTrailingOptions != null
-            ? allowTrailingOptions
-            : false;
-
-  /// Defines a command.
-  ///
-  /// A command is a named argument which may in turn define its own options and
-  /// subcommands using the given parser. If [parser] is omitted, implicitly
-  /// creates a new one. Returns the parser for the command.
-  ArgParser addCommand(String name, [ArgParser parser]) {
-    // Make sure the name isn't in use.
-    if (_commands.containsKey(name)) {
-      throw new ArgumentError('Duplicate command "$name".');
-    }
-
-    if (parser == null) parser = new ArgParser();
-    _commands[name] = parser;
-    return parser;
-  }
-
-  /// Defines a flag. Throws an [ArgumentError] if:
-  ///
-  /// * There is already an option named [name].
-  /// * There is already an option using abbreviation [abbr].
-  void addFlag(String name, {String abbr, String help, bool defaultsTo: false,
-      bool negatable: true, void callback(bool value), bool hide: false}) {
-    _addOption(name, abbr, help, null, null, null, defaultsTo, callback,
-        OptionType.FLAG, negatable: negatable, hide: hide);
-  }
-
-  /// Defines a value-taking option. Throws an [ArgumentError] if:
-  ///
-  /// * There is already an option with name [name].
-  /// * There is already an option using abbreviation [abbr].
-  /// * [splitCommas] is passed but [allowMultiple] is `false`.
-  void addOption(String name, {String abbr, String help, String valueHelp,
-      List<String> allowed, Map<String, String> allowedHelp, String defaultsTo,
-      void callback(value), bool allowMultiple: false, bool splitCommas,
-      bool hide: false}) {
-    if (!allowMultiple && splitCommas != null) {
-      throw new ArgumentError(
-          'splitCommas may not be set if allowMultiple is false.');
-    }
-
-    _addOption(name, abbr, help, valueHelp, allowed, allowedHelp, defaultsTo,
-        callback, allowMultiple ? OptionType.MULTIPLE : OptionType.SINGLE,
-        splitCommas: splitCommas, hide: hide);
-  }
-
-  void _addOption(String name, String abbr, String help, String valueHelp,
-      List<String> allowed, Map<String, String> allowedHelp, defaultsTo,
-      void callback(value), OptionType type,
-      {bool negatable: false, bool splitCommas, bool hide: false}) {
-    // Make sure the name isn't in use.
-    if (_options.containsKey(name)) {
-      throw new ArgumentError('Duplicate option "$name".');
-    }
-
-    // Make sure the abbreviation isn't too long or in use.
-    if (abbr != null) {
-      var existing = findByAbbreviation(abbr);
-      if (existing != null) {
-        throw new ArgumentError(
-            'Abbreviation "$abbr" is already used by "${existing.name}".');
-      }
-    }
-
-    var option = newOption(name, abbr, help, valueHelp, allowed,
-        allowedHelp, defaultsTo, callback, type,
-        negatable: negatable, splitCommas: splitCommas, hide: hide);
-    _options[name] = option;
-    _optionsAndSeparators.add(option);
-  }
-
-  /// Adds a separator line to the usage.
-  ///
-  /// In the usage text for the parser, this will appear between any options
-  /// added before this call and ones added after it.
-  void addSeparator(String text) {
-    _optionsAndSeparators.add(text);
-  }
-
-  /// Parses [args], a list of command-line arguments, matches them against the
-  /// flags and options defined by this parser, and returns the result.
-  ArgResults parse(List<String> args) =>
-      new Parser(null, this, args.toList()).parse();
-
-  /// Generates a string displaying usage information for the defined options.
-  ///
-  /// This is basically the help text shown on the command line.
-  @Deprecated("Replaced with get usage. getUsage() will be removed in args 1.0")
-  String getUsage() => usage;
-
-  /// Generates a string displaying usage information for the defined options.
-  ///
-  /// This is basically the help text shown on the command line.
-  String get usage => new Usage(_optionsAndSeparators).generate();
-
-  /// Get the default value for an option. Useful after parsing to test if the
-  /// user specified something other than the default.
-  getDefault(String option) {
-    if (!options.containsKey(option)) {
-      throw new ArgumentError('No option named $option');
-    }
-    return options[option].defaultValue;
-  }
-
-  /// Finds the option whose abbreviation is [abbr], or `null` if no option has
-  /// that abbreviation.
-  Option findByAbbreviation(String abbr) {
-    return options.values.firstWhere((option) => option.abbreviation == abbr,
-        orElse: () => null);
-  }
-}
diff --git a/packages/args/lib/src/arg_parser_exception.dart b/packages/args/lib/src/arg_parser_exception.dart
deleted file mode 100644
index 20eddb9..0000000
--- a/packages/args/lib/src/arg_parser_exception.dart
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) 2016, 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.
-
-/// An exception thrown by [ArgParser].
-class ArgParserException extends FormatException {
-  /// The command(s) that were parsed before discovering the error.
-  ///
-  /// This will be empty if the error was on the root parser.
-  final List<String> commands;
-
-  ArgParserException(String message, [Iterable<String> commands])
-      : commands = commands == null
-            ? const []
-            : new List.unmodifiable(commands),
-        super(message);
-}
diff --git a/packages/args/lib/src/arg_results.dart b/packages/args/lib/src/arg_results.dart
deleted file mode 100644
index 50e85fa..0000000
--- a/packages/args/lib/src/arg_results.dart
+++ /dev/null
@@ -1,94 +0,0 @@
-// 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.
-
-import 'dart:collection';
-
-import 'arg_parser.dart';
-
-/// Creates a new [ArgResults].
-///
-/// Since [ArgResults] doesn't have a public constructor, this lets [Parser]
-/// get to it. This function isn't exported to the public API of the package.
-ArgResults newArgResults(ArgParser parser, Map<String, dynamic> parsed,
-    String name, ArgResults command, List<String> rest,
-    List<String> arguments) {
-  return new ArgResults._(parser, parsed, name, command, rest, arguments);
-}
-
-/// The results of parsing a series of command line arguments using
-/// [ArgParser.parse()].
-///
-/// Includes the parsed options and any remaining unparsed command line
-/// arguments.
-class ArgResults {
-  /// The [ArgParser] whose options were parsed for these results.
-  final ArgParser _parser;
-
-  /// The option values that were parsed from arguments.
-  final Map<String, dynamic> _parsed;
-
-  /// If these are the results for parsing a command's options, this will be the
-  /// name of the command. For top-level results, this returns `null`.
-  final String name;
-
-  /// The command that was selected, or `null` if none was.
-  ///
-  /// This will contain the options that were selected for that command.
-  final ArgResults command;
-
-  /// The remaining command-line arguments that were not parsed as options or
-  /// flags.
-  ///
-  /// If `--` was used to separate the options from the remaining arguments,
-  /// it will not be included in this list unless parsing stopped before the
-  /// `--` was reached.
-  final List<String> rest;
-
-  /// The original list of arguments that were parsed.
-  final List<String> arguments;
-
-  /// Creates a new [ArgResults].
-  ArgResults._(this._parser, this._parsed, this.name, this.command,
-      List<String> rest, List<String> arguments)
-      : this.rest = new UnmodifiableListView(rest),
-        this.arguments = new UnmodifiableListView(arguments);
-
-  /// Gets the parsed command-line option named [name].
-  operator [](String name) {
-    if (!_parser.options.containsKey(name)) {
-      throw new ArgumentError('Could not find an option named "$name".');
-    }
-
-    return _parser.options[name].getOrDefault(_parsed[name]);
-  }
-
-  /// Get the names of the available options as an [Iterable].
-  ///
-  /// This includes the options whose values were parsed or that have defaults.
-  /// Options that weren't present and have no default will be omitted.
-  Iterable<String> get options {
-    var result = new Set<String>.from(_parsed.keys);
-
-    // Include the options that have defaults.
-    _parser.options.forEach((name, option) {
-      if (option.defaultValue != null) result.add(name);
-    });
-
-    return result;
-  }
-
-  /// Returns `true` if the option with [name] was parsed from an actual
-  /// argument.
-  ///
-  /// Returns `false` if it wasn't provided and the default value or no default
-  /// value would be used instead.
-  bool wasParsed(String name) {
-    var option = _parser.options[name];
-    if (option == null) {
-      throw new ArgumentError('Could not find an option named "$name".');
-    }
-
-    return _parsed.containsKey(name);
-  }
-}
diff --git a/packages/args/lib/src/help_command.dart b/packages/args/lib/src/help_command.dart
deleted file mode 100644
index 104327c..0000000
--- a/packages/args/lib/src/help_command.dart
+++ /dev/null
@@ -1,52 +0,0 @@
-// 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.
-
-import '../command_runner.dart';
-
-/// The built-in help command that's added to every [CommandRunner].
-///
-/// This command displays help information for the various subcommands.
-class HelpCommand<T> extends Command<T> {
-  final name = "help";
-  String get description =>
-      "Display help information for ${runner.executableName}.";
-  String get invocation => "${runner.executableName} help [command]";
-
-  T run() {
-    // Show the default help if no command was specified.
-    if (argResults.rest.isEmpty) {
-      runner.printUsage();
-      return null;
-    }
-
-    // Walk the command tree to show help for the selected command or
-    // subcommand.
-    var commands = runner.commands;
-    Command command;
-    var commandString = runner.executableName;
-
-    for (var name in argResults.rest) {
-      if (commands.isEmpty) {
-        command.usageException(
-            'Command "$commandString" does not expect a subcommand.');
-      }
-
-      if (commands[name] == null) {
-        if (command == null) {
-          runner.usageException('Could not find a command named "$name".');
-        }
-
-        command.usageException(
-            'Could not find a subcommand named "$name" for "$commandString".');
-      }
-
-      command = commands[name];
-      commands = command.subcommands;
-      commandString += " $name";
-    }
-
-    command.printUsage();
-    return null;
-  }
-}
diff --git a/packages/args/lib/src/option.dart b/packages/args/lib/src/option.dart
deleted file mode 100644
index a37193f..0000000
--- a/packages/args/lib/src/option.dart
+++ /dev/null
@@ -1,132 +0,0 @@
-// 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.
-
-import 'dart:collection';
-
-/// Creates a new [Option].
-///
-/// Since [Option] doesn't have a public constructor, this lets [ArgParser]
-/// get to it. This function isn't exported to the public API of the package.
-Option newOption(String name, String abbreviation, String help,
-    String valueHelp, List<String> allowed, Map<String, String> allowedHelp,
-    defaultValue, Function callback, OptionType type,
-    {bool negatable, bool splitCommas, bool hide: false}) {
-  return new Option._(name, abbreviation, help, valueHelp, allowed, allowedHelp,
-      defaultValue, callback, type, negatable: negatable,
-      splitCommas: splitCommas, hide: hide);
-}
-
-/// 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 String valueHelp;
-  final Map<String, String> allowedHelp;
-  final OptionType type;
-  final bool negatable;
-  final bool splitCommas;
-  final bool hide;
-
-  /// Whether the option is boolean-valued flag.
-  bool get isFlag => type == OptionType.FLAG;
-
-  /// Whether the option takes a single value.
-  bool get isSingle => type == OptionType.SINGLE;
-
-  /// Whether the option allows multiple values.
-  bool get isMultiple => type == OptionType.MULTIPLE;
-
-  Option._(this.name, this.abbreviation, this.help, this.valueHelp,
-      List<String> allowed, Map<String, String> allowedHelp, this.defaultValue,
-      this.callback, OptionType type, {this.negatable, bool splitCommas,
-      this.hide: false})
-      : this.allowed = allowed == null
-          ? null
-          : new UnmodifiableListView(allowed),
-        this.allowedHelp = allowedHelp == null
-            ? null
-            : new UnmodifiableMapView(allowedHelp),
-        this.type = type,
-        // If the user doesn't specify [splitCommas], it defaults to true for
-        // multiple options.
-        this.splitCommas = splitCommas == null
-            ? type == OptionType.MULTIPLE
-            : splitCommas {
-    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.');
-      }
-    }
-  }
-
-  /// Returns [value] if non-`null`, otherwise returns the default value for
-  /// this option.
-  ///
-  /// For single-valued options, it will be [defaultValue] if set or `null`
-  /// otherwise. For multiple-valued options, it will be an empty list or a
-  /// list containing [defaultValue] if set.
-  dynamic getOrDefault(value) {
-    if (value != null) return value;
-
-    if (!isMultiple) return defaultValue;
-    if (defaultValue != null) return [defaultValue];
-    return [];
-  }
-
-  static final _invalidChars = new RegExp(r'''[ \t\r\n"'\\/]''');
-}
-
-/// What kinds of values an option accepts.
-class OptionType {
-  /// An option that can only be `true` or `false`.
-  ///
-  /// The presence of the option name itself in the argument list means `true`.
-  static const FLAG = const OptionType._("OptionType.FLAG");
-
-  /// An option that takes a single value.
-  ///
-  /// Examples:
-  ///
-  ///     --mode debug
-  ///     -mdebug
-  ///     --mode=debug
-  ///
-  /// If the option is passed more than once, the last one wins.
-  static const SINGLE = const OptionType._("OptionType.SINGLE");
-
-  /// An option that allows multiple values.
-  ///
-  /// Example:
-  ///
-  ///     --output text --output xml
-  ///
-  /// In the parsed [ArgResults], a multiple-valued option will always return
-  /// a list, even if one or no values were passed.
-  static const MULTIPLE = const OptionType._("OptionType.MULTIPLE");
-
-  final String name;
-
-  const OptionType._(this.name);
-}
diff --git a/packages/args/lib/src/parser.dart b/packages/args/lib/src/parser.dart
deleted file mode 100644
index 64374fc..0000000
--- a/packages/args/lib/src/parser.dart
+++ /dev/null
@@ -1,297 +0,0 @@
-// 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.
-
-import 'arg_parser.dart';
-import 'arg_parser_exception.dart';
-import 'arg_results.dart';
-import 'option.dart';
-
-final _SOLO_OPT = new RegExp(r'^-([a-zA-Z0-9])$');
-final _ABBR_OPT = new RegExp(r'^-([a-zA-Z0-9]+)(.*)$');
-final _LONG_OPT = new RegExp(r'^--([a-zA-Z\-_0-9]+)(=(.*))?$');
-
-/// The actual argument parsing class.
-///
-/// Unlike [ArgParser] which is really more an "arg grammar", this is the class
-/// that does the parsing and holds the mutable state required during a parse.
-class Parser {
-  /// If parser is parsing a command's options, this will be the name of the
-  /// command. For top-level results, this returns `null`.
-  final String commandName;
-
-  /// The parser for the supercommand of this command parser, or `null` if this
-  /// is the top-level parser.
-  final Parser parent;
-
-  /// The grammar being parsed.
-  final ArgParser grammar;
-
-  /// The arguments being parsed.
-  final List<String> args;
-
-  /// The remaining non-option, non-command arguments.
-  final rest = <String>[];
-
-  /// The accumulated parsed options.
-  final Map<String, dynamic> results = <String, dynamic>{};
-
-  Parser(this.commandName, this.grammar, this.args,
-      [this.parent, List<String> rest]) {
-    if (rest != null) this.rest.addAll(rest);
-  }
-
-  /// The current argument being parsed.
-  String get current => args[0];
-
-  /// Parses the arguments. This can only be called once.
-  ArgResults parse() {
-    var arguments = args.toList();
-    var commandResults = null;
-
-    // Parse the args.
-    while (args.length > 0) {
-      if (current == '--') {
-        // Reached the argument terminator, so stop here.
-        args.removeAt(0);
-        break;
-      }
-
-      // Try to parse the current argument as a command. This happens before
-      // options so that commands can have option-like names.
-      var command = grammar.commands[current];
-      if (command != null) {
-        validate(rest.isEmpty, 'Cannot specify arguments before a command.');
-        var commandName = args.removeAt(0);
-        var commandParser = new Parser(commandName, command, args, this, rest);
-
-        try {
-          commandResults = commandParser.parse();
-        } on ArgParserException catch (error) {
-          if (commandName == null) rethrow;
-          throw new ArgParserException(
-              error.message,
-              [commandName]..addAll(error.commands));
-        }
-
-        // All remaining arguments were passed to command so clear them here.
-        rest.clear();
-        break;
-      }
-
-      // Try to parse the current argument as an option. Note that the order
-      // here matters.
-      if (parseSoloOption()) continue;
-      if (parseAbbreviation(this)) continue;
-      if (parseLongOption()) continue;
-
-      // This argument is neither option nor command, so stop parsing unless
-      // the [allowTrailingOptions] option is set.
-      if (!grammar.allowTrailingOptions) break;
-      rest.add(args.removeAt(0));
-    }
-
-    // Invoke the callbacks.
-    grammar.options.forEach((name, option) {
-      if (option.callback == null) return;
-      option.callback(option.getOrDefault(results[name]));
-    });
-
-    // Add in the leftover arguments we didn't parse to the innermost command.
-    rest.addAll(args);
-    args.clear();
-    return newArgResults(
-        grammar, results, commandName, commandResults, rest, arguments);
-  }
-
-  /// Pulls the value for [option] from the second argument in [args].
-  ///
-  /// Validates that there is a valid value there.
-  void readNextArgAsValue(Option option) {
-    // Take the option argument from the next command line arg.
-    validate(args.length > 0, 'Missing argument for "${option.name}".');
-
-    setOption(results, option, current);
-    args.removeAt(0);
-  }
-
-  /// Tries to parse the current argument as a "solo" option, which is a single
-  /// hyphen followed by a single letter.
-  ///
-  /// We treat this differently than collapsed abbreviations (like "-abc") to
-  /// handle the possible value that may follow it.
-  bool parseSoloOption() {
-    var soloOpt = _SOLO_OPT.firstMatch(current);
-    if (soloOpt == null) return false;
-
-    var option = grammar.findByAbbreviation(soloOpt[1]);
-    if (option == null) {
-      // Walk up to the parent command if possible.
-      validate(
-          parent != null, 'Could not find an option or flag "-${soloOpt[1]}".');
-      return parent.parseSoloOption();
-    }
-
-    args.removeAt(0);
-
-    if (option.isFlag) {
-      setFlag(results, option, true);
-    } else {
-      readNextArgAsValue(option);
-    }
-
-    return true;
-  }
-
-  /// Tries to parse the current argument as a series of collapsed abbreviations
-  /// (like "-abc") or a single abbreviation with the value directly attached
-  /// to it (like "-mrelease").
-  bool parseAbbreviation(Parser innermostCommand) {
-    var abbrOpt = _ABBR_OPT.firstMatch(current);
-    if (abbrOpt == null) return false;
-
-    // If the first character is the abbreviation for a non-flag option, then
-    // the rest is the value.
-    var c = abbrOpt[1].substring(0, 1);
-    var first = grammar.findByAbbreviation(c);
-    if (first == null) {
-      // Walk up to the parent command if possible.
-      validate(
-          parent != null, 'Could not find an option with short name "-$c".');
-      return parent.parseAbbreviation(innermostCommand);
-    } else if (!first.isFlag) {
-      // The first character is a non-flag option, so the rest must be the
-      // value.
-      var value = '${abbrOpt[1].substring(1)}${abbrOpt[2]}';
-      setOption(results, first, value);
-    } else {
-      // If we got some non-flag characters, then it must be a value, but
-      // if we got here, it's a flag, which is wrong.
-      validate(abbrOpt[2] == '',
-          'Option "-$c" is a flag and cannot handle value '
-          '"${abbrOpt[1].substring(1)}${abbrOpt[2]}".');
-
-      // Not an option, so all characters should be flags.
-      // We use "innermostCommand" here so that if a parent command parses the
-      // *first* letter, subcommands can still be found to parse the other
-      // letters.
-      for (var i = 0; i < abbrOpt[1].length; i++) {
-        var c = abbrOpt[1].substring(i, i + 1);
-        innermostCommand.parseShortFlag(c);
-      }
-    }
-
-    args.removeAt(0);
-    return true;
-  }
-
-  void parseShortFlag(String c) {
-    var option = grammar.findByAbbreviation(c);
-    if (option == null) {
-      // Walk up to the parent command if possible.
-      validate(
-          parent != null, 'Could not find an option with short name "-$c".');
-      parent.parseShortFlag(c);
-      return;
-    }
-
-    // In a list of short options, only the first can be a non-flag. If
-    // we get here we've checked that already.
-    validate(
-        option.isFlag, 'Option "-$c" must be a flag to be in a collapsed "-".');
-
-    setFlag(results, option, true);
-  }
-
-  /// Tries to parse the current argument as a long-form named option, which
-  /// may include a value like "--mode=release" or "--mode release".
-  bool parseLongOption() {
-    var longOpt = _LONG_OPT.firstMatch(current);
-    if (longOpt == null) return false;
-
-    var name = longOpt[1];
-    var option = grammar.options[name];
-    if (option != null) {
-      args.removeAt(0);
-      if (option.isFlag) {
-        validate(longOpt[3] == null,
-            'Flag option "$name" should not be given a value.');
-
-        setFlag(results, option, true);
-      } else if (longOpt[3] != null) {
-        // We have a value like --foo=bar.
-        setOption(results, option, longOpt[3]);
-      } else {
-        // Option like --foo, so look for the value as the next arg.
-        readNextArgAsValue(option);
-      }
-    } else if (name.startsWith('no-')) {
-      // See if it's a negated flag.
-      name = name.substring('no-'.length);
-      option = grammar.options[name];
-      if (option == null) {
-        // Walk up to the parent command if possible.
-        validate(parent != null, 'Could not find an option named "$name".');
-        return parent.parseLongOption();
-      }
-
-      args.removeAt(0);
-      validate(option.isFlag, 'Cannot negate non-flag option "$name".');
-      validate(option.negatable, 'Cannot negate option "$name".');
-
-      setFlag(results, option, false);
-    } else {
-      // Walk up to the parent command if possible.
-      validate(parent != null, 'Could not find an option named "$name".');
-      return parent.parseLongOption();
-    }
-
-    return true;
-  }
-
-  /// Called during parsing to validate the arguments.
-  ///
-  /// Throws an [ArgParserException] if [condition] is `false`.
-  void validate(bool condition, String message) {
-    if (!condition) throw new ArgParserException(message);
-  }
-
-  /// Validates and stores [value] as the value for [option], which must not be
-  /// a flag.
-  void setOption(Map results, Option option, String value) {
-    assert(!option.isFlag);
-
-    if (!option.isMultiple) {
-      _validateAllowed(option, value);
-      results[option.name] = value;
-      return;
-    }
-
-    var list = results.putIfAbsent(option.name, () => <String>[]);
-
-    if (option.splitCommas) {
-      for (var element in value.split(",")) {
-        _validateAllowed(option, element);
-        list.add(element);
-      }
-    } else {
-      _validateAllowed(option, value);
-      list.add(value);
-    }
-  }
-
-  /// Validates and stores [value] as the value for [option], which must be a
-  /// flag.
-  void setFlag(Map results, Option option, bool value) {
-    assert(option.isFlag);
-    results[option.name] = value;
-  }
-
-  /// Validates that [value] is allowed as a value of [option].
-  void _validateAllowed(Option option, String value) {
-    if (option.allowed == null) return;
-
-    validate(option.allowed.contains(value),
-        '"$value" is not an allowed value for option "${option.name}".');
-  }
-}
diff --git a/packages/args/lib/src/usage.dart b/packages/args/lib/src/usage.dart
deleted file mode 100644
index 2b40332..0000000
--- a/packages/args/lib/src/usage.dart
+++ /dev/null
@@ -1,251 +0,0 @@
-// 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.
-
-import 'dart:math';
-
-import '../args.dart';
-
-/// Takes an [ArgParser] and generates a string of usage (i.e. help) text for
-/// its defined options.
-///
-/// Internally, it works like a tabular printer. The output is divided into
-/// three horizontal columns, like so:
-///
-///     -h, --help  Prints the usage information
-///     |  |        |                                 |
-///
-/// It builds the usage text up one column at a time and handles padding with
-/// spaces and wrapping to the next line to keep the cells correctly lined up.
-class Usage {
-  static const NUM_COLUMNS = 3; // Abbreviation, long name, help.
-
-  /// A list of the [Option]s intermingled with [String] separators.
-  final List optionsAndSeparators;
-
-  /// The working buffer for the generated usage text.
-  StringBuffer buffer;
-
-  /// The column that the "cursor" is currently on.
-  ///
-  /// If the next call to [write()] is not for this column, it will correctly
-  /// handle advancing to the next column (and possibly the next row).
-  int currentColumn = 0;
-
-  /// The width in characters of each column.
-  List<int> columnWidths;
-
-  /// The number of sequential lines of text that have been written to the last
-  /// column (which shows help info).
-  ///
-  /// We track this so that help text that spans multiple lines can be padded
-  /// with a blank line after it for separation. Meanwhile, sequential options
-  /// with single-line help will be compacted next to each other.
-  int numHelpLines = 0;
-
-  /// How many newlines need to be rendered before the next bit of text can be
-  /// written.
-  ///
-  /// We do this lazily so that the last bit of usage doesn't have dangling
-  /// newlines. We only write newlines right *before* we write some real
-  /// content.
-  int newlinesNeeded = 0;
-
-  Usage(this.optionsAndSeparators);
-
-  /// Generates a string displaying usage information for the defined options.
-  /// This is basically the help text shown on the command line.
-  String generate() {
-    buffer = new StringBuffer();
-
-    calculateColumnWidths();
-
-    for (var optionOrSeparator in optionsAndSeparators) {
-      if (optionOrSeparator is String) {
-        // Ensure that there's always a blank line before a separator.
-        if (buffer.isNotEmpty) buffer.write("\n\n");
-        buffer.write(optionOrSeparator);
-        newlinesNeeded = 1;
-        continue;
-      }
-
-      var option = optionOrSeparator as Option;
-      if (option.hide) continue;
-
-      write(0, getAbbreviation(option));
-      write(1, getLongOption(option));
-
-      if (option.help != null) write(2, option.help);
-
-      if (option.allowedHelp != null) {
-        var allowedNames = option.allowedHelp.keys.toList(growable: false);
-        allowedNames.sort();
-        newline();
-        for (var name in allowedNames) {
-          write(1, getAllowedTitle(name));
-          write(2, option.allowedHelp[name]);
-        }
-        newline();
-      } else if (option.allowed != null) {
-        write(2, buildAllowedList(option));
-      } else if (option.defaultValue != null) {
-        if (option.isFlag && option.defaultValue == true) {
-          write(2, '(defaults to on)');
-        } else if (!option.isFlag) {
-          write(2, '(defaults to "${option.defaultValue}")');
-        }
-      }
-
-      // If any given option displays more than one line of text on the right
-      // column (i.e. help, default value, allowed options, etc.) then put a
-      // blank line after it. This gives space where it's useful while still
-      // keeping simple one-line options clumped together.
-      if (numHelpLines > 1) newline();
-    }
-
-    return buffer.toString();
-  }
-
-  String getAbbreviation(Option option) {
-    if (option.abbreviation != null) {
-      return '-${option.abbreviation}, ';
-    } else {
-      return '';
-    }
-  }
-
-  String getLongOption(Option option) {
-    var result;
-    if (option.negatable) {
-      result = '--[no-]${option.name}';
-    } else {
-      result = '--${option.name}';
-    }
-
-    if (option.valueHelp != null) result += "=<${option.valueHelp}>";
-
-    return result;
-  }
-
-  String getAllowedTitle(String allowed) {
-    return '      [$allowed]';
-  }
-
-  void calculateColumnWidths() {
-    int abbr = 0;
-    int title = 0;
-    for (var option in optionsAndSeparators) {
-      if (option is! Option) continue;
-      if (option.hide) continue;
-
-      // Make room in the first column if there are abbreviations.
-      abbr = max(abbr, getAbbreviation(option).length);
-
-      // Make room for the option.
-      title = max(title, getLongOption(option).length);
-
-      // Make room for the allowed help.
-      if (option.allowedHelp != null) {
-        for (var allowed in option.allowedHelp.keys) {
-          title = max(title, getAllowedTitle(allowed).length);
-        }
-      }
-    }
-
-    // Leave a gutter between the columns.
-    title += 4;
-    columnWidths = [abbr, title];
-  }
-
-  void newline() {
-    newlinesNeeded++;
-    currentColumn = 0;
-    numHelpLines = 0;
-  }
-
-  void write(int column, String text) {
-    var lines = text.split('\n');
-
-    // Strip leading and trailing empty lines.
-    while (lines.length > 0 && lines[0].trim() == '') {
-      lines.removeRange(0, 1);
-    }
-
-    while (lines.length > 0 && lines[lines.length - 1].trim() == '') {
-      lines.removeLast();
-    }
-
-    for (var line in lines) {
-      writeLine(column, line);
-    }
-  }
-
-  void writeLine(int column, String text) {
-    // Write any pending newlines.
-    while (newlinesNeeded > 0) {
-      buffer.write('\n');
-      newlinesNeeded--;
-    }
-
-    // Advance until we are at the right column (which may mean wrapping around
-    // to the next line.
-    while (currentColumn != column) {
-      if (currentColumn < NUM_COLUMNS - 1) {
-        buffer.write(padRight('', columnWidths[currentColumn]));
-      } else {
-        buffer.write('\n');
-      }
-      currentColumn = (currentColumn + 1) % NUM_COLUMNS;
-    }
-
-    if (column < columnWidths.length) {
-      // Fixed-size column, so pad it.
-      buffer.write(padRight(text, columnWidths[column]));
-    } else {
-      // The last column, so just write it.
-      buffer.write(text);
-    }
-
-    // Advance to the next column.
-    currentColumn = (currentColumn + 1) % NUM_COLUMNS;
-
-    // If we reached the last column, we need to wrap to the next line.
-    if (column == NUM_COLUMNS - 1) newlinesNeeded++;
-
-    // Keep track of how many consecutive lines we've written in the last
-    // column.
-    if (column == NUM_COLUMNS - 1) {
-      numHelpLines++;
-    } else {
-      numHelpLines = 0;
-    }
-  }
-
-  String buildAllowedList(Option option) {
-    var allowedBuffer = new StringBuffer();
-    allowedBuffer.write('[');
-    bool first = true;
-    for (var allowed in option.allowed) {
-      if (!first) allowedBuffer.write(', ');
-      allowedBuffer.write(allowed);
-      if (allowed == option.defaultValue) {
-        allowedBuffer.write(' (default)');
-      }
-      first = false;
-    }
-    allowedBuffer.write(']');
-    return allowedBuffer.toString();
-  }
-}
-
-/// Pads [source] to [length] by adding spaces at the end.
-String padRight(String source, int length) {
-  final result = new StringBuffer();
-  result.write(source);
-
-  while (result.length < length) {
-    result.write(' ');
-  }
-
-  return result.toString();
-}
diff --git a/packages/args/lib/src/usage_exception.dart b/packages/args/lib/src/usage_exception.dart
deleted file mode 100644
index 2d8e112..0000000
--- a/packages/args/lib/src/usage_exception.dart
+++ /dev/null
@@ -1,12 +0,0 @@
-// 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.
-
-class UsageException implements Exception {
-  final String message;
-  final String usage;
-
-  UsageException(this.message, this.usage);
-
-  String toString() => "$message\n\n$usage";
-}
diff --git a/packages/args/lib/src/utils.dart b/packages/args/lib/src/utils.dart
deleted file mode 100644
index 2469fac..0000000
--- a/packages/args/lib/src/utils.dart
+++ /dev/null
@@ -1,7 +0,0 @@
-// 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.
-
-/// Pads [source] to [length] by adding spaces at the end.
-String padRight(String source, int length) =>
-    source + ' ' * (length - source.length);
diff --git a/packages/args/pubspec.yaml b/packages/args/pubspec.yaml
deleted file mode 100644
index e1a6b05..0000000
--- a/packages/args/pubspec.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-name: args
-version: 0.13.7
-author: "Dart Team <misc@dartlang.org>"
-homepage: https://github.com/dart-lang/args
-description: >
- Library for defining parsers for parsing raw command-line arguments into
- a set of options and values using GNU and POSIX style options.
-
-dev_dependencies:
-  test: ">=0.12.0 <0.13.0"
-environment:
-  sdk: ">=1.4.0 <2.0.0"
diff --git a/packages/args/test/args_test.dart b/packages/args/test/args_test.dart
deleted file mode 100644
index 4553b30..0000000
--- a/packages/args/test/args_test.dart
+++ /dev/null
@@ -1,318 +0,0 @@
-// 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:test/test.dart';
-import 'package:args/args.dart';
-import 'utils.dart';
-
-void main() {
-  group('ArgParser.addFlag()', () {
-    test('throws ArgumentError if the flag already exists', () {
-      var parser = new ArgParser();
-      parser.addFlag('foo');
-      throwsIllegalArg(() => parser.addFlag('foo'));
-    });
-
-    test('throws ArgumentError if the option already exists', () {
-      var parser = new ArgParser();
-      parser.addOption('foo');
-      throwsIllegalArg(() => parser.addFlag('foo'));
-    });
-
-    test('throws ArgumentError if the abbreviation exists', () {
-      var parser = new ArgParser();
-      parser.addFlag('foo', abbr: 'f');
-      throwsIllegalArg(() => parser.addFlag('flummox', abbr: 'f'));
-    });
-
-    test('throws ArgumentError if the abbreviation is longer '
-        'than one character', () {
-      var parser = new ArgParser();
-      throwsIllegalArg(() => parser.addFlag('flummox', abbr: 'flu'));
-    });
-
-    test('throws ArgumentError if a flag name is invalid', () {
-      var parser = new ArgParser();
-
-      for (var name in _INVALID_OPTIONS) {
-        var reason = '${Error.safeToString(name)} is not valid';
-        throwsIllegalArg(() => parser.addFlag(name), reason: reason);
-      }
-    });
-
-    test('accepts valid flag names', () {
-      var parser = new ArgParser();
-
-      for (var name in _VALID_OPTIONS) {
-        var reason = '${Error.safeToString(name)} is valid';
-        expect(() => parser.addFlag(name), returnsNormally, reason: reason);
-      }
-    });
-  });
-
-  group('ArgParser.addOption()', () {
-    test('throws ArgumentError if the flag already exists', () {
-      var parser = new ArgParser();
-      parser.addFlag('foo');
-      throwsIllegalArg(() => parser.addOption('foo'));
-    });
-
-    test('throws ArgumentError if the option already exists', () {
-      var parser = new ArgParser();
-      parser.addOption('foo');
-      throwsIllegalArg(() => parser.addOption('foo'));
-    });
-
-    test('throws ArgumentError if the abbreviation exists', () {
-      var parser = new ArgParser();
-      parser.addFlag('foo', abbr: 'f');
-      throwsIllegalArg(() => parser.addOption('flummox', abbr: 'f'));
-    });
-
-    test('throws ArgumentError if the abbreviation is longer '
-        'than one character', () {
-      var parser = new ArgParser();
-      throwsIllegalArg(() => parser.addOption('flummox', abbr: 'flu'));
-    });
-
-    test('throws ArgumentError if the abbreviation is empty', () {
-      var parser = new ArgParser();
-      throwsIllegalArg(() => parser.addOption('flummox', abbr: ''));
-    });
-
-    test('throws ArgumentError if the abbreviation is an invalid value', () {
-      var parser = new ArgParser();
-      for (var name in _INVALID_OPTIONS.where((v) => v != null)) {
-        throwsIllegalArg(() => parser.addOption('flummox', abbr: name));
-      }
-    });
-
-    test('throws ArgumentError if the abbreviation is a dash', () {
-      var parser = new ArgParser();
-      throwsIllegalArg(() => parser.addOption('flummox', abbr: '-'));
-    });
-
-    test('allows explict null value for "abbr"', () {
-      var parser = new ArgParser();
-      expect(() => parser.addOption('flummox', abbr: null), returnsNormally);
-    });
-
-    test('throws ArgumentError if an option name is invalid', () {
-      var parser = new ArgParser();
-
-      for (var name in _INVALID_OPTIONS) {
-        var reason = '${Error.safeToString(name)} is not valid';
-        throwsIllegalArg(() => parser.addOption(name), reason: reason);
-      }
-    });
-
-    test('throws ArgumentError if splitCommas is passed with allowMultiple: '
-        'false', () {
-      var parser = new ArgParser();
-      throwsIllegalArg(() => parser.addOption('flummox', splitCommas: true));
-      throwsIllegalArg(() => parser.addOption('flummox', splitCommas: false));
-    });
-
-    test('accepts valid option names', () {
-      var parser = new ArgParser();
-
-      for (var name in _VALID_OPTIONS) {
-        var reason = '${Error.safeToString(name)} is valid';
-        expect(() => parser.addOption(name), returnsNormally, reason: reason);
-      }
-    });
-  });
-
-  group('ArgParser.getDefault()', () {
-    test('returns the default value for an option', () {
-      var parser = new ArgParser();
-      parser.addOption('mode', defaultsTo: 'debug');
-      expect(parser.getDefault('mode'), 'debug');
-    });
-
-    test('throws if the option is unknown', () {
-      var parser = new ArgParser();
-      parser.addOption('mode', defaultsTo: 'debug');
-      throwsIllegalArg(() => parser.getDefault('undefined'));
-    });
-  });
-
-  group('ArgParser.commands', () {
-    test('returns an empty map if there are no commands', () {
-      var parser = new ArgParser();
-      expect(parser.commands, isEmpty);
-    });
-
-    test('returns the commands that were added', () {
-      var parser = new ArgParser();
-      parser.addCommand('hide');
-      parser.addCommand('seek');
-      expect(parser.commands, hasLength(2));
-      expect(parser.commands['hide'], isNotNull);
-      expect(parser.commands['seek'], isNotNull);
-    });
-
-    test('iterates over the commands in the order they were added', () {
-      var parser = new ArgParser();
-      parser.addCommand('a');
-      parser.addCommand('d');
-      parser.addCommand('b');
-      parser.addCommand('c');
-      expect(parser.commands.keys, equals(['a', 'd', 'b', 'c']));
-    });
-  });
-
-  group('ArgParser.options', () {
-    test('returns an empty map if there are no options', () {
-      var parser = new ArgParser();
-      expect(parser.options, isEmpty);
-    });
-
-    test('returns the options that were added', () {
-      var parser = new ArgParser();
-      parser.addFlag('hide');
-      parser.addOption('seek');
-      expect(parser.options, hasLength(2));
-      expect(parser.options['hide'], isNotNull);
-      expect(parser.options['seek'], isNotNull);
-    });
-
-    test('iterates over the options in the order they were added', () {
-      var parser = new ArgParser();
-      parser.addFlag('a');
-      parser.addOption('d');
-      parser.addFlag('b');
-      parser.addOption('c');
-      expect(parser.options.keys, equals(['a', 'd', 'b', 'c']));
-    });
-  });
-
-  group('ArgResults', () {
-    group('options', () {
-      test('returns the provided options', () {
-        var parser = new ArgParser();
-        parser.addFlag('woof');
-        parser.addOption('meow');
-
-        parser.addOption('missing-option');
-        parser.addFlag('missing-flag', defaultsTo: null);
-
-        var args = parser.parse(['--woof', '--meow', 'kitty']);
-        expect(args.options, hasLength(2));
-        expect(args.options, contains('woof'));
-        expect(args.options, contains('meow'));
-      });
-
-      test('includes defaulted options', () {
-        var parser = new ArgParser();
-        parser.addFlag('woof', defaultsTo: false);
-        parser.addOption('meow', defaultsTo: 'kitty');
-
-        // Flags normally have a default value.
-        parser.addFlag('moo');
-
-        parser.addOption('missing-option');
-        parser.addFlag('missing-flag', defaultsTo: null);
-
-        var args = parser.parse([]);
-        expect(args.options, hasLength(3));
-        expect(args.options, contains('woof'));
-        expect(args.options, contains('meow'));
-        expect(args.options, contains('moo'));
-      });
-    });
-
-    test('[] throws if the name is not an option', () {
-      var results = new ArgParser().parse([]);
-      throwsIllegalArg(() => results['unknown']);
-    });
-
-    test('rest cannot be modified', () {
-      var results = new ArgParser().parse([]);
-      expect(() => results.rest.add('oops'), throwsUnsupportedError);
-    });
-
-    test('.arguments returns the original argument list', () {
-      var parser = new ArgParser();
-      parser.addFlag('foo');
-
-      var results = parser.parse(['--foo']);
-      expect(results.arguments, equals(['--foo']));
-    });
-
-    group('.wasParsed()', () {
-      test('throws if the name is not an option', () {
-        var results = new ArgParser().parse([]);
-        throwsIllegalArg(() => results.wasParsed('unknown'));
-      });
-
-      test('returns true for parsed options', () {
-        var parser = new ArgParser();
-        parser.addFlag('fast');
-        parser.addFlag('verbose');
-        parser.addOption('mode');
-        parser.addOption('output');
-
-        var results = parser.parse(['--fast', '--mode=debug']);
-
-        expect(results.wasParsed('fast'), isTrue);
-        expect(results.wasParsed('verbose'), isFalse);
-        expect(results.wasParsed('mode'), isTrue);
-        expect(results.wasParsed('output'), isFalse);
-      });
-    });
-  });
-
-  group('Option', () {
-    test('.getOrDefault() returns a type-specific default value', () {
-      var parser = new ArgParser();
-      parser.addFlag('flag-no', defaultsTo: null);
-      parser.addFlag('flag-def', defaultsTo: true);
-      parser.addOption('single-no');
-      parser.addOption('single-def', defaultsTo: 'def');
-      parser.addOption('multi-no', allowMultiple: true);
-      parser.addOption('multi-def', allowMultiple: true, defaultsTo: 'def');
-
-      expect(parser.options['flag-no'].getOrDefault(null), equals(null));
-      expect(parser.options['flag-no'].getOrDefault(false), equals(false));
-      expect(parser.options['flag-def'].getOrDefault(null), equals(true));
-      expect(parser.options['flag-def'].getOrDefault(false), equals(false));
-      expect(parser.options['single-no'].getOrDefault(null), equals(null));
-      expect(parser.options['single-no'].getOrDefault('v'), equals('v'));
-      expect(parser.options['single-def'].getOrDefault(null), equals('def'));
-      expect(parser.options['single-def'].getOrDefault('v'), equals('v'));
-      expect(parser.options['multi-no'].getOrDefault(null), equals([]));
-      expect(parser.options['multi-no'].getOrDefault(['v']), equals(['v']));
-      expect(parser.options['multi-def'].getOrDefault(null), equals(['def']));
-      expect(parser.options['multi-def'].getOrDefault(['v']), equals(['v']));
-    });
-  });
-}
-
-const _INVALID_OPTIONS = const [
-  ' ',
-  '',
-  '-',
-  '--',
-  '--foo',
-  ' with space',
-  'with\ttab',
-  'with\rcarriage\rreturn',
-  'with\nline\nfeed',
-  "'singlequotes'",
-  '"doublequotes"',
-  'back\\slash',
-  'forward/slash'
-];
-
-const _VALID_OPTIONS = const [
-  'a', // One character.
-  'contains-dash',
-  'contains_underscore',
-  'ends-with-dash-',
-  'contains--doubledash--',
-  '1starts-with-number',
-  'contains-a-1number',
-  'ends-with-a-number8'
-];
diff --git a/packages/args/test/command_parse_test.dart b/packages/args/test/command_parse_test.dart
deleted file mode 100644
index 269ed01..0000000
--- a/packages/args/test/command_parse_test.dart
+++ /dev/null
@@ -1,203 +0,0 @@
-// 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:test/test.dart';
-import 'package:args/args.dart';
-import 'utils.dart';
-
-void main() {
-  group('ArgParser.addCommand()', () {
-    test('creates a new ArgParser if none is given', () {
-      var parser = new ArgParser();
-      var command = parser.addCommand('install');
-      expect(parser.commands['install'], equals(command));
-      expect(command is ArgParser, isTrue);
-    });
-
-    test('uses the command parser if given one', () {
-      var parser = new ArgParser();
-      var command = new ArgParser();
-      var result = parser.addCommand('install', command);
-      expect(parser.commands['install'], equals(command));
-      expect(result, equals(command));
-    });
-
-    test('throws on a duplicate command name', () {
-      var parser = new ArgParser();
-      parser.addCommand('install');
-      throwsIllegalArg(() => parser.addCommand('install'));
-    });
-  });
-
-  group('ArgParser.parse()', () {
-    test('parses a command', () {
-      var parser = new ArgParser()..addCommand('install');
-
-      var args = parser.parse(['install']);
-
-      expect(args.command.name, equals('install'));
-      expect(args.rest, isEmpty);
-    });
-
-    test('parses a command option', () {
-      var parser = new ArgParser();
-      var command = parser.addCommand('install');
-      command.addOption('path');
-
-      var args = parser.parse(['install', '--path', 'some/path']);
-      expect(args.command['path'], equals('some/path'));
-    });
-
-    test('parses a parent solo option before the command', () {
-      var parser = new ArgParser()
-        ..addOption('mode', abbr: 'm')
-        ..addCommand('install');
-
-      var args = parser.parse(['-m', 'debug', 'install']);
-      expect(args['mode'], equals('debug'));
-      expect(args.command.name, equals('install'));
-    });
-
-    test('parses a parent solo option after the command', () {
-      var parser = new ArgParser()
-        ..addOption('mode', abbr: 'm')
-        ..addCommand('install');
-
-      var args = parser.parse(['install', '-m', 'debug']);
-      expect(args['mode'], equals('debug'));
-      expect(args.command.name, equals('install'));
-    });
-
-    test('parses a parent option before the command', () {
-      var parser = new ArgParser()
-        ..addFlag('verbose')
-        ..addCommand('install');
-
-      var args = parser.parse(['--verbose', 'install']);
-      expect(args['verbose'], isTrue);
-      expect(args.command.name, equals('install'));
-    });
-
-    test('parses a parent option after the command', () {
-      var parser = new ArgParser()
-        ..addFlag('verbose')
-        ..addCommand('install');
-
-      var args = parser.parse(['install', '--verbose']);
-      expect(args['verbose'], isTrue);
-      expect(args.command.name, equals('install'));
-    });
-
-    test('parses a parent negated option before the command', () {
-      var parser = new ArgParser()
-        ..addFlag('verbose', defaultsTo: true)
-        ..addCommand('install');
-
-      var args = parser.parse(['--no-verbose', 'install']);
-      expect(args['verbose'], isFalse);
-      expect(args.command.name, equals('install'));
-    });
-
-    test('parses a parent negated option after the command', () {
-      var parser = new ArgParser()
-        ..addFlag('verbose', defaultsTo: true)
-        ..addCommand('install');
-
-      var args = parser.parse(['install', '--no-verbose']);
-      expect(args['verbose'], isFalse);
-      expect(args.command.name, equals('install'));
-    });
-
-    test('parses a parent abbreviation before the command', () {
-      var parser = new ArgParser()
-        ..addFlag('debug', abbr: 'd')
-        ..addFlag('verbose', abbr: 'v')
-        ..addCommand('install');
-
-      var args = parser.parse(['-dv', 'install']);
-      expect(args['debug'], isTrue);
-      expect(args['verbose'], isTrue);
-      expect(args.command.name, equals('install'));
-    });
-
-    test('parses a parent abbreviation after the command', () {
-      var parser = new ArgParser()
-        ..addFlag('debug', abbr: 'd')
-        ..addFlag('verbose', abbr: 'v')
-        ..addCommand('install');
-
-      var args = parser.parse(['install', '-dv']);
-      expect(args['debug'], isTrue);
-      expect(args['verbose'], isTrue);
-      expect(args.command.name, equals('install'));
-    });
-
-    test('does not parse a solo command option before the command', () {
-      var parser = new ArgParser();
-      var command = parser.addCommand('install');
-      command.addOption('path', abbr: 'p');
-
-      throwsFormat(parser, ['-p', 'foo', 'install']);
-    });
-
-    test('does not parse a command option before the command', () {
-      var parser = new ArgParser();
-      var command = parser.addCommand('install');
-      command.addOption('path');
-
-      throwsFormat(parser, ['--path', 'foo', 'install']);
-    });
-
-    test('does not parse a command abbreviation before the command', () {
-      var parser = new ArgParser();
-      var command = parser.addCommand('install');
-      command.addFlag('debug', abbr: 'd');
-      command.addFlag('verbose', abbr: 'v');
-
-      throwsFormat(parser, ['-dv', 'install']);
-    });
-
-    test('assigns collapsed options to the proper command', () {
-      var parser = new ArgParser();
-      parser.addFlag('apple', abbr: 'a');
-      var command = parser.addCommand('cmd');
-      command.addFlag('banana', abbr: 'b');
-      var subcommand = command.addCommand('subcmd');
-      subcommand.addFlag('cherry', abbr: 'c');
-
-      var args = parser.parse(['cmd', 'subcmd', '-abc']);
-      expect(args['apple'], isTrue);
-      expect(args.command.name, equals('cmd'));
-      expect(args.command['banana'], isTrue);
-      expect(args.command.command.name, equals('subcmd'));
-      expect(args.command.command['cherry'], isTrue);
-    });
-
-    test('option is given to innermost command that can take it', () {
-      var parser = new ArgParser();
-      parser.addFlag('verbose');
-      parser.addCommand('cmd')
-        ..addFlag('verbose')
-        ..addCommand('subcmd');
-
-      var args = parser.parse(['cmd', 'subcmd', '--verbose']);
-      expect(args['verbose'], isFalse);
-      expect(args.command.name, equals('cmd'));
-      expect(args.command['verbose'], isTrue);
-      expect(args.command.command.name, equals('subcmd'));
-    });
-
-    test('remaining arguments are given to the innermost command', () {
-      var parser = new ArgParser();
-      parser.addCommand('cmd')..addCommand('subcmd');
-
-      var args = parser.parse(['cmd', 'subcmd', 'other', 'stuff']);
-      expect(args.command.name, equals('cmd'));
-      expect(args.rest, isEmpty);
-      expect(args.command.command.name, equals('subcmd'));
-      expect(args.command.rest, isEmpty);
-      expect(args.command.command.rest, equals(['other', 'stuff']));
-    });
-  });
-}
diff --git a/packages/args/test/command_runner_test.dart b/packages/args/test/command_runner_test.dart
deleted file mode 100644
index 9b0713a..0000000
--- a/packages/args/test/command_runner_test.dart
+++ /dev/null
@@ -1,375 +0,0 @@
-// 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.
-
-import 'package:args/command_runner.dart';
-import 'package:test/test.dart';
-
-import 'utils.dart';
-
-const _DEFAULT_USAGE = """
-Usage: test <command> [arguments]
-
-Global options:
--h, --help    Print this usage information.
-
-Available commands:
-  help   Display help information for test.
-
-Run "test help <command>" for more information about a command.""";
-
-void main() {
-  var runner;
-  setUp(() {
-    runner = new CommandRunner("test", "A test command runner.");
-  });
-
-  test(".invocation has a sane default", () {
-    expect(runner.invocation, equals("test <command> [arguments]"));
-  });
-
-  group(".usage", () {
-    test("returns the usage string", () {
-      expect(runner.usage, equals("""
-A test command runner.
-
-$_DEFAULT_USAGE"""));
-    });
-
-    test("contains custom commands", () {
-      runner.addCommand(new FooCommand());
-
-      expect(runner.usage, equals("""
-A test command runner.
-
-Usage: test <command> [arguments]
-
-Global options:
--h, --help    Print this usage information.
-
-Available commands:
-  foo    Set a value.
-  help   Display help information for test.
-
-Run "test help <command>" for more information about a command."""));
-    });
-
-    test("truncates newlines in command descriptions by default", () {
-      runner.addCommand(new MultilineCommand());
-
-      expect(runner.usage, equals("""
-A test command runner.
-
-Usage: test <command> [arguments]
-
-Global options:
--h, --help    Print this usage information.
-
-Available commands:
-  help        Display help information for test.
-  multiline   Multi
-
-Run "test help <command>" for more information about a command."""));
-    });
-
-    test("supports newlines in command summaries", () {
-      runner.addCommand(new MultilineSummaryCommand());
-
-      expect(runner.usage, equals("""
-A test command runner.
-
-Usage: test <command> [arguments]
-
-Global options:
--h, --help    Print this usage information.
-
-Available commands:
-  help        Display help information for test.
-  multiline   Multi
-              line.
-
-Run "test help <command>" for more information about a command."""));
-    });
-
-    test("contains custom options", () {
-      runner.argParser.addFlag("foo", help: "Do something.");
-
-      expect(runner.usage, equals("""
-A test command runner.
-
-Usage: test <command> [arguments]
-
-Global options:
--h, --help        Print this usage information.
-    --[no-]foo    Do something.
-
-Available commands:
-  help   Display help information for test.
-
-Run "test help <command>" for more information about a command."""));
-    });
-
-    test("doesn't print hidden commands", () {
-      runner.addCommand(new HiddenCommand());
-
-      expect(runner.usage, equals("""
-A test command runner.
-
-$_DEFAULT_USAGE"""));
-    });
-
-    test("doesn't print aliases", () {
-      runner.addCommand(new AliasedCommand());
-
-      expect(runner.usage, equals("""
-A test command runner.
-
-Usage: test <command> [arguments]
-
-Global options:
--h, --help    Print this usage information.
-
-Available commands:
-  aliased   Set a value.
-  help      Display help information for test.
-
-Run "test help <command>" for more information about a command."""));
-    });
-  });
-
-  test("usageException splits up the message and usage", () {
-    expect(() => runner.usageException("message"),
-        throwsUsageException("message", _DEFAULT_USAGE));
-  });
-
-  group("run()", () {
-    test("runs a command", () {
-      var command = new FooCommand();
-      runner.addCommand(command);
-
-      expect(runner.run(["foo"]).then((_) {
-        expect(command.hasRun, isTrue);
-      }), completes);
-    });
-
-    test("runs an asynchronous command", () {
-      var command = new AsyncCommand();
-      runner.addCommand(command);
-
-      expect(runner.run(["async"]).then((_) {
-        expect(command.hasRun, isTrue);
-      }), completes);
-    });
-
-    test("runs a command with a return value", () {
-      var runner = new CommandRunner<int>("test", "");
-      var command = new ValueCommand();
-      runner.addCommand(command);
-
-      expect(runner.run(["foo"]), completion(equals(12)));
-    });
-
-    test("runs a command with an asynchronous return value", () {
-      var runner = new CommandRunner<String>("test", "");
-      var command = new AsyncValueCommand();
-      runner.addCommand(command);
-
-      expect(runner.run(["foo"]), completion(equals("hi")));
-    });
-
-    test("runs a hidden comand", () {
-      var command = new HiddenCommand();
-      runner.addCommand(command);
-
-      expect(runner.run(["hidden"]).then((_) {
-        expect(command.hasRun, isTrue);
-      }), completes);
-    });
-
-    test("runs an aliased comand", () {
-      var command = new AliasedCommand();
-      runner.addCommand(command);
-
-      expect(runner.run(["als"]).then((_) {
-        expect(command.hasRun, isTrue);
-      }), completes);
-    });
-
-    test("runs a subcommand", () {
-      var command = new AsyncCommand();
-      runner.addCommand(new FooCommand()..addSubcommand(command));
-
-      expect(runner.run(["foo", "async"]).then((_) {
-        expect(command.hasRun, isTrue);
-      }), completes);
-    });
-
-    group("with --help", () {
-      test("with no command prints the usage", () {
-        expect(() => runner.run(["--help"]), prints("""
-A test command runner.
-
-$_DEFAULT_USAGE
-"""));
-      });
-
-      test("with a preceding command prints the usage for that command", () {
-        var command = new FooCommand();
-        runner.addCommand(command);
-
-        expect(() => runner.run(["foo", "--help"]), prints("""
-Set a value.
-
-Usage: test foo [arguments]
--h, --help    Print this usage information.
-
-Run "test help" to see global options.
-"""));
-      });
-
-      test("with a following command prints the usage for that command", () {
-        var command = new FooCommand();
-        runner.addCommand(command);
-
-        expect(() => runner.run(["--help", "foo"]), prints("""
-Set a value.
-
-Usage: test foo [arguments]
--h, --help    Print this usage information.
-
-Run "test help" to see global options.
-"""));
-      });
-    });
-
-    group("with help command", () {
-      test("with no command prints the usage", () {
-        expect(() => runner.run(["help"]), prints("""
-A test command runner.
-
-$_DEFAULT_USAGE
-"""));
-      });
-
-      test("with a command prints the usage for that command", () {
-        var command = new FooCommand();
-        runner.addCommand(command);
-
-        expect(() => runner.run(["help", "foo"]), prints("""
-Set a value.
-
-Usage: test foo [arguments]
--h, --help    Print this usage information.
-
-Run "test help" to see global options.
-"""));
-      });
-
-      test("prints its own usage", () {
-        expect(() => runner.run(["help", "help"]), prints("""
-Display help information for test.
-
-Usage: test help [command]
--h, --help    Print this usage information.
-
-Run "test help" to see global options.
-"""));
-      });
-    });
-
-    group("with an invalid argument", () {
-      test("at the root throws the root usage", () {
-        expect(runner.run(["--asdf"]), throwsUsageException(
-            'Could not find an option named "asdf".',
-            '$_DEFAULT_USAGE'));
-      });
-
-      test("for a command throws the command usage", () {
-        var command = new FooCommand();
-        runner.addCommand(command);
-
-        expect(runner.run(["foo", "--asdf"]), throwsUsageException(
-            'Could not find an option named "asdf".',
-            """
-Usage: test foo [arguments]
--h, --help    Print this usage information.
-
-Run "test help" to see global options."""));
-      });
-    });
-  });
-
-  group("with a footer", () {
-    setUp(() {
-      runner = new CommandRunnerWithFooter("test", "A test command runner.");
-    });
-
-    test("includes the footer in the usage string", () {
-      expect(runner.usage, equals("""
-A test command runner.
-
-$_DEFAULT_USAGE
-Also, footer!"""));
-    });
-
-    test("includes the footer in usage errors", () {
-      expect(runner.run(["--bad"]), throwsUsageException(
-          'Could not find an option named "bad".',
-          "$_DEFAULT_USAGE\nAlso, footer!"));
-    });
-  });
-
-  group("throws a useful error when", () {
-    test("arg parsing fails", () {
-      expect(runner.run(["--bad"]), throwsUsageException(
-          'Could not find an option named "bad".', _DEFAULT_USAGE));
-    });
-
-    test("a top-level command doesn't exist", () {
-      expect(runner.run(["bad"]), throwsUsageException(
-          'Could not find a command named "bad".', _DEFAULT_USAGE));
-    });
-
-    test("a subcommand doesn't exist", () {
-      runner.addCommand(new FooCommand()..addSubcommand(new AsyncCommand()));
-
-      expect(runner.run(["foo bad"]), throwsUsageException(
-          'Could not find a command named "foo bad".', """
-Usage: test <command> [arguments]
-
-Global options:
--h, --help    Print this usage information.
-
-Available commands:
-  foo    Set a value.
-  help   Display help information for test.
-
-Run "test help <command>" for more information about a command."""));
-    });
-
-    test("a subcommand wasn't passed", () {
-      runner.addCommand(new FooCommand()..addSubcommand(new AsyncCommand()));
-
-      expect(runner.run(["foo"]), throwsUsageException(
-          'Missing subcommand for "test foo".', """
-Usage: test foo <subcommand> [arguments]
--h, --help    Print this usage information.
-
-Available subcommands:
-  async   Set a value asynchronously.
-
-Run "test help" to see global options."""));
-    });
-
-    test("a command that doesn't take arguments was given them", () {
-      runner.addCommand(new FooCommand());
-
-      expect(runner.run(["foo", "bar"]), throwsUsageException(
-          'Command "foo" does not take any arguments.', """
-Usage: test foo [arguments]
--h, --help    Print this usage information.
-
-Run "test help" to see global options."""));
-    });
-  });
-}
diff --git a/packages/args/test/command_test.dart b/packages/args/test/command_test.dart
deleted file mode 100644
index cd32c63..0000000
--- a/packages/args/test/command_test.dart
+++ /dev/null
@@ -1,105 +0,0 @@
-// 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.
-
-import 'package:args/command_runner.dart';
-import 'package:test/test.dart';
-import 'utils.dart';
-
-void main() {
-  var foo;
-  setUp(() {
-    foo = new FooCommand();
-
-    // Make sure [Command.runner] is set up.
-    new CommandRunner("test", "A test command runner.").addCommand(foo);
-  });
-
-  group(".invocation has a sane default", () {
-    test("without subcommands", () {
-      expect(foo.invocation, equals("test foo [arguments]"));
-    });
-
-    test("with subcommands", () {
-      foo.addSubcommand(new AsyncCommand());
-      expect(foo.invocation, equals("test foo <subcommand> [arguments]"));
-    });
-
-    test("for a subcommand", () {
-      var async = new AsyncCommand();
-      foo.addSubcommand(async);
-
-      expect(async.invocation, equals("test foo async [arguments]"));
-    });
-  });
-
-  group(".usage", () {
-    test("returns the usage string", () {
-      expect(foo.usage, equals("""
-Set a value.
-
-Usage: test foo [arguments]
--h, --help    Print this usage information.
-
-Run "test help" to see global options."""));
-    });
-
-    test("contains custom options", () {
-      foo.argParser.addFlag("flag", help: "Do something.");
-
-      expect(foo.usage, equals("""
-Set a value.
-
-Usage: test foo [arguments]
--h, --help         Print this usage information.
-    --[no-]flag    Do something.
-
-Run "test help" to see global options."""));
-    });
-
-    test("doesn't print hidden subcommands", () {
-      foo.addSubcommand(new AsyncCommand());
-      foo.addSubcommand(new HiddenCommand());
-
-      expect(foo.usage, equals("""
-Set a value.
-
-Usage: test foo <subcommand> [arguments]
--h, --help    Print this usage information.
-
-Available subcommands:
-  async   Set a value asynchronously.
-
-Run "test help" to see global options."""));
-    });
-
-    test("doesn't print subcommand aliases", () {
-      foo.addSubcommand(new AliasedCommand());
-
-      expect(foo.usage, equals("""
-Set a value.
-
-Usage: test foo <subcommand> [arguments]
--h, --help    Print this usage information.
-
-Available subcommands:
-  aliased   Set a value.
-
-Run "test help" to see global options."""));
-    });
-  });
-
-  test("usageException splits up the message and usage", () {
-    expect(() => foo.usageException("message"),
-        throwsUsageException("message", """
-Usage: test foo [arguments]
--h, --help    Print this usage information.
-
-Run "test help" to see global options."""));
-  });
-
-  test("considers a command hidden if all its subcommands are hidden", () {
-    foo.addSubcommand(new HiddenCommand());
-    expect(foo.hidden, isTrue);
-  });
-}
diff --git a/packages/args/test/parse_test.dart b/packages/args/test/parse_test.dart
deleted file mode 100644
index 6ce8a19..0000000
--- a/packages/args/test/parse_test.dart
+++ /dev/null
@@ -1,509 +0,0 @@
-// 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:test/test.dart';
-import 'package:args/args.dart';
-import 'utils.dart';
-
-void main() {
-  group('ArgParser.parse()', () {
-    test('does not destructively modify the argument list', () {
-      var parser = new ArgParser();
-      parser.addFlag('verbose');
-
-      var args = ['--verbose'];
-      var results = parser.parse(args);
-      expect(args, equals(['--verbose']));
-      expect(results['verbose'], isTrue);
-    });
-
-    group('flags', () {
-      test('are true if present', () {
-        var parser = new ArgParser();
-        parser.addFlag('verbose');
-
-        var args = parser.parse(['--verbose']);
-        expect(args['verbose'], isTrue);
-      });
-
-      test('default if missing', () {
-        var parser = new ArgParser();
-        parser.addFlag('a', defaultsTo: true);
-        parser.addFlag('b', defaultsTo: false);
-
-        var args = parser.parse([]);
-        expect(args['a'], isTrue);
-        expect(args['b'], isFalse);
-      });
-
-      test('are false if missing with no default', () {
-        var parser = new ArgParser();
-        parser.addFlag('verbose');
-
-        var args = parser.parse([]);
-        expect(args['verbose'], isFalse);
-      });
-
-      test('throws if given a value', () {
-        var parser = new ArgParser();
-        parser.addFlag('verbose');
-
-        throwsFormat(parser, ['--verbose=true']);
-      });
-
-      test('are case-sensitive', () {
-        var parser = new ArgParser();
-        parser.addFlag('verbose');
-        parser.addFlag('Verbose');
-        var results = parser.parse(['--verbose']);
-        expect(results['verbose'], isTrue);
-        expect(results['Verbose'], isFalse);
-      });
-    });
-
-    group('flags negated with "no-"', () {
-      test('set the flag to false', () {
-        var parser = new ArgParser();
-        parser.addFlag('verbose');
-
-        var args = parser.parse(['--no-verbose']);
-        expect(args['verbose'], isFalse);
-      });
-
-      test('set the flag to true if the flag actually starts with "no-"', () {
-        var parser = new ArgParser();
-        parser.addFlag('no-body');
-
-        var args = parser.parse(['--no-body']);
-        expect(args['no-body'], isTrue);
-      });
-
-      test('are not preferred over a colliding one without', () {
-        var parser = new ArgParser();
-        parser.addFlag('no-strum');
-        parser.addFlag('strum');
-
-        var args = parser.parse(['--no-strum']);
-        expect(args['no-strum'], isTrue);
-        expect(args['strum'], isFalse);
-      });
-
-      test('fail for non-negatable flags', () {
-        var parser = new ArgParser();
-        parser.addFlag('strum', negatable: false);
-
-        throwsFormat(parser, ['--no-strum']);
-      });
-    });
-
-    group('callbacks', () {
-      test('for present flags are invoked with the value', () {
-        var a;
-        var parser = new ArgParser();
-        parser.addFlag('a', callback: (value) => a = value);
-
-        parser.parse(['--a']);
-        expect(a, isTrue);
-      });
-
-      test('for absent flags are invoked with the default value', () {
-        var a;
-        var parser = new ArgParser();
-        parser.addFlag('a', defaultsTo: false, callback: (value) => a = value);
-
-        parser.parse([]);
-        expect(a, isFalse);
-      });
-
-      test('are invoked even if the flag is not present', () {
-        var a = true;
-        var parser = new ArgParser();
-        parser.addFlag('a', callback: (value) => a = value);
-
-        parser.parse([]);
-        expect(a, isFalse);
-      });
-
-      test('for present options are invoked with the value', () {
-        var a;
-        var parser = new ArgParser();
-        parser.addOption('a', callback: (value) => a = value);
-
-        parser.parse(['--a=v']);
-        expect(a, equals('v'));
-      });
-
-      test('for absent options are invoked with the default value', () {
-        var a;
-        var parser = new ArgParser();
-        parser.addOption('a', defaultsTo: 'v', callback: (value) => a = value);
-
-        parser.parse([]);
-        expect(a, equals('v'));
-      });
-
-      test('are invoked even if the option is not present', () {
-        var a = 'not called';
-        var parser = new ArgParser();
-        parser.addOption('a', callback: (value) => a = value);
-
-        parser.parse([]);
-        expect(a, isNull);
-      });
-
-      test('for multiple present, allowMultiple, options are invoked with '
-          'value as a list', () {
-        var a;
-        var parser = new ArgParser();
-        parser.addOption('a',
-            allowMultiple: true, callback: (value) => a = value);
-
-        parser.parse(['--a=v', '--a=x']);
-        expect(a, equals(['v', 'x']));
-
-        // This reified type is important in strong mode so that people can
-        // safely write "as List<String>".
-        expect(a, new isInstanceOf<List<String>>());
-      });
-
-      test('for single present, allowMultiple, options are invoked with '
-          ' value as a single element list', () {
-        var a;
-        var parser = new ArgParser();
-        parser.addOption('a',
-            allowMultiple: true, callback: (value) => a = value);
-
-        parser.parse(['--a=v']);
-        expect(a, equals(['v']));
-      });
-
-      test('for absent, allowMultiple, options are invoked with default '
-          'value as a list.', () {
-        var a;
-        var parser = new ArgParser();
-        parser.addOption('a',
-            allowMultiple: true,
-            defaultsTo: 'v',
-            callback: (value) => a = value);
-
-        parser.parse([]);
-        expect(a, equals(['v']));
-      });
-
-      test('for absent, allowMultiple, options are invoked with value '
-          'as an empty list.', () {
-        var a;
-        var parser = new ArgParser();
-        parser.addOption('a',
-            allowMultiple: true, callback: (value) => a = value);
-
-        parser.parse([]);
-        expect(a, isEmpty);
-      });
-
-      test('allowMultiple parses comma-separated strings', () {
-        var a;
-        var parser = new ArgParser();
-        parser.addOption('a',
-            allowMultiple: true, callback: (value) => a = value);
-
-        parser.parse(['--a=v,w', '--a=x']);
-        expect(a, equals(['v', 'w', 'x']));
-      });
-
-      test("allowMultiple doesn't parses comma-separated strings with "
-          "splitCommas: false", () {
-        var a;
-        var parser = new ArgParser();
-        parser.addOption('a',
-            allowMultiple: true,
-            splitCommas: false,
-            callback: (value) => a = value);
-
-        parser.parse(['--a=v,w', '--a=x']);
-        expect(a, equals(['v,w', 'x']));
-      });
-
-      test('allowMultiple parses empty strings', () {
-        var a;
-        var parser = new ArgParser();
-        parser.addOption('a',
-            allowMultiple: true, callback: (value) => a = value);
-
-        parser.parse(['--a=,v', '--a=w,', '--a=,', '--a=x,,y', '--a', '']);
-        expect(a, equals(['', 'v', 'w', '', '', '', 'x', '', 'y', '']));
-      });
-
-      test('allowMultiple with allowed parses comma-separated strings', () {
-        var a;
-        var parser = new ArgParser();
-        parser.addOption('a',
-            allowMultiple: true,
-            allowed: ['v', 'w', 'x'],
-            callback: (value) => a = value);
-
-        parser.parse(['--a=v,w', '--a=x']);
-        expect(a, equals(['v', 'w', 'x']));
-      });
-    });
-
-    group('abbreviations', () {
-      test('are parsed with a preceding "-"', () {
-        var parser = new ArgParser();
-        parser.addFlag('arg', abbr: 'a');
-
-        var args = parser.parse(['-a']);
-        expect(args['arg'], isTrue);
-      });
-
-      test('can use multiple after a single "-"', () {
-        var parser = new ArgParser();
-        parser.addFlag('first', abbr: 'f');
-        parser.addFlag('second', abbr: 's');
-        parser.addFlag('third', abbr: 't');
-
-        var args = parser.parse(['-tf']);
-        expect(args['first'], isTrue);
-        expect(args['second'], isFalse);
-        expect(args['third'], isTrue);
-      });
-
-      test('can have multiple "-" args', () {
-        var parser = new ArgParser();
-        parser.addFlag('first', abbr: 'f');
-        parser.addFlag('second', abbr: 's');
-        parser.addFlag('third', abbr: 't');
-
-        var args = parser.parse(['-s', '-tf']);
-        expect(args['first'], isTrue);
-        expect(args['second'], isTrue);
-        expect(args['third'], isTrue);
-      });
-
-      test('can take arguments without a space separating', () {
-        var parser = new ArgParser();
-        parser.addOption('file', abbr: 'f');
-
-        var args = parser.parse(['-flip']);
-        expect(args['file'], equals('lip'));
-      });
-
-      test('can take arguments with a space separating', () {
-        var parser = new ArgParser();
-        parser.addOption('file', abbr: 'f');
-
-        var args = parser.parse(['-f', 'name']);
-        expect(args['file'], equals('name'));
-      });
-
-      test('allow non-option characters in the value', () {
-        var parser = new ArgParser();
-        parser.addOption('apple', abbr: 'a');
-
-        var args = parser.parse(['-ab?!c']);
-        expect(args['apple'], equals('b?!c'));
-      });
-
-      test('throw if unknown', () {
-        var parser = new ArgParser();
-        throwsFormat(parser, ['-f']);
-      });
-
-      test('throw if the value is missing', () {
-        var parser = new ArgParser();
-        parser.addOption('file', abbr: 'f');
-
-        throwsFormat(parser, ['-f']);
-      });
-
-      test('does not throw if the value looks like an option', () {
-        var parser = new ArgParser();
-        parser.addOption('file', abbr: 'f');
-        parser.addOption('other');
-
-        expect(parser.parse(['-f', '--other'])['file'], equals('--other'));
-        expect(parser.parse(['-f', '--unknown'])['file'], equals('--unknown'));
-        expect(parser.parse(['-f', '-abbr'])['file'], equals('-abbr'));
-        expect(parser.parse(['-f', '--'])['file'], equals('--'));
-      });
-
-      test('throw if the value is not allowed', () {
-        var parser = new ArgParser();
-        parser.addOption('mode', abbr: 'm', allowed: ['debug', 'release']);
-
-        throwsFormat(parser, ['-mprofile']);
-      });
-
-      test('throw if a comma-separated value is not allowed', () {
-        var parser = new ArgParser();
-        parser.addOption('mode', abbr: 'm', allowMultiple: true,
-            allowed: ['debug', 'release']);
-
-        throwsFormat(parser, ['-mdebug,profile']);
-      });
-
-      test('throw if any but the first is not a flag', () {
-        var parser = new ArgParser();
-        parser.addFlag('apple', abbr: 'a');
-        parser.addOption('banana', abbr: 'b'); // Takes an argument.
-        parser.addFlag('cherry', abbr: 'c');
-
-        throwsFormat(parser, ['-abc']);
-      });
-
-      test('throw if it has a value but the option is a flag', () {
-        var parser = new ArgParser();
-        parser.addFlag('apple', abbr: 'a');
-        parser.addFlag('banana', abbr: 'b');
-
-        // The '?!' means this can only be understood as '--apple b?!c'.
-        throwsFormat(parser, ['-ab?!c']);
-      });
-
-      test('are case-sensitive', () {
-        var parser = new ArgParser();
-        parser.addFlag('file', abbr: 'f');
-        parser.addFlag('force', abbr: 'F');
-        var results = parser.parse(['-f']);
-        expect(results['file'], isTrue);
-        expect(results['force'], isFalse);
-      });
-    });
-
-    group('options', () {
-      test('are parsed if present', () {
-        var parser = new ArgParser();
-        parser.addOption('mode');
-        var args = parser.parse(['--mode=release']);
-        expect(args['mode'], equals('release'));
-      });
-
-      test('are null if not present', () {
-        var parser = new ArgParser();
-        parser.addOption('mode');
-        var args = parser.parse([]);
-        expect(args['mode'], isNull);
-      });
-
-      test('default if missing', () {
-        var parser = new ArgParser();
-        parser.addOption('mode', defaultsTo: 'debug');
-        var args = parser.parse([]);
-        expect(args['mode'], equals('debug'));
-      });
-
-      test('allow the value to be separated by whitespace', () {
-        var parser = new ArgParser();
-        parser.addOption('mode');
-        var args = parser.parse(['--mode', 'release']);
-        expect(args['mode'], equals('release'));
-      });
-
-      test('throw if unknown', () {
-        var parser = new ArgParser();
-        throwsFormat(parser, ['--unknown']);
-        throwsFormat(parser, ['--nobody']); // Starts with "no".
-      });
-
-      test('throw if the arg does not include a value', () {
-        var parser = new ArgParser();
-        parser.addOption('mode');
-        throwsFormat(parser, ['--mode']);
-      });
-
-      test('do not throw if the value looks like an option', () {
-        var parser = new ArgParser();
-        parser.addOption('mode');
-        parser.addOption('other');
-
-        expect(parser.parse(['--mode', '--other'])['mode'], equals('--other'));
-        expect(parser.parse(['--mode', '--unknown'])['mode'],
-            equals('--unknown'));
-        expect(parser.parse(['--mode', '-abbr'])['mode'], equals('-abbr'));
-        expect(parser.parse(['--mode', '--'])['mode'], equals('--'));
-      });
-
-      test('do not throw if the value is in the allowed set', () {
-        var parser = new ArgParser();
-        parser.addOption('mode', allowed: ['debug', 'release']);
-        var args = parser.parse(['--mode=debug']);
-        expect(args['mode'], equals('debug'));
-      });
-
-      test('throw if the value is not in the allowed set', () {
-        var parser = new ArgParser();
-        parser.addOption('mode', allowed: ['debug', 'release']);
-        throwsFormat(parser, ['--mode=profile']);
-      });
-
-      test('returns last provided value', () {
-        var parser = new ArgParser();
-        parser.addOption('define');
-        var args = parser.parse(['--define=1', '--define=2']);
-        expect(args['define'], equals('2'));
-      });
-
-      test('returns a List if multi-valued', () {
-        var parser = new ArgParser();
-        parser.addOption('define', allowMultiple: true);
-        var args = parser.parse(['--define=1']);
-        expect(args['define'], equals(['1']));
-        args = parser.parse(['--define=1', '--define=2']);
-        expect(args['define'], equals(['1', '2']));
-      });
-
-      test('returns the default value for multi-valued arguments '
-          'if not explicitly set', () {
-        var parser = new ArgParser();
-        parser.addOption('define', defaultsTo: '0', allowMultiple: true);
-        var args = parser.parse(['']);
-        expect(args['define'], equals(['0']));
-      });
-
-      test('are case-sensitive', () {
-        var parser = new ArgParser();
-        parser.addOption('verbose', defaultsTo: 'no');
-        parser.addOption('Verbose', defaultsTo: 'no');
-        var results = parser.parse(['--verbose', 'chatty']);
-        expect(results['verbose'], equals('chatty'));
-        expect(results['Verbose'], equals('no'));
-      });
-    });
-
-    group('remaining args', () {
-      test('stops parsing args when a non-option-like arg is encountered', () {
-        var parser = new ArgParser();
-        parser.addFlag('woof');
-        parser.addOption('meow');
-        parser.addOption('tweet', defaultsTo: 'bird');
-
-        var results = parser.parse(['--woof', '--meow', 'v', 'not', 'option']);
-        expect(results['woof'], isTrue);
-        expect(results['meow'], equals('v'));
-        expect(results['tweet'], equals('bird'));
-        expect(results.rest, equals(['not', 'option']));
-      });
-
-      test('consumes "--" and stops', () {
-        var parser = new ArgParser();
-        parser.addFlag('woof', defaultsTo: false);
-        parser.addOption('meow', defaultsTo: 'kitty');
-
-        var results = parser.parse(['--woof', '--', '--meow']);
-        expect(results['woof'], isTrue);
-        expect(results['meow'], equals('kitty'));
-        expect(results.rest, equals(['--meow']));
-      });
-
-      test('leaves "--" if not the first non-option', () {
-        var parser = new ArgParser();
-        parser.addFlag('woof');
-
-        var results = parser.parse(['--woof', 'stop', '--', 'arg']);
-        expect(results['woof'], isTrue);
-        expect(results.rest, equals(['stop', '--', 'arg']));
-      });
-    });
-  });
-}
diff --git a/packages/args/test/trailing_options_test.dart b/packages/args/test/trailing_options_test.dart
deleted file mode 100644
index cdc4227..0000000
--- a/packages/args/test/trailing_options_test.dart
+++ /dev/null
@@ -1,98 +0,0 @@
-// 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.
-
-import 'package:test/test.dart';
-import 'package:args/args.dart';
-
-void main() {
-  test('allowTrailingOptions defaults to false', () {
-    var parser = new ArgParser();
-    expect(parser.allowTrailingOptions, isFalse);
-  });
-
-  group('when trailing options are allowed', () {
-    var parser;
-    setUp(() {
-      parser = new ArgParser(allowTrailingOptions: true);
-    });
-
-    void expectThrows(List<String> args) => expect(
-        () => parser.parse(args), throwsFormatException,
-        reason: "with allowTrailingOptions: true");
-
-    test('collects non-options in rest', () {
-      parser.addFlag('flag');
-      parser.addOption('opt', abbr: 'o');
-      var results = parser.parse(['a', '--flag', 'b', '-o', 'value', 'c']);
-      expect(results['flag'], isTrue);
-      expect(results['opt'], equals('value'));
-      expect(results.rest, equals(['a', 'b', 'c']));
-    });
-
-    test('stops parsing options at "--"', () {
-      parser.addFlag('flag');
-      parser.addOption('opt', abbr: 'o');
-      var results = parser.parse(['a', '--flag', '--', '-ovalue', 'c']);
-      expect(results['flag'], isTrue);
-      expect(results.rest, equals(['a', '-ovalue', 'c']));
-    });
-
-    test('only consumes first "--"', () {
-      parser.addFlag('flag', abbr: 'f');
-      parser.addOption('opt', abbr: 'o');
-      var results = parser.parse(['a', '--', 'b', '--', 'c']);
-      expect(results.rest, equals(['a', 'b', '--', 'c']));
-    });
-
-    test('parses a trailing flag', () {
-      parser.addFlag('flag');
-      var results = parser.parse(['arg', '--flag']);
-
-      expect(results['flag'], isTrue);
-      expect(results.rest, equals(['arg']));
-    });
-
-    test('throws on a trailing option missing its value', () {
-      parser.addOption('opt');
-      expectThrows(['arg', '--opt']);
-    });
-
-    test('parses a trailing option', () {
-      parser.addOption('opt');
-      var results = parser.parse(['arg', '--opt', 'v']);
-      expect(results['opt'], equals('v'));
-      expect(results.rest, equals(['arg']));
-    });
-
-    test('throws on a trailing unknown flag', () {
-      expectThrows(['arg', '--xflag']);
-    });
-
-    test('throws on a trailing unknown option and value', () {
-      expectThrows(['arg', '--xopt', 'v']);
-    });
-
-    test('throws on a command', () {
-      parser.addCommand('com');
-      expectThrows(['arg', 'com']);
-    });
-  });
-
-  test("uses the innermost command's trailing options behavior", () {
-    var parser = new ArgParser(allowTrailingOptions: true);
-    parser.addFlag('flag', abbr: 'f');
-    var command =
-        parser.addCommand('cmd', new ArgParser(allowTrailingOptions: false));
-    command.addFlag('verbose', abbr: 'v');
-
-    var results = parser.parse(['a', '-f', 'b']);
-    expect(results['flag'], isTrue);
-    expect(results.rest, equals(['a', 'b']));
-
-    results = parser.parse(['cmd', '-f', 'a', '-v', '--unknown']);
-    expect(results['flag'], isTrue); // Not trailing.
-    expect(results.command['verbose'], isFalse);
-    expect(results.command.rest, equals(['a', '-v', '--unknown']));
-  });
-}
diff --git a/packages/args/test/usage_test.dart b/packages/args/test/usage_test.dart
deleted file mode 100644
index 31c19d2..0000000
--- a/packages/args/test/usage_test.dart
+++ /dev/null
@@ -1,318 +0,0 @@
-// 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:test/test.dart';
-import 'package:args/args.dart';
-
-void main() {
-  group('ArgParser.usage', () {
-    test('negatable flags show "no-" in title', () {
-      var parser = new ArgParser();
-      parser.addFlag('mode', help: 'The mode');
-
-      validateUsage(parser, '''
-          --[no-]mode    The mode
-          ''');
-    });
-
-    test('non-negatable flags don\'t show "no-" in title', () {
-      var parser = new ArgParser();
-      parser.addFlag('mode', negatable: false, help: 'The mode');
-
-      validateUsage(parser, '''
-          --mode    The mode
-          ''');
-    });
-
-    test('if there are no abbreviations, there is no column for them', () {
-      var parser = new ArgParser();
-      parser.addFlag('mode', help: 'The mode');
-
-      validateUsage(parser, '''
-          --[no-]mode    The mode
-          ''');
-    });
-
-    test('options are lined up past abbreviations', () {
-      var parser = new ArgParser();
-      parser.addFlag('mode', abbr: 'm', help: 'The mode');
-      parser.addOption('long', help: 'Lacks an abbreviation');
-
-      validateUsage(parser, '''
-          -m, --[no-]mode    The mode
-              --long         Lacks an abbreviation
-          ''');
-    });
-
-    test('help text is lined up past the longest option', () {
-      var parser = new ArgParser();
-      parser.addFlag('mode', abbr: 'm', help: 'Lined up with below');
-      parser.addOption('a-really-long-name', help: 'Its help text');
-
-      validateUsage(parser, '''
-          -m, --[no-]mode             Lined up with below
-              --a-really-long-name    Its help text
-          ''');
-    });
-
-    test('leading empty lines are ignored in help text', () {
-      var parser = new ArgParser();
-      parser.addFlag('mode', help: '\n\n\n\nAfter newlines');
-
-      validateUsage(parser, '''
-          --[no-]mode    After newlines
-          ''');
-    });
-
-    test('trailing empty lines are ignored in help text', () {
-      var parser = new ArgParser();
-      parser.addFlag('mode', help: 'Before newlines\n\n\n\n');
-
-      validateUsage(parser, '''
-          --[no-]mode    Before newlines
-          ''');
-    });
-
-    test('options are documented in the order they were added', () {
-      var parser = new ArgParser();
-      parser.addFlag('zebra', help: 'First');
-      parser.addFlag('monkey', help: 'Second');
-      parser.addFlag('wombat', help: 'Third');
-
-      validateUsage(parser, '''
-          --[no-]zebra     First
-          --[no-]monkey    Second
-          --[no-]wombat    Third
-          ''');
-    });
-
-    test('the default value for a flag is shown if on', () {
-      var parser = new ArgParser();
-      parser.addFlag('affirm', help: 'Should be on', defaultsTo: true);
-      parser.addFlag('negate', help: 'Should be off', defaultsTo: false);
-
-      validateUsage(parser, '''
-          --[no-]affirm    Should be on
-                           (defaults to on)
-
-          --[no-]negate    Should be off
-          ''');
-    });
-
-    test('the default value for an option with no allowed list is shown', () {
-      var parser = new ArgParser();
-      parser.addOption('any', help: 'Can be anything', defaultsTo: 'whatevs');
-
-      validateUsage(parser, '''
-          --any    Can be anything
-                   (defaults to "whatevs")
-          ''');
-    });
-
-    test('the value help is shown', () {
-      var parser = new ArgParser();
-      parser.addOption('out',
-          abbr: 'o', help: 'Where to write file', valueHelp: 'path');
-
-      validateUsage(parser, '''
-          -o, --out=<path>    Where to write file
-          ''');
-    });
-
-    test('the allowed list is shown', () {
-      var parser = new ArgParser();
-      parser.addOption('suit',
-          help: 'Like in cards',
-          allowed: ['spades', 'clubs', 'hearts', 'diamonds']);
-
-      validateUsage(parser, '''
-          --suit    Like in cards
-                    [spades, clubs, hearts, diamonds]
-          ''');
-    });
-
-    test('the default is highlighted in the allowed list', () {
-      var parser = new ArgParser();
-      parser.addOption('suit',
-          help: 'Like in cards',
-          defaultsTo: 'clubs',
-          allowed: ['spades', 'clubs', 'hearts', 'diamonds']);
-
-      validateUsage(parser, '''
-          --suit    Like in cards
-                    [spades, clubs (default), hearts, diamonds]
-          ''');
-    });
-
-    test('the allowed help is shown', () {
-      var parser = new ArgParser();
-      parser.addOption('suit',
-          help: 'Like in cards',
-          defaultsTo: 'clubs',
-          allowed: ['spades', 'clubs', 'diamonds', 'hearts'],
-          allowedHelp: {
-        'spades': 'Swords of a soldier',
-        'clubs': 'Weapons of war',
-        'diamonds': 'Money for this art',
-        'hearts': 'The shape of my heart'
-      });
-
-      validateUsage(parser, '''
-          --suit              Like in cards
-
-                [clubs]       Weapons of war
-                [diamonds]    Money for this art
-                [hearts]      The shape of my heart
-                [spades]      Swords of a soldier
-          ''');
-    });
-
-    test("hidden options don't appear in the help", () {
-      var parser = new ArgParser();
-      parser.addOption('first', help: 'The first option');
-      parser.addOption('second', hide: true);
-      parser.addOption('third', help: 'The third option');
-
-      validateUsage(parser, '''
-          --first    The first option
-          --third    The third option
-          ''');
-    });
-
-    test("hidden flags don't appear in the help", () {
-      var parser = new ArgParser();
-      parser.addFlag('first', help: 'The first flag');
-      parser.addFlag('second', hide: true);
-      parser.addFlag('third', help: 'The third flag');
-
-      validateUsage(parser, '''
-          --[no-]first    The first flag
-          --[no-]third    The third flag
-          ''');
-    });
-
-    test("hidden options don't affect spacing", () {
-      var parser = new ArgParser();
-      parser.addFlag('first', help: 'The first flag');
-      parser.addFlag('second-very-long-option', hide: true);
-      parser.addFlag('third', help: 'The third flag');
-
-      validateUsage(parser, '''
-          --[no-]first    The first flag
-          --[no-]third    The third flag
-          ''');
-    });
-
-    group("separators", () {
-      test("separates options where it's placed", () {
-        var parser = new ArgParser();
-        parser.addFlag('zebra', help: 'First');
-        parser.addSeparator('Primate:');
-        parser.addFlag('monkey', help: 'Second');
-        parser.addSeparator('Marsupial:');
-        parser.addFlag('wombat', help: 'Third');
-
-        validateUsage(parser, '''
-            --[no-]zebra     First
-
-            Primate:
-            --[no-]monkey    Second
-
-            Marsupial:
-            --[no-]wombat    Third
-            ''');
-      });
-
-      test("doesn't add extra newlines after a multiline option", () {
-        var parser = new ArgParser();
-        parser.addFlag('zebra', help: 'Multi\nline');
-        parser.addSeparator('Primate:');
-        parser.addFlag('monkey', help: 'Second');
-
-        validateUsage(parser, '''
-            --[no-]zebra     Multi
-                             line
-
-            Primate:
-            --[no-]monkey    Second
-            ''');
-      });
-
-      test("doesn't add newlines if it's the first component", () {
-        var parser = new ArgParser();
-        parser.addSeparator('Equine:');
-        parser.addFlag('zebra', help: 'First');
-
-        validateUsage(parser, '''
-            Equine:
-            --[no-]zebra    First
-            ''');
-      });
-
-      test("doesn't add trailing newlines if it's the last component", () {
-        var parser = new ArgParser();
-        parser.addFlag('zebra', help: 'First');
-        parser.addSeparator('Primate:');
-
-        validateUsage(parser, '''
-            --[no-]zebra    First
-
-            Primate:
-            ''');
-      });
-
-      test("adds a newline after another separator", () {
-        var parser = new ArgParser();
-        parser.addSeparator('First');
-        parser.addSeparator('Second');
-
-        validateUsage(parser, '''
-            First
-
-            Second
-            ''');
-      });
-    });
-  });
-}
-
-void validateUsage(ArgParser parser, String expected) {
-  expected = unindentString(expected);
-  expect(parser.usage, equals(expected));
-}
-
-// TODO(rnystrom): Replace one in test_utils.
-String unindentString(String text) {
-  var lines = text.split('\n');
-
-  // Count the indentation of the last line.
-  var whitespace = new RegExp('^ *');
-  var indent = whitespace.firstMatch(lines[lines.length - 1])[0].length;
-
-  // Drop the last line. It only exists for specifying indentation.
-  lines.removeLast();
-
-  // Strip indentation from the remaining lines.
-  for (var i = 0; i < lines.length; i++) {
-    var line = lines[i];
-    if (line.length <= indent) {
-      // It's short, so it must be nothing but whitespace.
-      if (line.trim() != '') {
-        throw new ArgumentError(
-            'Line "$line" does not have enough indentation.');
-      }
-
-      lines[i] = '';
-    } else {
-      if (line.substring(0, indent).trim() != '') {
-        throw new ArgumentError(
-            'Line "$line" does not have enough indentation.');
-      }
-
-      lines[i] = line.substring(indent);
-    }
-  }
-
-  return lines.join('\n');
-}
diff --git a/packages/args/test/utils.dart b/packages/args/test/utils.dart
deleted file mode 100644
index a874eee..0000000
--- a/packages/args/test/utils.dart
+++ /dev/null
@@ -1,113 +0,0 @@
-// 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.
-
-import 'dart:async';
-
-import 'package:args/args.dart';
-import 'package:args/command_runner.dart';
-import 'package:test/test.dart';
-
-class CommandRunnerWithFooter extends CommandRunner {
-  String get usageFooter => "Also, footer!";
-
-  CommandRunnerWithFooter(String executableName, String description)
-      : super(executableName, description);
-}
-
-class FooCommand extends Command {
-  var hasRun = false;
-
-  final name = "foo";
-  final description = "Set a value.";
-  final takesArguments = false;
-
-  void run() {
-    hasRun = true;
-  }
-}
-
-class ValueCommand extends Command<int> {
-  final name = "foo";
-  final description = "Return a value.";
-  final takesArguments = false;
-
-  int run() => 12;
-}
-
-class AsyncValueCommand extends Command<String> {
-  final name = "foo";
-  final description = "Return a future.";
-  final takesArguments = false;
-
-  Future<String> run() async => "hi";
-}
-
-class MultilineCommand extends Command {
-  var hasRun = false;
-
-  final name = "multiline";
-  final description = "Multi\nline.";
-  final takesArguments = false;
-
-  void run() {
-    hasRun = true;
-  }
-}
-
-class MultilineSummaryCommand extends MultilineCommand {
-  String get summary => description;
-}
-
-class HiddenCommand extends Command {
-  var hasRun = false;
-
-  final name = "hidden";
-  final description = "Set a value.";
-  final hidden = true;
-  final takesArguments = false;
-
-  void run() {
-    hasRun = true;
-  }
-}
-
-class AliasedCommand extends Command {
-  var hasRun = false;
-
-  final name = "aliased";
-  final description = "Set a value.";
-  final takesArguments = false;
-  final aliases = const ["alias", "als"];
-
-  void run() {
-    hasRun = true;
-  }
-}
-
-class AsyncCommand extends Command {
-  var hasRun = false;
-
-  final name = "async";
-  final description = "Set a value asynchronously.";
-  final takesArguments = false;
-
-  Future run() => new Future.value().then((_) => hasRun = true);
-}
-
-void throwsIllegalArg(function, {String reason: null}) {
-  expect(function, throwsArgumentError, reason: reason);
-}
-
-void throwsFormat(ArgParser parser, List<String> args) {
-  expect(() => parser.parse(args), throwsFormatException);
-}
-
-Matcher throwsUsageException(message, usage) {
-  return throwsA(predicate((error) {
-    expect(error, new isInstanceOf<UsageException>());
-    expect(error.message, message);
-    expect(error.usage, usage);
-    return true;
-  }));
-}
diff --git a/packages/charted/lib/charts/src/chart_axis_impl.dart b/packages/charted/lib/charts/src/chart_axis_impl.dart
index 8e7973f..890a18c 100644
--- a/packages/charted/lib/charts/src/chart_axis_impl.dart
+++ b/packages/charted/lib/charts/src/chart_axis_impl.dart
@@ -183,7 +183,7 @@
         outerTickSize: 0,
         tickPadding: _theme.axisTickPadding,
         tickFormat: _columnSpec.formatter,
-        tickValues: tickValues.toList(),
+        tickValues: tickValues?.toList(),
         scale: scale);
 
     axis.create(element, scope,
diff --git a/packages/charted/pubspec.yaml b/packages/charted/pubspec.yaml
index e753f51..e92537a 100644
--- a/packages/charted/pubspec.yaml
+++ b/packages/charted/pubspec.yaml
@@ -1,5 +1,5 @@
 name: charted
-version: 0.4.9
+version: 0.5.0
 authors:
 - Prasad Sunkari <prsd@google.com>
 - Michael Cheng <midoringo@google.com>
diff --git a/packages/collection/.gitignore b/packages/collection/.gitignore
new file mode 100644
index 0000000..25a1df3
--- /dev/null
+++ b/packages/collection/.gitignore
@@ -0,0 +1,9 @@
+.buildlog
+.DS_Store
+.idea
+.pub/
+.settings/
+build/
+packages
+.packages
+pubspec.lock
diff --git a/packages/args/.test_config b/packages/collection/.test_config
similarity index 100%
rename from packages/args/.test_config
rename to packages/collection/.test_config
diff --git a/packages/collection/.travis.yml b/packages/collection/.travis.yml
new file mode 100644
index 0000000..264316d
--- /dev/null
+++ b/packages/collection/.travis.yml
@@ -0,0 +1,24 @@
+language: dart
+
+dart:
+  - dev
+  - stable
+
+dart_task:
+  - test: --platform vm
+  - test: --platform firefox -j 1
+  - dartanalyzer
+  - dartfmt
+
+branches:
+  only: [master]
+
+matrix:
+  # Only run dartfmt checks with stable.
+  exclude:
+    - dart: dev
+      dart_task: dartfmt
+
+cache:
+  directories:
+    - $HOME/.pub-cache
diff --git a/packages/collection/AUTHORS b/packages/collection/AUTHORS
new file mode 100644
index 0000000..e8063a8
--- /dev/null
+++ b/packages/collection/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+#   Name/Organization <email address>
+
+Google Inc.
diff --git a/packages/collection/CHANGELOG.md b/packages/collection/CHANGELOG.md
new file mode 100644
index 0000000..b676ee1
--- /dev/null
+++ b/packages/collection/CHANGELOG.md
@@ -0,0 +1,167 @@
+## 1.14.6
+
+* Make `DefaultEquality`'s `equals()` and `hash()` methods take any `Object`
+  rather than objects of type `E`. This makes `const DefaultEquality<Null>()`
+  usable as `Equality<E>` for any `E`, which means it can be used in a const
+  context which expects `Equality<E>`.
+
+  This makes the default arguments of various other const equality constructors
+  work in strong mode.
+
+## 1.14.5
+
+* Fix issue with `EmptyUnmodifiableSet`'s stubs that were introduced in 1.14.4.
+
+## 1.14.4
+
+* Add implementation stubs of upcoming Dart 2.0 core library methods, namely
+  new methods for classes that implement `Iterable`, `List`, `Map`, `Queue`, 
+  and `Set`.
+
+## 1.14.3
+
+* Fix `MapKeySet.lookup` to be a valid override in strong mode.
+
+## 1.14.2
+
+* Add type arguments to `SyntheticInvocation`.
+
+## 1.14.1
+
+* Make `Equality` implementations accept `null` as argument to `hash`.
+
+## 1.14.0
+
+* Add `CombinedListView`, a view of several lists concatenated together.
+* Add `CombinedIterableView`, a view of several iterables concatenated together.
+* Add `CombinedMapView`, a view of several maps concatenated together.
+
+## 1.13.0
+
+* Add `EqualityBy`
+
+## 1.12.0
+
+* Add `CaseInsensitiveEquality`.
+
+* Fix bug in `equalsIgnoreAsciiCase`.
+
+## 1.11.0
+
+* Add `EqualityMap` and `EqualitySet` classes which use `Equality` objects for
+  key and element equality, respectively.
+
+## 1.10.1
+
+* `Set.difference` now takes a `Set<Object>` as argument.
+
+## 1.9.1
+
+* Fix some documentation bugs.
+
+## 1.9.0
+
+* Add a top-level `stronglyConnectedComponents()` function that returns the
+  strongly connected components in a directed graph.
+
+## 1.8.0
+
+* Add a top-level `mapMap()` function that works like `Iterable.map()` on a
+  `Map`.
+
+* Add a top-level `mergeMaps()` function that creates a new map with the
+  combined contents of two existing maps.
+
+* Add a top-level `groupBy()` function that converts an `Iterable` to a `Map` by
+  grouping its elements using a function.
+
+* Add top-level `minBy()` and `maxBy()` functions that return the minimum and
+  maximum values in an `Iterable`, respectively, ordered by a derived value.
+
+* Add a top-level `transitiveClosure()` function that returns the transitive
+  closure of a directed graph.
+
+## 1.7.0
+
+* Add a `const UnmodifiableSetView.empty()` constructor.
+
+## 1.6.0
+
+* Add a `UnionSet` class that provides a view of the union of a set of sets.
+
+* Add a `UnionSetController` class that provides a convenient way to manage the
+  contents of a `UnionSet`.
+
+* Fix another incorrectly-declared generic type.
+
+## 1.5.1
+
+* Fix an incorrectly-declared generic type.
+
+## 1.5.0
+
+* Add `DelegatingIterable.typed()`, `DelegatingList.typed()`,
+  `DelegatingSet.typed()`, `DelegatingMap.typed()`, and
+  `DelegatingQueue.typed()` static methods. These wrap untyped instances of
+  these classes with the correct type parameter, and assert the types of values
+  as they're accessed.
+
+* Fix the types for `binarySearch()` and `lowerBound()` so they no longer
+  require all arguments to be comparable.
+
+* Add generic annotations to `insertionSort()` and `mergeSort()`.
+
+## 1.4.1
+
+* Fix all strong mode warnings.
+
+## 1.4.0
+
+* Add a `new PriorityQueue()` constructor that forwards to `new
+  HeapPriorityQueue()`.
+
+* Deprecate top-level libraries other than `package:collection/collection.dart`,
+  which exports these libraries' interfaces.
+
+## 1.3.0
+
+* Add `lowerBound` to binary search for values that might not be present.
+
+* Verify that the is valid for `CanonicalMap.[]`.
+
+## 1.2.0
+
+* Add string comparators that ignore ASCII case and sort numbers numerically.
+
+## 1.1.3
+
+* Fix type inconsistencies with `Map` and `Set`.
+
+## 1.1.2
+
+* Export `UnmodifiableMapView` from the Dart core libraries.
+
+## 1.1.1
+
+* Bug-fix for signatures of `isValidKey` arguments of `CanonicalizedMap`.
+
+## 1.1.0
+
+* Add a `QueueList` class that implements both `Queue` and `List`.
+
+## 0.9.4
+
+* Add a `CanonicalizedMap` class that canonicalizes its keys to provide a custom
+  equality relation.
+
+## 0.9.3+1
+
+* Fix all analyzer hints.
+
+## 0.9.3
+
+* Add a `MapKeySet` class that exposes an unmodifiable `Set` view of a `Map`'s
+  keys.
+
+* Add a `MapValueSet` class that takes a function from values to keys and uses
+  it to expose a `Set` view of a `Map`'s values.
diff --git a/packages/collection/CONTRIBUTING.md b/packages/collection/CONTRIBUTING.md
new file mode 100644
index 0000000..6f5e0ea
--- /dev/null
+++ b/packages/collection/CONTRIBUTING.md
@@ -0,0 +1,33 @@
+Want to contribute? Great! First, read this page (including the small print at
+the end).
+
+### Before you contribute
+Before we can use your code, you must sign the
+[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual)
+(CLA), which you can do online. The CLA is necessary mainly because you own the
+copyright to your changes, even after your contribution becomes part of our
+codebase, so we need your permission to use and distribute your code. We also
+need to be sure of various other things—for instance that you'll tell us if you
+know that your code infringes on other people's patents. You don't have to sign
+the CLA until after you've submitted your code for review and a member has
+approved it, but you must do it before we can put your code into our codebase.
+
+Before you start working on a larger contribution, you should get in touch with
+us first through the issue tracker with your idea so that we can help out and
+possibly guide you. Coordinating up front makes it much easier to avoid
+frustration later on.
+
+### Code reviews
+All submissions, including submissions by project members, require review.
+
+### File headers
+All files in the project must start with the following header.
+
+    // Copyright (c) 2015, 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.
+
+### The small print
+Contributions made by corporations are covered by a different agreement than the
+one above, the
+[Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate).
diff --git a/packages/args/LICENSE b/packages/collection/LICENSE
similarity index 95%
rename from packages/args/LICENSE
rename to packages/collection/LICENSE
index ee99930..de31e1a 100644
--- a/packages/args/LICENSE
+++ b/packages/collection/LICENSE
@@ -1,4 +1,4 @@
-Copyright 2013, the Dart project authors. All rights reserved.
+Copyright 2015, the Dart project authors. All rights reserved.
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are
 met:
diff --git a/packages/collection/README.md b/packages/collection/README.md
new file mode 100644
index 0000000..a7985fa
--- /dev/null
+++ b/packages/collection/README.md
@@ -0,0 +1,55 @@
+Contains utility functions and classes in the style of `dart:collection` to make
+working with collections easier.
+
+## Algorithms
+
+The package contains functions that operate on lists.
+
+It contains ways to shuffle a `List`, do binary search on a sorted `List`, and
+various sorting algorithms.
+
+## Equality
+
+The package provides a way to specify the equality of elements and collections.
+
+Collections in Dart have no inherent equality. Two sets are not equal, even
+if they contain exactly the same objects as elements.
+
+The `Equality` interface provides a way to say define such an equality. In this
+case, for example, `const SetEquality(const IdentityEquality())` is an equality
+that considers two sets equal exactly if they contain identical elements.
+
+Equalities are provided for `Iterable`s, `List`s, `Set`s, and `Map`s, as well as
+combinations of these, such as:
+
+```dart
+const MapEquality(const IdentityEquality(), const ListEquality());
+```
+
+This equality considers maps equal if they have identical keys, and the
+corresponding values are lists with equal (`operator==`) values.
+
+## Iterable Zip
+
+Utilities for "zipping" a list of iterables into an iterable of lists.
+
+## Priority Queue
+
+An interface and implementation of a priority queue.
+
+## Wrappers
+
+The package contains classes that "wrap" a collection.
+
+A wrapper class contains an object of the same type, and it forwards all
+methods to the wrapped object.
+
+Wrapper classes can be used in various ways, for example to restrict the type
+of an object to that of a supertype, or to change the behavior of selected
+functions on an existing object.
+
+## Features and bugs
+
+Please file feature requests and bugs at the [issue tracker][tracker].
+
+[tracker]: https://github.com/dart-lang/collection/issues
diff --git a/packages/collection/analysis_options.yaml b/packages/collection/analysis_options.yaml
new file mode 100644
index 0000000..5b5eb96
--- /dev/null
+++ b/packages/collection/analysis_options.yaml
@@ -0,0 +1,37 @@
+analyzer:
+  strong-mode: true
+  errors:
+    unused_element: error
+    unused_import: error
+    unused_local_variable: error
+    dead_code: error
+linter:
+  rules:
+    # Errors
+    - avoid_empty_else
+    - control_flow_in_finally
+    - empty_statements
+    - hash_and_equals
+    - implementation_imports
+    - test_types_in_equals
+    - throw_in_finally
+    - unrelated_type_equality_checks
+    - valid_regexps
+
+    # Style
+    - avoid_init_to_null
+    - avoid_return_types_on_setters
+    - await_only_futures
+    - camel_case_types
+    - directives_ordering
+    - empty_catches
+    - empty_constructor_bodies
+    - library_names
+    - library_prefixes
+    - non_constant_identifier_names
+    - only_throw_errors
+    - prefer_final_fields
+    - prefer_is_not_empty
+    - prefer_single_quotes
+    - slash_for_doc_comments
+    - type_init_formals
diff --git a/packages/collection/codereview.settings b/packages/collection/codereview.settings
new file mode 100644
index 0000000..4afc2cf
--- /dev/null
+++ b/packages/collection/codereview.settings
@@ -0,0 +1,3 @@
+CODE_REVIEW_SERVER: http://codereview.chromium.org/
+VIEW_VC: https://github.com/dart-lang/collection/commit/
+CC_LIST: reviews@dartlang.org
diff --git a/packages/collection/lib/algorithms.dart b/packages/collection/lib/algorithms.dart
new file mode 100644
index 0000000..4d3ae8b
--- /dev/null
+++ b/packages/collection/lib/algorithms.dart
@@ -0,0 +1,9 @@
+// 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.
+
+/// Import `collection.dart` instead.
+@Deprecated("Will be removed in collection 2.0.0.")
+library dart.pkg.collection.algorithms;
+
+export "src/algorithms.dart";
diff --git a/packages/collection/lib/collection.dart b/packages/collection/lib/collection.dart
new file mode 100644
index 0000000..70f9fbc
--- /dev/null
+++ b/packages/collection/lib/collection.dart
@@ -0,0 +1,21 @@
+// 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.
+
+export "src/algorithms.dart";
+export "src/canonicalized_map.dart";
+export "src/combined_wrappers/combined_iterable.dart";
+export "src/combined_wrappers/combined_list.dart";
+export "src/combined_wrappers/combined_map.dart";
+export "src/comparators.dart";
+export "src/equality.dart";
+export "src/equality_map.dart";
+export "src/equality_set.dart";
+export "src/functions.dart";
+export "src/iterable_zip.dart";
+export "src/priority_queue.dart";
+export "src/queue_list.dart";
+export "src/union_set.dart";
+export "src/union_set_controller.dart";
+export "src/unmodifiable_wrappers.dart";
+export "src/wrappers.dart";
diff --git a/packages/collection/lib/equality.dart b/packages/collection/lib/equality.dart
new file mode 100644
index 0000000..0f5b51d
--- /dev/null
+++ b/packages/collection/lib/equality.dart
@@ -0,0 +1,9 @@
+// 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.
+
+/// Import `collection.dart` instead.
+@Deprecated("Will be removed in collection 2.0.0.")
+library dart.pkg.collection.equality;
+
+export "src/equality.dart";
diff --git a/packages/collection/lib/iterable_zip.dart b/packages/collection/lib/iterable_zip.dart
new file mode 100644
index 0000000..34e18ef
--- /dev/null
+++ b/packages/collection/lib/iterable_zip.dart
@@ -0,0 +1,9 @@
+// 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.
+
+/// Import `collection.dart` instead.
+@Deprecated("Will be removed in collection 2.0.0.")
+library dart.pkg.collection.iterable_zip;
+
+export "src/iterable_zip.dart";
diff --git a/packages/collection/lib/priority_queue.dart b/packages/collection/lib/priority_queue.dart
new file mode 100644
index 0000000..f2a4703
--- /dev/null
+++ b/packages/collection/lib/priority_queue.dart
@@ -0,0 +1,9 @@
+// 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.
+
+/// Import `collection.dart` instead.
+@Deprecated("Will be removed in collection 2.0.0.")
+library dart.pkg.collection.priority_queue;
+
+export "src/priority_queue.dart";
diff --git a/packages/collection/lib/src/algorithms.dart b/packages/collection/lib/src/algorithms.dart
new file mode 100644
index 0000000..567230c
--- /dev/null
+++ b/packages/collection/lib/src/algorithms.dart
@@ -0,0 +1,283 @@
+// 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.
+
+import "dart:math" as math;
+
+import "utils.dart";
+
+/// Returns a position of the [value] in [sortedList], if it is there.
+///
+/// If the list isn't sorted according to the [compare] function, the result
+/// is unpredictable.
+///
+/// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
+/// the objects. If any object is not [Comparable], this throws a [CastError].
+///
+/// Returns -1 if [value] is not in the list by default.
+int binarySearch<T>(List<T> sortedList, T value, {int compare(T a, T b)}) {
+  compare ??= defaultCompare<T>();
+  int min = 0;
+  int max = sortedList.length;
+  while (min < max) {
+    int mid = min + ((max - min) >> 1);
+    var element = sortedList[mid];
+    int comp = compare(element, value);
+    if (comp == 0) return mid;
+    if (comp < 0) {
+      min = mid + 1;
+    } else {
+      max = mid;
+    }
+  }
+  return -1;
+}
+
+/// Returns the first position in [sortedList] that does not compare less than
+/// [value].
+///
+/// If the list isn't sorted according to the [compare] function, the result
+/// is unpredictable.
+///
+/// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
+/// the objects. If any object is not [Comparable], this throws a [CastError].
+///
+/// Returns [sortedList.length] if all the items in [sortedList] compare less
+/// than [value].
+int lowerBound<T>(List<T> sortedList, T value, {int compare(T a, T b)}) {
+  compare ??= defaultCompare<T>();
+  int min = 0;
+  int max = sortedList.length;
+  while (min < max) {
+    int mid = min + ((max - min) >> 1);
+    var element = sortedList[mid];
+    int comp = compare(element, value);
+    if (comp < 0) {
+      min = mid + 1;
+    } else {
+      max = mid;
+    }
+  }
+  return min;
+}
+
+/// Shuffles a list randomly.
+///
+/// A sub-range of a list can be shuffled by providing [start] and [end].
+void shuffle(List list, [int start = 0, int end = null]) {
+  var random = new math.Random();
+  if (end == null) end = list.length;
+  int length = end - start;
+  while (length > 1) {
+    int pos = random.nextInt(length);
+    length--;
+    var tmp1 = list[start + pos];
+    list[start + pos] = list[start + length];
+    list[start + length] = tmp1;
+  }
+}
+
+/// Reverses a list, or a part of a list, in-place.
+void reverse(List list, [int start = 0, int end = null]) {
+  if (end == null) end = list.length;
+  _reverse(list, start, end);
+}
+
+/// Internal helper function that assumes valid arguments.
+void _reverse(List list, int start, int end) {
+  for (int i = start, j = end - 1; i < j; i++, j--) {
+    var tmp = list[i];
+    list[i] = list[j];
+    list[j] = tmp;
+  }
+}
+
+/// Sort a list between [start] (inclusive) and [end] (exclusive) using
+/// insertion sort.
+///
+/// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
+/// the objects. If any object is not [Comparable], this throws a [CastError].
+///
+/// Insertion sort is a simple sorting algorithm. For `n` elements it does on
+/// the order of `n * log(n)` comparisons but up to `n` squared moves. The
+/// sorting is performed in-place, without using extra memory.
+///
+/// For short lists the many moves have less impact than the simple algorithm,
+/// and it is often the favored sorting algorithm for short lists.
+///
+/// This insertion sort is stable: Equal elements end up in the same order
+/// as they started in.
+void insertionSort<T>(List<T> list,
+    {int compare(T a, T b), int start: 0, int end}) {
+  // If the same method could have both positional and named optional
+  // parameters, this should be (list, [start, end], {compare}).
+  compare ??= defaultCompare<T>();
+  end ??= list.length;
+
+  for (int pos = start + 1; pos < end; pos++) {
+    int min = start;
+    int max = pos;
+    var element = list[pos];
+    while (min < max) {
+      int mid = min + ((max - min) >> 1);
+      int comparison = compare(element, list[mid]);
+      if (comparison < 0) {
+        max = mid;
+      } else {
+        min = mid + 1;
+      }
+    }
+    list.setRange(min + 1, pos + 1, list, min);
+    list[min] = element;
+  }
+}
+
+/// Limit below which merge sort defaults to insertion sort.
+const int _MERGE_SORT_LIMIT = 32;
+
+/// Sorts a list between [start] (inclusive) and [end] (exclusive) using the
+/// merge sort algorithm.
+///
+/// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
+/// the objects. If any object is not [Comparable], this throws a [CastError].
+///
+/// Merge-sorting works by splitting the job into two parts, sorting each
+/// recursively, and then merging the two sorted parts.
+///
+/// This takes on the order of `n * log(n)` comparisons and moves to sort
+/// `n` elements, but requires extra space of about the same size as the list
+/// being sorted.
+///
+/// This merge sort is stable: Equal elements end up in the same order
+/// as they started in.
+void mergeSort<T>(List<T> list,
+    {int start: 0, int end, int compare(T a, T b)}) {
+  end ??= list.length;
+  compare ??= defaultCompare<T>();
+
+  int length = end - start;
+  if (length < 2) return;
+  if (length < _MERGE_SORT_LIMIT) {
+    insertionSort(list, compare: compare, start: start, end: end);
+    return;
+  }
+  // Special case the first split instead of directly calling
+  // _mergeSort, because the _mergeSort requires its target to
+  // be different from its source, and it requires extra space
+  // of the same size as the list to sort.
+  // This split allows us to have only half as much extra space,
+  // and it ends up in the original place.
+  int middle = start + ((end - start) >> 1);
+  int firstLength = middle - start;
+  int secondLength = end - middle;
+  // secondLength is always the same as firstLength, or one greater.
+  var scratchSpace = new List<T>(secondLength);
+  _mergeSort(list, compare, middle, end, scratchSpace, 0);
+  int firstTarget = end - firstLength;
+  _mergeSort(list, compare, start, middle, list, firstTarget);
+  _merge(compare, list, firstTarget, end, scratchSpace, 0, secondLength, list,
+      start);
+}
+
+/// Performs an insertion sort into a potentially different list than the
+/// one containing the original values.
+///
+/// It will work in-place as well.
+void _movingInsertionSort<T>(List<T> list, int compare(T a, T b), int start,
+    int end, List<T> target, int targetOffset) {
+  int length = end - start;
+  if (length == 0) return;
+  target[targetOffset] = list[start];
+  for (int i = 1; i < length; i++) {
+    var element = list[start + i];
+    int min = targetOffset;
+    int max = targetOffset + i;
+    while (min < max) {
+      int mid = min + ((max - min) >> 1);
+      if (compare(element, target[mid]) < 0) {
+        max = mid;
+      } else {
+        min = mid + 1;
+      }
+    }
+    target.setRange(min + 1, targetOffset + i + 1, target, min);
+    target[min] = element;
+  }
+}
+
+/// Sorts [list] from [start] to [end] into [target] at [targetOffset].
+///
+/// The `target` list must be able to contain the range from `start` to `end`
+/// after `targetOffset`.
+///
+/// Allows target to be the same list as [list], as long as it's not
+/// overlapping the `start..end` range.
+void _mergeSort<T>(List<T> list, int compare(T a, T b), int start, int end,
+    List<T> target, int targetOffset) {
+  int length = end - start;
+  if (length < _MERGE_SORT_LIMIT) {
+    _movingInsertionSort(list, compare, start, end, target, targetOffset);
+    return;
+  }
+  int middle = start + (length >> 1);
+  int firstLength = middle - start;
+  int secondLength = end - middle;
+  // Here secondLength >= firstLength (differs by at most one).
+  int targetMiddle = targetOffset + firstLength;
+  // Sort the second half into the end of the target area.
+  _mergeSort(list, compare, middle, end, target, targetMiddle);
+  // Sort the first half into the end of the source area.
+  _mergeSort(list, compare, start, middle, list, middle);
+  // Merge the two parts into the target area.
+  _merge(compare, list, middle, middle + firstLength, target, targetMiddle,
+      targetMiddle + secondLength, target, targetOffset);
+}
+
+/// Merges two lists into a target list.
+///
+/// One of the input lists may be positioned at the end of the target
+/// list.
+///
+/// For equal object, elements from [firstList] are always preferred.
+/// This allows the merge to be stable if the first list contains elements
+/// that started out earlier than the ones in [secondList]
+void _merge<T>(
+    int compare(T a, T b),
+    List<T> firstList,
+    int firstStart,
+    int firstEnd,
+    List<T> secondList,
+    int secondStart,
+    int secondEnd,
+    List<T> target,
+    int targetOffset) {
+  // No empty lists reaches here.
+  assert(firstStart < firstEnd);
+  assert(secondStart < secondEnd);
+  int cursor1 = firstStart;
+  int cursor2 = secondStart;
+  var firstElement = firstList[cursor1++];
+  var secondElement = secondList[cursor2++];
+  while (true) {
+    if (compare(firstElement, secondElement) <= 0) {
+      target[targetOffset++] = firstElement;
+      if (cursor1 == firstEnd) break; // Flushing second list after loop.
+      firstElement = firstList[cursor1++];
+    } else {
+      target[targetOffset++] = secondElement;
+      if (cursor2 != secondEnd) {
+        secondElement = secondList[cursor2++];
+        continue;
+      }
+      // Second list empties first. Flushing first list here.
+      target[targetOffset++] = firstElement;
+      target.setRange(targetOffset, targetOffset + (firstEnd - cursor1),
+          firstList, cursor1);
+      return;
+    }
+  }
+  // First list empties first. Reached by break above.
+  target[targetOffset++] = secondElement;
+  target.setRange(
+      targetOffset, targetOffset + (secondEnd - cursor2), secondList, cursor2);
+}
diff --git a/packages/collection/lib/src/canonicalized_map.dart b/packages/collection/lib/src/canonicalized_map.dart
new file mode 100644
index 0000000..117cd63
--- /dev/null
+++ b/packages/collection/lib/src/canonicalized_map.dart
@@ -0,0 +1,192 @@
+// 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.
+
+import 'dart:collection';
+
+import 'utils.dart';
+
+typedef C _Canonicalize<C, K>(K key);
+
+typedef bool _IsValidKey(Object key);
+
+/// A map whose keys are converted to canonical values of type `C`.
+///
+/// This is useful for using case-insensitive String keys, for example. It's
+/// more efficient than a [LinkedHashMap] with a custom equality operator
+/// because it only canonicalizes each key once, rather than doing so for each
+/// comparison.
+///
+/// By default, `null` is allowed as a key. It can be forbidden via the
+/// `isValidKey` parameter.
+class CanonicalizedMap<C, K, V> implements Map<K, V> {
+  final _Canonicalize<C, K> _canonicalize;
+
+  final _IsValidKey _isValidKeyFn;
+
+  final _base = new Map<C, Pair<K, V>>();
+
+  /// Creates an empty canonicalized map.
+  ///
+  /// The [canonicalize] function should return the canonical value for the
+  /// given key. Keys with the same canonical value are considered equivalent.
+  ///
+  /// The [isValidKey] function is called before calling [canonicalize] for
+  /// methods that take arbitrary objects. It can be used to filter out keys
+  /// that can't be canonicalized.
+  CanonicalizedMap(C canonicalize(K key), {bool isValidKey(Object key)})
+      : _canonicalize = canonicalize,
+        _isValidKeyFn = isValidKey;
+
+  /// Creates a canonicalized map that is initialized with the key/value pairs
+  /// of [other].
+  ///
+  /// The [canonicalize] function should return the canonical value for the
+  /// given key. Keys with the same canonical value are considered equivalent.
+  ///
+  /// The [isValidKey] function is called before calling [canonicalize] for
+  /// methods that take arbitrary objects. It can be used to filter out keys
+  /// that can't be canonicalized.
+  CanonicalizedMap.from(Map<K, V> other, C canonicalize(K key),
+      {bool isValidKey(Object key)})
+      : _canonicalize = canonicalize,
+        _isValidKeyFn = isValidKey {
+    addAll(other);
+  }
+
+  V operator [](Object key) {
+    if (!_isValidKey(key)) return null;
+    var pair = _base[_canonicalize(key as K)];
+    return pair == null ? null : pair.last;
+  }
+
+  void operator []=(K key, V value) {
+    if (!_isValidKey(key)) return;
+    _base[_canonicalize(key)] = new Pair(key, value);
+  }
+
+  void addAll(Map<K, V> other) {
+    other.forEach((key, value) => this[key] = value);
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  void addEntries(Iterable<Object> entries) {
+    // Change Iterable<Object> to Iterable<MapEntry<K, V>> when
+    // the MapEntry class has been added.
+    throw new UnimplementedError('addEntries');
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Map<K2, V2> cast<K2, V2>() {
+    throw new UnimplementedError('cast');
+  }
+
+  void clear() {
+    _base.clear();
+  }
+
+  bool containsKey(Object key) {
+    if (!_isValidKey(key)) return false;
+    return _base.containsKey(_canonicalize(key as K));
+  }
+
+  bool containsValue(Object value) =>
+      _base.values.any((pair) => pair.last == value);
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Iterable<Null> get entries {
+    // Change Iterable<Null> to Iterable<MapEntry<K, V>> when
+    // the MapEntry class has been added.
+    throw new UnimplementedError('entries');
+  }
+
+  void forEach(void f(K key, V value)) {
+    _base.forEach((key, pair) => f(pair.first, pair.last));
+  }
+
+  bool get isEmpty => _base.isEmpty;
+
+  bool get isNotEmpty => _base.isNotEmpty;
+
+  Iterable<K> get keys => _base.values.map((pair) => pair.first);
+
+  int get length => _base.length;
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Map<K2, V2> map<K2, V2>(Object transform(K key, V value)) {
+    // Change Object to MapEntry<K2, V2> when
+    // the MapEntry class has been added.
+    throw new UnimplementedError('map');
+  }
+
+  V putIfAbsent(K key, V ifAbsent()) {
+    return _base
+        .putIfAbsent(_canonicalize(key), () => new Pair(key, ifAbsent()))
+        .last;
+  }
+
+  V remove(Object key) {
+    if (!_isValidKey(key)) return null;
+    var pair = _base.remove(_canonicalize(key as K));
+    return pair == null ? null : pair.last;
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  void removeWhere(bool test(K key, V value)) {
+    throw new UnimplementedError('removeWhere');
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Map<K2, V2> retype<K2, V2>() {
+    throw new UnimplementedError('retype');
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  V update(K key, V update(V value), {V ifAbsent()}) {
+    throw new UnimplementedError('update');
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  void updateAll(V update(K key, V value)) {
+    throw new UnimplementedError('updateAll');
+  }
+
+  Iterable<V> get values => _base.values.map((pair) => pair.last);
+
+  String toString() {
+    // Detect toString() cycles.
+    if (_isToStringVisiting(this)) {
+      return '{...}';
+    }
+
+    var result = new StringBuffer();
+    try {
+      _toStringVisiting.add(this);
+      result.write('{');
+      bool first = true;
+      forEach((k, v) {
+        if (!first) {
+          result.write(', ');
+        }
+        first = false;
+        result.write('$k: $v');
+      });
+      result.write('}');
+    } finally {
+      assert(identical(_toStringVisiting.last, this));
+      _toStringVisiting.removeLast();
+    }
+
+    return result.toString();
+  }
+
+  bool _isValidKey(Object key) =>
+      (key == null || key is K) &&
+      (_isValidKeyFn == null || _isValidKeyFn(key));
+}
+
+/// A collection used to identify cyclic maps during toString() calls.
+final List _toStringVisiting = [];
+
+/// Check if we are currently visiting `o` in a toString() call.
+bool _isToStringVisiting(o) => _toStringVisiting.any((e) => identical(o, e));
diff --git a/packages/collection/lib/src/combined_wrappers/combined_iterable.dart b/packages/collection/lib/src/combined_wrappers/combined_iterable.dart
new file mode 100644
index 0000000..511876e
--- /dev/null
+++ b/packages/collection/lib/src/combined_wrappers/combined_iterable.dart
@@ -0,0 +1,55 @@
+// Copyright (c) 2017, 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:collection';
+
+/// A view of several iterables combined sequentially into a single iterable.
+///
+/// All methods and accessors treat the [CombinedIterableView] as if it were a
+/// single concatenated iterable, but the underlying implementation is based on
+/// lazily accessing individual iterable instances. This means that if the
+/// underlying iterables change, the [CombinedIterableView] will reflect those
+/// changes.
+class CombinedIterableView<T> extends IterableBase<T> {
+  /// The iterables that this combines.
+  final Iterable<Iterable<T>> _iterables;
+
+  /// Creates a combined view of [iterables].
+  const CombinedIterableView(this._iterables);
+
+  Iterator<T> get iterator =>
+      new _CombinedIterator<T>(_iterables.map((i) => i.iterator).iterator);
+
+  // Special cased contains/isEmpty/length since many iterables have an
+  // efficient implementation instead of running through the entire iterator.
+
+  bool contains(Object element) => _iterables.any((i) => i.contains(element));
+
+  bool get isEmpty => _iterables.every((i) => i.isEmpty);
+
+  int get length => _iterables.fold(0, (length, i) => length + i.length);
+}
+
+/// The iterator for [CombinedIterableView].
+///
+/// This moves through each iterable's iterators in sequence.
+class _CombinedIterator<T> implements Iterator<T> {
+  /// The iterators that this combines.
+  ///
+  /// Because this comes from a call to [Iterable.map], it's lazy and will
+  /// avoid instantiating unnecessary iterators.
+  final Iterator<Iterator<T>> _iterators;
+
+  _CombinedIterator(this._iterators);
+
+  T get current => _iterators.current?.current;
+
+  bool moveNext() {
+    var current = _iterators.current;
+    if (current != null && current.moveNext()) {
+      return true;
+    }
+    return _iterators.moveNext() && moveNext();
+  }
+}
diff --git a/packages/collection/lib/src/combined_wrappers/combined_list.dart b/packages/collection/lib/src/combined_wrappers/combined_list.dart
new file mode 100644
index 0000000..d2e0349
--- /dev/null
+++ b/packages/collection/lib/src/combined_wrappers/combined_list.dart
@@ -0,0 +1,66 @@
+// Copyright (c) 2017, 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:collection';
+
+/// A view of several lists combined into a single list.
+///
+/// All methods and accessors treat the [CombinedListView] list as if it were a
+/// single concatenated list, but the underlying implementation is based on
+/// lazily accessing individual list instances. This means that if the
+/// underlying lists change, the [CombinedListView] will reflect those changes.
+///
+/// The index operator (`[]`) and [length] property of a [CombinedListView] are
+/// both `O(lists)` rather than `O(1)`. A [CombinedListView] is unmodifiable.
+class CombinedListView<T> extends ListBase<T>
+    implements UnmodifiableListView<T> {
+  static void _throw() {
+    throw new UnsupportedError('Cannot modify an unmodifiable List');
+  }
+
+  /// The lists that this combines.
+  final List<List<T>> _lists;
+
+  /// Creates a combined view of [lists].
+  CombinedListView(this._lists);
+
+  set length(int length) {
+    _throw();
+  }
+
+  int get length => _lists.fold(0, (length, list) => length + list.length);
+
+  T operator [](int index) {
+    var initialIndex = index;
+    for (var i = 0; i < _lists.length; i++) {
+      var list = _lists[i];
+      if (index < list.length) {
+        return list[index];
+      }
+      index -= list.length;
+    }
+    throw new RangeError.index(initialIndex, this, 'index', null, length);
+  }
+
+  void operator []=(int index, T value) {
+    _throw();
+  }
+
+  void clear() {
+    _throw();
+  }
+
+  bool remove(Object element) {
+    _throw();
+    return null;
+  }
+
+  void removeWhere(bool filter(T element)) {
+    _throw();
+  }
+
+  void retainWhere(bool filter(T element)) {
+    _throw();
+  }
+}
diff --git a/packages/collection/lib/src/combined_wrappers/combined_map.dart b/packages/collection/lib/src/combined_wrappers/combined_map.dart
new file mode 100644
index 0000000..8c2760b
--- /dev/null
+++ b/packages/collection/lib/src/combined_wrappers/combined_map.dart
@@ -0,0 +1,52 @@
+// Copyright (c) 2017, 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:collection';
+
+import 'combined_iterable.dart';
+
+/// Returns a new map that represents maps flattened into a single map.
+///
+/// All methods and accessors treat the new map as-if it were a single
+/// concatenated map, but the underlying implementation is based on lazily
+/// accessing individual map instances. In the occasion where a key occurs in
+/// multiple maps the first value is returned.
+///
+/// The resulting map has an index operator (`[]`) and `length` property that
+/// are both `O(maps)`, rather than `O(1)`, and the map is unmodifiable - but
+/// underlying changes to these maps are still accessible from the resulting
+/// map.
+class CombinedMapView<K, V> extends UnmodifiableMapBase<K, V> {
+  final Iterable<Map<K, V>> _maps;
+
+  /// Create a new combined view into multiple maps.
+  ///
+  /// The iterable is accessed lazily so it should be collection type like
+  /// [List] or [Set] rather than a lazy iterable produced by `map()` et al.
+  CombinedMapView(this._maps);
+
+  V operator [](Object key) {
+    for (var map in _maps) {
+      // Avoid two hash lookups on a positive hit.
+      var value = map[key];
+      if (value != null || map.containsKey(value)) {
+        return value;
+      }
+    }
+    return null;
+  }
+
+  /// The keys of [this].
+  ///
+  /// The returned iterable has efficient `length` and `contains` operations,
+  /// based on [length] and [containsKey] of the individual maps.
+  ///
+  /// The order of iteration is defined by the individual `Map` implementations,
+  /// but must be consistent between changes to the maps.
+  ///
+  /// Unlike most [Map] implementations, modifying an individual map while
+  /// iterating the keys will _sometimes_ throw. This behavior may change in
+  /// the future.
+  Iterable<K> get keys => new CombinedIterableView<K>(_maps.map((m) => m.keys));
+}
diff --git a/packages/collection/lib/src/comparators.dart b/packages/collection/lib/src/comparators.dart
new file mode 100644
index 0000000..5fc55b2
--- /dev/null
+++ b/packages/collection/lib/src/comparators.dart
@@ -0,0 +1,393 @@
+// Copyright (c) 2015, 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.

+

+// Character constants.

+const int _zero = 0x30;

+const int _upperCaseA = 0x41;

+const int _upperCaseZ = 0x5a;

+const int _lowerCaseA = 0x61;

+const int _lowerCaseZ = 0x7a;

+const int _asciiCaseBit = 0x20;

+

+/// Checks if strings [a] and [b] differ only on the case of ASCII letters.

+///

+/// Strings are equal if they have the same length, and the characters at

+/// each index are the same, or they are ASCII letters where one is upper-case

+/// and the other is the lower-case version of the same letter.

+///

+/// The comparison does not ignore the case of non-ASCII letters, so

+/// an upper-case ae-ligature (Æ) is different from

+/// a lower case ae-ligature (æ).

+///

+/// Ignoring non-ASCII letters is not generally a good idea, but it makes sense

+/// for situations where the strings are known to be ASCII. Examples could

+/// be Dart identifiers, base-64 or hex encoded strings, GUIDs or similar

+/// strings with a known structure.

+bool equalsIgnoreAsciiCase(String a, String b) {

+  if (a.length != b.length) return false;

+  for (int i = 0; i < a.length; i++) {

+    var aChar = a.codeUnitAt(i);

+    var bChar = b.codeUnitAt(i);

+    if (aChar == bChar) continue;

+    // Quick-check for whether this may be different cases of the same letter.

+    if (aChar ^ bChar != _asciiCaseBit) return false;

+    // If it's possible, then check if either character is actually an ASCII

+    // letter.

+    int aCharLowerCase = aChar | _asciiCaseBit;

+    if (_lowerCaseA <= aCharLowerCase && aCharLowerCase <= _lowerCaseZ) {

+      continue;

+    }

+    return false;

+  }

+  return true;

+}

+

+/// Hash code for a string which is compatible with [equalsIgnoreAsciiCase].

+///

+/// The hash code is unaffected by changing the case of ASCII letters, but

+/// the case of non-ASCII letters do affect the result.

+int hashIgnoreAsciiCase(String string) {

+  // Jenkins hash code ( http://en.wikipedia.org/wiki/Jenkins_hash_function).

+  // adapted to smi values.

+  // Same hash used by dart2js for strings, modified to ignore ASCII letter

+  // case.

+  int hash = 0;

+  for (int i = 0; i < string.length; i++) {

+    int char = string.codeUnitAt(i);

+    // Convert lower-case ASCII letters to upper case.upper

+    // This ensures that strings that differ only in case will have the

+    // same hash code.

+    if (_lowerCaseA <= char && char <= _lowerCaseZ) char -= _asciiCaseBit;

+    hash = 0x1fffffff & (hash + char);

+    hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));

+    hash >>= 6;

+  }

+  hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));

+  hash >>= 11;

+  return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));

+}

+

+/// Compares [a] and [b] lexically, converting ASCII letters to upper case.

+///

+/// Comparison treats all lower-case ASCII letters as upper-case letters,

+/// but does no case conversion for non-ASCII letters.

+///

+/// If two strings differ only on the case of ASCII letters, the one with the

+/// capital letter at the first difference will compare as less than the other

+/// string. This tie-breaking ensures that the comparison is a total ordering

+/// on strings and is compatible with equality.

+///

+/// Ignoring non-ASCII letters is not generally a good idea, but it makes sense

+/// for situations where the strings are known to be ASCII. Examples could

+/// be Dart identifiers, base-64 or hex encoded strings, GUIDs or similar

+/// strings with a known structure.

+int compareAsciiUpperCase(String a, String b) {

+  int defaultResult = 0; // Returned if no difference found.

+  for (int i = 0; i < a.length; i++) {

+    if (i >= b.length) return 1;

+    var aChar = a.codeUnitAt(i);

+    var bChar = b.codeUnitAt(i);

+    if (aChar == bChar) continue;

+    // Upper-case if letters.

+    int aUpperCase = aChar;

+    int bUpperCase = bChar;

+    if (_lowerCaseA <= aChar && aChar <= _lowerCaseZ) {

+      aUpperCase -= _asciiCaseBit;

+    }

+    if (_lowerCaseA <= bChar && bChar <= _lowerCaseZ) {

+      bUpperCase -= _asciiCaseBit;

+    }

+    if (aUpperCase != bUpperCase) return (aUpperCase - bUpperCase).sign;

+    if (defaultResult == 0) defaultResult = (aChar - bChar);

+  }

+  if (b.length > a.length) return -1;

+  return defaultResult.sign;

+}

+

+/// Compares [a] and [b] lexically, converting ASCII letters to lower case.

+///

+/// Comparison treats all upper-case ASCII letters as lower-case letters,

+/// but does no case conversion for non-ASCII letters.

+///

+/// If two strings differ only on the case of ASCII letters, the one with the

+/// capital letter at the first difference will compare as less than the other

+/// string. This tie-breaking ensures that the comparison is a total ordering

+/// on strings.

+///

+/// Ignoring non-ASCII letters is not generally a good idea, but it makes sense

+/// for situations where the strings are known to be ASCII. Examples could

+/// be Dart identifiers, base-64 or hex encoded strings, GUIDs or similar

+/// strings with a known structure.

+int compareAsciiLowerCase(String a, String b) {

+  int defaultResult = 0;

+  for (int i = 0; i < a.length; i++) {

+    if (i >= b.length) return 1;

+    var aChar = a.codeUnitAt(i);

+    var bChar = b.codeUnitAt(i);

+    if (aChar == bChar) continue;

+    int aLowerCase = aChar;

+    int bLowerCase = bChar;

+    // Upper case if ASCII letters.

+    if (_upperCaseA <= bChar && bChar <= _upperCaseZ) {

+      bLowerCase += _asciiCaseBit;

+    }

+    if (_upperCaseA <= aChar && aChar <= _upperCaseZ) {

+      aLowerCase += _asciiCaseBit;

+    }

+    if (aLowerCase != bLowerCase) return (aLowerCase - bLowerCase).sign;

+    if (defaultResult == 0) defaultResult = aChar - bChar;

+  }

+  if (b.length > a.length) return -1;

+  return defaultResult.sign;

+}

+

+/// Compares strings [a] and [b] according to [natural sort ordering][].

+///

+/// A natural sort ordering is a lexical ordering where embedded

+/// numerals (digit sequences) are treated as a single unit and ordered by

+/// numerical value.

+/// This means that `"a10b"` will be ordered after `"a7b"` in natural

+/// ordering, where lexical ordering would put the `1` before the `7`, ignoring

+/// that the `1` is part of a larger number.

+///

+/// Example:

+/// The following strings are in the order they would be sorted by using this

+/// comparison function:

+///

+///     "a", "a0", "a0b", "a1", "a01", "a9", "a10", "a100", "a100b", "aa"

+///

+/// [natural sort ordering]: https://en.wikipedia.org/wiki/Natural_sort_order

+int compareNatural(String a, String b) {

+  for (int i = 0; i < a.length; i++) {

+    if (i >= b.length) return 1;

+    var aChar = a.codeUnitAt(i);

+    var bChar = b.codeUnitAt(i);

+    if (aChar != bChar) {

+      return _compareNaturally(a, b, i, aChar, bChar);

+    }

+  }

+  if (b.length > a.length) return -1;

+  return 0;

+}

+

+/// Compares strings [a] and [b] according to lower-case

+/// [natural sort ordering][].

+///

+/// ASCII letters are converted to lower case before being compared, like

+/// for [compareAsciiLowerCase], then the result is compared like for

+/// [compareNatural].

+///

+/// If two strings differ only on the case of ASCII letters, the one with the

+/// capital letter at the first difference will compare as less than the other

+/// string. This tie-breaking ensures that the comparison is a total ordering

+/// on strings.

+///

+/// [natural sort ordering]: https://en.wikipedia.org/wiki/Natural_sort_order

+int compareAsciiLowerCaseNatural(String a, String b) {

+  int defaultResult = 0; // Returned if no difference found.

+  for (int i = 0; i < a.length; i++) {

+    if (i >= b.length) return 1;

+    var aChar = a.codeUnitAt(i);

+    var bChar = b.codeUnitAt(i);

+    if (aChar == bChar) continue;

+    int aLowerCase = aChar;

+    int bLowerCase = bChar;

+    if (_upperCaseA <= aChar && aChar <= _upperCaseZ) {

+      aLowerCase += _asciiCaseBit;

+    }

+    if (_upperCaseA <= bChar && bChar <= _upperCaseZ) {

+      bLowerCase += _asciiCaseBit;

+    }

+    if (aLowerCase != bLowerCase) {

+      return _compareNaturally(a, b, i, aLowerCase, bLowerCase);

+    }

+    if (defaultResult == 0) defaultResult = aChar - bChar;

+  }

+  if (b.length > a.length) return -1;

+  return defaultResult.sign;

+}

+

+/// Compares strings [a] and [b] according to upper-case

+/// [natural sort ordering][].

+///

+/// ASCII letters are converted to upper case before being compared, like

+/// for [compareAsciiUpperCase], then the result is compared like for

+/// [compareNatural].

+///

+/// If two strings differ only on the case of ASCII letters, the one with the

+/// capital letter at the first difference will compare as less than the other

+/// string. This tie-breaking ensures that the comparison is a total ordering

+/// on strings

+///

+/// [natural sort ordering]: https://en.wikipedia.org/wiki/Natural_sort_order

+int compareAsciiUpperCaseNatural(String a, String b) {

+  int defaultResult = 0;

+  for (int i = 0; i < a.length; i++) {

+    if (i >= b.length) return 1;

+    var aChar = a.codeUnitAt(i);

+    var bChar = b.codeUnitAt(i);

+    if (aChar == bChar) continue;

+    int aUpperCase = aChar;

+    int bUpperCase = bChar;

+    if (_lowerCaseA <= aChar && aChar <= _lowerCaseZ) {

+      aUpperCase -= _asciiCaseBit;

+    }

+    if (_lowerCaseA <= bChar && bChar <= _lowerCaseZ) {

+      bUpperCase -= _asciiCaseBit;

+    }

+    if (aUpperCase != bUpperCase) {

+      return _compareNaturally(a, b, i, aUpperCase, bUpperCase);

+    }

+    if (defaultResult == 0) defaultResult = aChar - bChar;

+  }

+  if (b.length > a.length) return -1;

+  return defaultResult.sign;

+}

+

+/// Check for numbers overlapping the current mismatched characters.

+///

+/// If both [aChar] and [bChar] are digits, use numerical comparison.

+/// Check if the previous characters is a non-zero number, and if not,

+/// skip - but count - leading zeros before comparing numbers.

+///

+/// If one is a digit and the other isn't, check if the previous character

+/// is a digit, and if so, the the one with the digit is the greater number.

+///

+/// Otherwise just returns the difference between [aChar] and [bChar].

+int _compareNaturally(String a, String b, int index, int aChar, int bChar) {

+  assert(aChar != bChar);

+  var aIsDigit = _isDigit(aChar);

+  var bIsDigit = _isDigit(bChar);

+  if (aIsDigit) {

+    if (bIsDigit) {

+      return _compareNumerically(a, b, aChar, bChar, index);

+    } else if (index > 0 && _isDigit(a.codeUnitAt(index - 1))) {

+      // aChar is the continuation of a longer number.

+      return 1;

+    }

+  } else if (bIsDigit && index > 0 && _isDigit(b.codeUnitAt(index - 1))) {

+    // bChar is the continuation of a longer number.

+    return -1;

+  }

+  // Characters are both non-digits, or not continuation of earlier number.

+  return (aChar - bChar).sign;

+}

+

+/// Compare numbers overlapping [aChar] and [bChar] numerically.

+///

+/// If the numbers have the same numerical value, but one has more leading

+/// zeros, the longer number is considered greater than the shorter one.

+///

+/// This ensures a total ordering on strings compatible with equality.

+int _compareNumerically(String a, String b, int aChar, int bChar, int index) {

+  // Both are digits. Find the first significant different digit, then find

+  // the length of the numbers.

+  if (_isNonZeroNumberSuffix(a, index)) {

+    // Part of a longer number, differs at this index, just count the length.

+    int result = _compareDigitCount(a, b, index, index);

+    if (result != 0) return result;

+    // If same length, the current character is the most significant differing

+    // digit.

+    return (aChar - bChar).sign;

+  }

+  // Not part of larger (non-zero) number, so skip leading zeros before

+  // comparing numbers.

+  int aIndex = index;

+  int bIndex = index;

+  if (aChar == _zero) {

+    do {

+      aIndex++;

+      if (aIndex == a.length) return -1; // number in a is zero, b is not.

+      aChar = a.codeUnitAt(aIndex);

+    } while (aChar == _zero);

+    if (!_isDigit(aChar)) return -1;

+  } else if (bChar == _zero) {

+    do {

+      bIndex++;

+      if (bIndex == b.length) return 1; // number in b is zero, a is not.

+      bChar = b.codeUnitAt(bIndex);

+    } while (bChar == _zero);

+    if (!_isDigit(bChar)) return 1;

+  }

+  if (aChar != bChar) {

+    int result = _compareDigitCount(a, b, aIndex, bIndex);

+    if (result != 0) return result;

+    return (aChar - bChar).sign;

+  }

+  // Same leading digit, one had more leading zeros.

+  // Compare digits until reaching a difference.

+  while (true) {

+    var aIsDigit = false;

+    var bIsDigit = false;

+    aChar = 0;

+    bChar = 0;

+    if (++aIndex < a.length) {

+      aChar = a.codeUnitAt(aIndex);

+      aIsDigit = _isDigit(aChar);

+    }

+    if (++bIndex < b.length) {

+      bChar = b.codeUnitAt(bIndex);

+      bIsDigit = _isDigit(bChar);

+    }

+    if (aIsDigit) {

+      if (bIsDigit) {

+        if (aChar == bChar) continue;

+        // First different digit found.

+        break;

+      }

+      // bChar is non-digit, so a has longer number.

+      return 1;

+    } else if (bIsDigit) {

+      return -1; // b has longer number.

+    } else {

+      // Neither is digit, so numbers had same numerical value.

+      // Fall back on number of leading zeros

+      // (reflected by difference in indices).

+      return (aIndex - bIndex).sign;

+    }

+  }

+  // At first differing digits.

+  int result = _compareDigitCount(a, b, aIndex, bIndex);

+  if (result != 0) return result;

+  return (aChar - bChar).sign;

+}

+

+/// Checks which of [a] and [b] has the longest sequence of digits.

+///

+/// Starts counting from `i + 1` and `j + 1` (assumes that `a[i]` and `b[j]` are

+/// both already known to be digits).

+int _compareDigitCount(String a, String b, int i, int j) {

+  while (++i < a.length) {

+    bool aIsDigit = _isDigit(a.codeUnitAt(i));

+    if (++j == b.length) return aIsDigit ? 1 : 0;

+    bool bIsDigit = _isDigit(b.codeUnitAt(j));

+    if (aIsDigit) {

+      if (bIsDigit) continue;

+      return 1;

+    } else if (bIsDigit) {

+      return -1;

+    } else {

+      return 0;

+    }

+  }

+  if (++j < b.length && _isDigit(b.codeUnitAt(j))) {

+    return -1;

+  }

+  return 0;

+}

+

+bool _isDigit(int charCode) => (charCode ^ _zero) <= 9;

+

+/// Check if the digit at [index] is continuing a non-zero number.

+///

+/// If there is no non-zero digits before, then leading zeros at [index]

+/// are also ignored when comparing numerically. If there is a non-zero digit

+/// before, then zeros at [index] are significant.

+bool _isNonZeroNumberSuffix(String string, int index) {

+  while (--index >= 0) {

+    int char = string.codeUnitAt(index);

+    if (char != _zero) return _isDigit(char);

+  }

+  return false;

+}

diff --git a/packages/collection/lib/src/empty_unmodifiable_set.dart b/packages/collection/lib/src/empty_unmodifiable_set.dart
new file mode 100644
index 0000000..436e88d
--- /dev/null
+++ b/packages/collection/lib/src/empty_unmodifiable_set.dart
@@ -0,0 +1,44 @@
+// Copyright (c) 2016, 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:collection';
+
+import 'unmodifiable_wrappers.dart';
+
+// Unfortunately, we can't use UnmodifiableSetMixin here, since const classes
+// can't use mixins.
+/// An unmodifiable, empty set that can have a const constructor.
+class EmptyUnmodifiableSet<E> extends IterableBase<E>
+    implements UnmodifiableSetView<E> {
+  static T _throw<T>() {
+    throw new UnsupportedError("Cannot modify an unmodifiable Set");
+  }
+
+  Iterator<E> get iterator => new Iterable<E>.empty().iterator;
+  int get length => 0;
+
+  const EmptyUnmodifiableSet();
+
+  EmptyUnmodifiableSet<T> cast<T>() => new EmptyUnmodifiableSet<T>();
+  bool contains(Object element) => false;
+  bool containsAll(Iterable<Object> other) => other.isEmpty;
+  Iterable<E> followedBy(Iterable<E> other) => new Set.from(other);
+  E lookup(Object element) => null;
+  EmptyUnmodifiableSet<T> retype<T>() => new EmptyUnmodifiableSet<T>();
+  E singleWhere(bool test(E element), {E orElse()}) => super.singleWhere(test);
+  Iterable<T> whereType<T>() => new EmptyUnmodifiableSet<T>();
+  Set<E> toSet() => new Set();
+  Set<E> union(Set<E> other) => new Set.from(other);
+  Set<E> intersection(Set<Object> other) => new Set();
+  Set<E> difference(Set<Object> other) => new Set();
+
+  bool add(E value) => _throw();
+  void addAll(Iterable<E> elements) => _throw();
+  void clear() => _throw();
+  bool remove(Object element) => _throw();
+  void removeAll(Iterable<Object> elements) => _throw();
+  void removeWhere(bool test(E element)) => _throw();
+  void retainWhere(bool test(E element)) => _throw();
+  void retainAll(Iterable<Object> elements) => _throw();
+}
diff --git a/packages/collection/lib/src/equality.dart b/packages/collection/lib/src/equality.dart
new file mode 100644
index 0000000..855503e
--- /dev/null
+++ b/packages/collection/lib/src/equality.dart
@@ -0,0 +1,464 @@
+// 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.
+
+import "dart:collection";
+
+import "comparators.dart";
+
+const int _HASH_MASK = 0x7fffffff;
+
+/// A generic equality relation on objects.
+abstract class Equality<E> {
+  const factory Equality() = DefaultEquality<E>;
+
+  /// Compare two elements for being equal.
+  ///
+  /// This should be a proper equality relation.
+  bool equals(E e1, E e2);
+
+  /// Get a hashcode of an element.
+  ///
+  /// The hashcode should be compatible with [equals], so that if
+  /// `equals(a, b)` then `hash(a) == hash(b)`.
+  int hash(E e);
+
+  /// Test whether an object is a valid argument to [equals] and [hash].
+  ///
+  /// Some implementations may be restricted to only work on specific types
+  /// of objects.
+  bool isValidKey(Object o);
+}
+
+typedef F _GetKey<E, F>(E object);
+
+/// Equality of objects based on derived values.
+///
+/// For example, given the class:
+/// ```dart
+/// abstract class Employee {
+///   int get employmentId;
+/// }
+/// ```
+///
+/// The following [Equality] considers employees with the same IDs to be equal:
+/// ```dart
+/// new EqualityBy((Employee e) => e.employmentId);
+/// ```
+///
+/// It's also possible to pass an additional equality instance that should be
+/// used to compare the value itself.
+class EqualityBy<E, F> implements Equality<E> {
+  // Returns a derived value F from an object E.
+  final _GetKey<E, F> _getKey;
+
+  // Determines equality between two values of F.
+  final Equality<F> _inner;
+
+  EqualityBy(F getKey(E object), [Equality<F> inner = const DefaultEquality()])
+      : _getKey = getKey,
+        _inner = inner;
+
+  bool equals(E e1, E e2) => _inner.equals(_getKey(e1), _getKey(e2));
+
+  int hash(E e) => _inner.hash(_getKey(e));
+
+  bool isValidKey(Object o) {
+    if (o is E) {
+      final value = _getKey(o);
+      return value is F && _inner.isValidKey(value);
+    }
+    return false;
+  }
+}
+
+/// Equality of objects that compares only the natural equality of the objects.
+///
+/// This equality uses the objects' own [Object.==] and [Object.hashCode] for
+/// the equality.
+///
+/// Note that [equals] and [hash] take `Object`s rather than `E`s. This allows
+/// `E` to be inferred as `Null` in const contexts where `E` wouldn't be a
+/// compile-time constant, while still allowing the class to be used at runtime.
+class DefaultEquality<E> implements Equality<E> {
+  const DefaultEquality();
+  bool equals(Object e1, Object e2) => e1 == e2;
+  int hash(Object e) => e.hashCode;
+  bool isValidKey(Object o) => true;
+}
+
+/// Equality of objects that compares only the identity of the objects.
+class IdentityEquality<E> implements Equality<E> {
+  const IdentityEquality();
+  bool equals(E e1, E e2) => identical(e1, e2);
+  int hash(E e) => identityHashCode(e);
+  bool isValidKey(Object o) => true;
+}
+
+/// Equality on iterables.
+///
+/// Two iterables are equal if they have the same elements in the same order.
+///
+/// The [equals] and [hash] methods accepts `null` values,
+/// even if the [isValidKey] returns `false` for `null`.
+/// The [hash] of `null` is `null.hashCode`.
+class IterableEquality<E> implements Equality<Iterable<E>> {
+  final Equality<E> _elementEquality;
+  const IterableEquality(
+      [Equality<E> elementEquality = const DefaultEquality()])
+      : _elementEquality = elementEquality;
+
+  bool equals(Iterable<E> elements1, Iterable<E> elements2) {
+    if (identical(elements1, elements2)) return true;
+    if (elements1 == null || elements2 == null) return false;
+    var it1 = elements1.iterator;
+    var it2 = elements2.iterator;
+    while (true) {
+      bool hasNext = it1.moveNext();
+      if (hasNext != it2.moveNext()) return false;
+      if (!hasNext) return true;
+      if (!_elementEquality.equals(it1.current, it2.current)) return false;
+    }
+  }
+
+  int hash(Iterable<E> elements) {
+    if (elements == null) return null.hashCode;
+    // Jenkins's one-at-a-time hash function.
+    int hash = 0;
+    for (E element in elements) {
+      int c = _elementEquality.hash(element);
+      hash = (hash + c) & _HASH_MASK;
+      hash = (hash + (hash << 10)) & _HASH_MASK;
+      hash ^= (hash >> 6);
+    }
+    hash = (hash + (hash << 3)) & _HASH_MASK;
+    hash ^= (hash >> 11);
+    hash = (hash + (hash << 15)) & _HASH_MASK;
+    return hash;
+  }
+
+  bool isValidKey(Object o) => o is Iterable<E>;
+}
+
+/// Equality on lists.
+///
+/// Two lists are equal if they have the same length and their elements
+/// at each index are equal.
+///
+/// This is effectively the same as [IterableEquality] except that it
+/// accesses elements by index instead of through iteration.
+///
+/// The [equals] and [hash] methods accepts `null` values,
+/// even if the [isValidKey] returns `false` for `null`.
+/// The [hash] of `null` is `null.hashCode`.
+class ListEquality<E> implements Equality<List<E>> {
+  final Equality<E> _elementEquality;
+  const ListEquality([Equality<E> elementEquality = const DefaultEquality()])
+      : _elementEquality = elementEquality;
+
+  bool equals(List<E> list1, List<E> list2) {
+    if (identical(list1, list2)) return true;
+    if (list1 == null || list2 == null) return false;
+    int length = list1.length;
+    if (length != list2.length) return false;
+    for (int i = 0; i < length; i++) {
+      if (!_elementEquality.equals(list1[i], list2[i])) return false;
+    }
+    return true;
+  }
+
+  int hash(List<E> list) {
+    if (list == null) return null.hashCode;
+    // Jenkins's one-at-a-time hash function.
+    // This code is almost identical to the one in IterableEquality, except
+    // that it uses indexing instead of iterating to get the elements.
+    int hash = 0;
+    for (int i = 0; i < list.length; i++) {
+      int c = _elementEquality.hash(list[i]);
+      hash = (hash + c) & _HASH_MASK;
+      hash = (hash + (hash << 10)) & _HASH_MASK;
+      hash ^= (hash >> 6);
+    }
+    hash = (hash + (hash << 3)) & _HASH_MASK;
+    hash ^= (hash >> 11);
+    hash = (hash + (hash << 15)) & _HASH_MASK;
+    return hash;
+  }
+
+  bool isValidKey(Object o) => o is List<E>;
+}
+
+abstract class _UnorderedEquality<E, T extends Iterable<E>>
+    implements Equality<T> {
+  final Equality<E> _elementEquality;
+
+  const _UnorderedEquality(this._elementEquality);
+
+  bool equals(T elements1, T elements2) {
+    if (identical(elements1, elements2)) return true;
+    if (elements1 == null || elements2 == null) return false;
+    HashMap<E, int> counts = new HashMap(
+        equals: _elementEquality.equals,
+        hashCode: _elementEquality.hash,
+        isValidKey: _elementEquality.isValidKey);
+    int length = 0;
+    for (var e in elements1) {
+      int count = counts[e];
+      if (count == null) count = 0;
+      counts[e] = count + 1;
+      length++;
+    }
+    for (var e in elements2) {
+      int count = counts[e];
+      if (count == null || count == 0) return false;
+      counts[e] = count - 1;
+      length--;
+    }
+    return length == 0;
+  }
+
+  int hash(T elements) {
+    if (elements == null) return null.hashCode;
+    int hash = 0;
+    for (E element in elements) {
+      int c = _elementEquality.hash(element);
+      hash = (hash + c) & _HASH_MASK;
+    }
+    hash = (hash + (hash << 3)) & _HASH_MASK;
+    hash ^= (hash >> 11);
+    hash = (hash + (hash << 15)) & _HASH_MASK;
+    return hash;
+  }
+}
+
+/// Equality of the elements of two iterables without considering order.
+///
+/// Two iterables are considered equal if they have the same number of elements,
+/// and the elements of one set can be paired with the elements
+/// of the other iterable, so that each pair are equal.
+class UnorderedIterableEquality<E> extends _UnorderedEquality<E, Iterable<E>> {
+  const UnorderedIterableEquality(
+      [Equality<E> elementEquality = const DefaultEquality()])
+      : super(elementEquality);
+
+  bool isValidKey(Object o) => o is Iterable<E>;
+}
+
+/// Equality of sets.
+///
+/// Two sets are considered equal if they have the same number of elements,
+/// and the elements of one set can be paired with the elements
+/// of the other set, so that each pair are equal.
+///
+/// This equality behaves the same as [UnorderedIterableEquality] except that
+/// it expects sets instead of iterables as arguments.
+///
+/// The [equals] and [hash] methods accepts `null` values,
+/// even if the [isValidKey] returns `false` for `null`.
+/// The [hash] of `null` is `null.hashCode`.
+class SetEquality<E> extends _UnorderedEquality<E, Set<E>> {
+  const SetEquality([Equality<E> elementEquality = const DefaultEquality()])
+      : super(elementEquality);
+
+  bool isValidKey(Object o) => o is Set<E>;
+}
+
+/// Internal class used by [MapEquality].
+///
+/// The class represents a map entry as a single object,
+/// using a combined hashCode and equality of the key and value.
+class _MapEntry {
+  final MapEquality equality;
+  final key;
+  final value;
+  _MapEntry(this.equality, this.key, this.value);
+
+  int get hashCode =>
+      (3 * equality._keyEquality.hash(key) +
+          7 * equality._valueEquality.hash(value)) &
+      _HASH_MASK;
+
+  bool operator ==(Object other) =>
+      other is _MapEntry &&
+      equality._keyEquality.equals(key, other.key) &&
+      equality._valueEquality.equals(value, other.value);
+}
+
+/// Equality on maps.
+///
+/// Two maps are equal if they have the same number of entries, and if the
+/// entries of the two maps are pairwise equal on both key and value.
+///
+/// The [equals] and [hash] methods accepts `null` values,
+/// even if the [isValidKey] returns `false` for `null`.
+/// The [hash] of `null` is `null.hashCode`.
+class MapEquality<K, V> implements Equality<Map<K, V>> {
+  final Equality<K> _keyEquality;
+  final Equality<V> _valueEquality;
+  const MapEquality(
+      {Equality<K> keys: const DefaultEquality(),
+      Equality<V> values: const DefaultEquality()})
+      : _keyEquality = keys,
+        _valueEquality = values;
+
+  bool equals(Map<K, V> map1, Map<K, V> map2) {
+    if (identical(map1, map2)) return true;
+    if (map1 == null || map2 == null) return false;
+    int length = map1.length;
+    if (length != map2.length) return false;
+    Map<_MapEntry, int> equalElementCounts = new HashMap();
+    for (K key in map1.keys) {
+      _MapEntry entry = new _MapEntry(this, key, map1[key]);
+      int count = equalElementCounts[entry];
+      if (count == null) count = 0;
+      equalElementCounts[entry] = count + 1;
+    }
+    for (K key in map2.keys) {
+      _MapEntry entry = new _MapEntry(this, key, map2[key]);
+      int count = equalElementCounts[entry];
+      if (count == null || count == 0) return false;
+      equalElementCounts[entry] = count - 1;
+    }
+    return true;
+  }
+
+  int hash(Map<K, V> map) {
+    if (map == null) return null.hashCode;
+    int hash = 0;
+    for (K key in map.keys) {
+      int keyHash = _keyEquality.hash(key);
+      int valueHash = _valueEquality.hash(map[key]);
+      hash = (hash + 3 * keyHash + 7 * valueHash) & _HASH_MASK;
+    }
+    hash = (hash + (hash << 3)) & _HASH_MASK;
+    hash ^= (hash >> 11);
+    hash = (hash + (hash << 15)) & _HASH_MASK;
+    return hash;
+  }
+
+  bool isValidKey(Object o) => o is Map<K, V>;
+}
+
+/// Combines several equalities into a single equality.
+///
+/// Tries each equality in order, using [Equality.isValidKey], and returns
+/// the result of the first equality that applies to the argument or arguments.
+///
+/// For `equals`, the first equality that matches the first argument is used,
+/// and if the second argument of `equals` is not valid for that equality,
+/// it returns false.
+///
+/// Because the equalities are tried in order, they should generally work on
+/// disjoint types. Otherwise the multi-equality may give inconsistent results
+/// for `equals(e1, e2)` and `equals(e2, e1)`. This can happen if one equality
+/// considers only `e1` a valid key, and not `e2`, but an equality which is
+/// checked later, allows both.
+class MultiEquality<E> implements Equality<E> {
+  final Iterable<Equality<E>> _equalities;
+
+  const MultiEquality(Iterable<Equality<E>> equalities)
+      : _equalities = equalities;
+
+  bool equals(E e1, E e2) {
+    for (Equality<E> eq in _equalities) {
+      if (eq.isValidKey(e1)) return eq.isValidKey(e2) && eq.equals(e1, e2);
+    }
+    return false;
+  }
+
+  int hash(E e) {
+    for (Equality<E> eq in _equalities) {
+      if (eq.isValidKey(e)) return eq.hash(e);
+    }
+    return 0;
+  }
+
+  bool isValidKey(Object o) {
+    for (Equality<E> eq in _equalities) {
+      if (eq.isValidKey(o)) return true;
+    }
+    return false;
+  }
+}
+
+/// Deep equality on collections.
+///
+/// Recognizes lists, sets, iterables and maps and compares their elements using
+/// deep equality as well.
+///
+/// Non-iterable/map objects are compared using a configurable base equality.
+///
+/// Works in one of two modes: ordered or unordered.
+///
+/// In ordered mode, lists and iterables are required to have equal elements
+/// in the same order. In unordered mode, the order of elements in iterables
+/// and lists are not important.
+///
+/// A list is only equal to another list, likewise for sets and maps. All other
+/// iterables are compared as iterables only.
+class DeepCollectionEquality implements Equality {
+  final Equality _base;
+  final bool _unordered;
+  const DeepCollectionEquality([Equality base = const DefaultEquality()])
+      : _base = base,
+        _unordered = false;
+
+  /// Creates a deep equality on collections where the order of lists and
+  /// iterables are not considered important. That is, lists and iterables are
+  /// treated as unordered iterables.
+  const DeepCollectionEquality.unordered(
+      [Equality base = const DefaultEquality()])
+      : _base = base,
+        _unordered = true;
+
+  bool equals(e1, e2) {
+    if (e1 is Set) {
+      return e2 is Set && new SetEquality(this).equals(e1, e2);
+    }
+    if (e1 is Map) {
+      return e2 is Map &&
+          new MapEquality(keys: this, values: this).equals(e1, e2);
+    }
+    if (!_unordered) {
+      if (e1 is List) {
+        return e2 is List && new ListEquality(this).equals(e1, e2);
+      }
+      if (e1 is Iterable) {
+        return e2 is Iterable && new IterableEquality(this).equals(e1, e2);
+      }
+    } else if (e1 is Iterable) {
+      if (e1 is List != e2 is List) return false;
+      return e2 is Iterable &&
+          new UnorderedIterableEquality(this).equals(e1, e2);
+    }
+    return _base.equals(e1, e2);
+  }
+
+  int hash(Object o) {
+    if (o is Set) return new SetEquality(this).hash(o);
+    if (o is Map) return new MapEquality(keys: this, values: this).hash(o);
+    if (!_unordered) {
+      if (o is List) return new ListEquality(this).hash(o);
+      if (o is Iterable) return new IterableEquality(this).hash(o);
+    } else if (o is Iterable) {
+      return new UnorderedIterableEquality(this).hash(o);
+    }
+    return _base.hash(o);
+  }
+
+  bool isValidKey(Object o) => o is Iterable || o is Map || _base.isValidKey(o);
+}
+
+/// String equality that's insensitive to differences in ASCII case.
+///
+/// Non-ASCII characters are compared as-is, with no conversion.
+class CaseInsensitiveEquality implements Equality<String> {
+  const CaseInsensitiveEquality();
+
+  bool equals(String string1, String string2) =>
+      equalsIgnoreAsciiCase(string1, string2);
+
+  int hash(String string) => hashIgnoreAsciiCase(string);
+
+  bool isValidKey(Object object) => object is String;
+}
diff --git a/packages/collection/lib/src/equality_map.dart b/packages/collection/lib/src/equality_map.dart
new file mode 100644
index 0000000..14f074c
--- /dev/null
+++ b/packages/collection/lib/src/equality_map.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2016, 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:collection';
+
+import 'equality.dart';
+import 'wrappers.dart';
+
+/// A [Map] whose key equality is determined by an [Equality] object.
+class EqualityMap<K, V> extends DelegatingMap<K, V> {
+  /// Creates a map with equality based on [equality].
+  EqualityMap(Equality<K> equality)
+      : super(new LinkedHashMap(
+            equals: equality.equals,
+            hashCode: equality.hash,
+            isValidKey: equality.isValidKey));
+
+  /// Creates a map with equality based on [equality] that contains all
+  /// key-value pairs of [other].
+  ///
+  /// If [other] has multiple keys that are equivalent according to [equality],
+  /// the last one reached during iteration takes precedence.
+  EqualityMap.from(Equality<K> equality, Map<K, V> other)
+      : super(new LinkedHashMap(
+            equals: equality.equals,
+            hashCode: equality.hash,
+            isValidKey: equality.isValidKey)) {
+    addAll(other);
+  }
+}
diff --git a/packages/collection/lib/src/equality_set.dart b/packages/collection/lib/src/equality_set.dart
new file mode 100644
index 0000000..6f6a426
--- /dev/null
+++ b/packages/collection/lib/src/equality_set.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2016, 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:collection';
+
+import 'equality.dart';
+import 'wrappers.dart';
+
+/// A [Map] whose key equality is determined by an [Equality] object.
+class EqualitySet<E> extends DelegatingSet<E> {
+  /// Creates a set with equality based on [equality].
+  EqualitySet(Equality<E> equality)
+      : super(new LinkedHashSet(
+            equals: equality.equals,
+            hashCode: equality.hash,
+            isValidKey: equality.isValidKey));
+
+  /// Creates a set with equality based on [equality] that contains all
+  /// elements in [other].
+  ///
+  /// If [other] has multiple values that are equivalent according to
+  /// [equality], the first one reached during iteration takes precedence.
+  EqualitySet.from(Equality<E> equality, Iterable<E> other)
+      : super(new LinkedHashSet(
+            equals: equality.equals,
+            hashCode: equality.hash,
+            isValidKey: equality.isValidKey)) {
+    addAll(other);
+  }
+}
diff --git a/packages/collection/lib/src/functions.dart b/packages/collection/lib/src/functions.dart
new file mode 100644
index 0000000..b4ff9a9
--- /dev/null
+++ b/packages/collection/lib/src/functions.dart
@@ -0,0 +1,203 @@
+// Copyright (c) 2016, 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:collection';
+import 'dart:math' as math;
+
+import 'utils.dart';
+
+// TODO(nweiz): When sdk#26488 is fixed, use overloads to ensure that if [key]
+// or [value] isn't passed, `K2`/`V2` defaults to `K1`/`V1`, respectively.
+/// Creates a new map from [map] with new keys and values.
+///
+/// The return values of [key] are used as the keys and the return values of
+/// [value] are used as the values for the new map.
+Map<K2, V2> mapMap<K1, V1, K2, V2>(Map<K1, V1> map,
+    {K2 key(K1 key, V1 value), V2 value(K1 key, V1 value)}) {
+  key ??= (mapKey, _) => mapKey as K2;
+  value ??= (_, mapValue) => mapValue as V2;
+
+  var result = <K2, V2>{};
+  map.forEach((mapKey, mapValue) {
+    result[key(mapKey, mapValue)] = value(mapKey, mapValue);
+  });
+  return result;
+}
+
+/// Returns a new map with all key/value pairs in both [map1] and [map2].
+///
+/// If there are keys that occur in both maps, the [value] function is used to
+/// select the value that goes into the resulting map based on the two original
+/// values. If [value] is omitted, the value from [map2] is used.
+Map<K, V> mergeMaps<K, V>(Map<K, V> map1, Map<K, V> map2,
+    {V value(V value1, V value2)}) {
+  var result = new Map<K, V>.from(map1);
+  if (value == null) return result..addAll(map2);
+
+  map2.forEach((key, mapValue) {
+    result[key] =
+        result.containsKey(key) ? value(result[key], mapValue) : mapValue;
+  });
+  return result;
+}
+
+/// Groups the elements in [values] by the value returned by [key].
+///
+/// Returns a map from keys computed by [key] to a list of all values for which
+/// [key] returns that key. The values appear in the list in the same relative
+/// order as in [values].
+Map<T, List<S>> groupBy<S, T>(Iterable<S> values, T key(S element)) {
+  var map = <T, List<S>>{};
+  for (var element in values) {
+    var list = map.putIfAbsent(key(element), () => []);
+    list.add(element);
+  }
+  return map;
+}
+
+/// Returns the element of [values] for which [orderBy] returns the minimum
+/// value.
+///
+/// The values returned by [orderBy] are compared using the [compare] function.
+/// If [compare] is omitted, values must implement [Comparable<T>] and they are
+/// compared using their [Comparable.compareTo].
+S minBy<S, T>(Iterable<S> values, T orderBy(S element),
+    {int compare(T value1, T value2)}) {
+  compare ??= defaultCompare<T>();
+
+  S minValue;
+  T minOrderBy;
+  for (var element in values) {
+    var elementOrderBy = orderBy(element);
+    if (minOrderBy == null || compare(elementOrderBy, minOrderBy) < 0) {
+      minValue = element;
+      minOrderBy = elementOrderBy;
+    }
+  }
+  return minValue;
+}
+
+/// Returns the element of [values] for which [orderBy] returns the maximum
+/// value.
+///
+/// The values returned by [orderBy] are compared using the [compare] function.
+/// If [compare] is omitted, values must implement [Comparable<T>] and they are
+/// compared using their [Comparable.compareTo].
+S maxBy<S, T>(Iterable<S> values, T orderBy(S element),
+    {int compare(T value1, T value2)}) {
+  compare ??= defaultCompare<T>();
+
+  S maxValue;
+  T maxOrderBy;
+  for (var element in values) {
+    var elementOrderBy = orderBy(element);
+    if (maxOrderBy == null || compare(elementOrderBy, maxOrderBy) > 0) {
+      maxValue = element;
+      maxOrderBy = elementOrderBy;
+    }
+  }
+  return maxValue;
+}
+
+/// Returns the [transitive closure][] of [graph].
+///
+/// [transitive closure]: https://en.wikipedia.org/wiki/Transitive_closure
+///
+/// Interprets [graph] as a directed graph with a vertex for each key and edges
+/// from each key to the values that the key maps to.
+///
+/// Assumes that every vertex in the graph has a key to represent it, even if
+/// that vertex has no outgoing edges. This isn't checked, but if it's not
+/// satisfied, the function may crash or provide unexpected output. For example,
+/// `{"a": ["b"]}` is not valid, but `{"a": ["b"], "b": []}` is.
+Map<T, Set<T>> transitiveClosure<T>(Map<T, Iterable<T>> graph) {
+  // This uses [Warshall's algorithm][], modified not to add a vertex from each
+  // node to itself.
+  //
+  // [Warshall's algorithm]: https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm#Applications_and_generalizations.
+  var result = <T, Set<T>>{};
+  graph.forEach((vertex, edges) {
+    result[vertex] = new Set<T>.from(edges);
+  });
+
+  // Lists are faster to iterate than maps, so we create a list since we're
+  // iterating repeatedly.
+  var keys = graph.keys.toList();
+  for (var vertex1 in keys) {
+    for (var vertex2 in keys) {
+      for (var vertex3 in keys) {
+        if (result[vertex2].contains(vertex1) &&
+            result[vertex1].contains(vertex3)) {
+          result[vertex2].add(vertex3);
+        }
+      }
+    }
+  }
+
+  return result;
+}
+
+/// Returns the [strongly connected components][] of [graph], in topological
+/// order.
+///
+/// [strongly connected components]: https://en.wikipedia.org/wiki/Strongly_connected_component
+///
+/// Interprets [graph] as a directed graph with a vertex for each key and edges
+/// from each key to the values that the key maps to.
+///
+/// Assumes that every vertex in the graph has a key to represent it, even if
+/// that vertex has no outgoing edges. This isn't checked, but if it's not
+/// satisfied, the function may crash or provide unexpected output. For example,
+/// `{"a": ["b"]}` is not valid, but `{"a": ["b"], "b": []}` is.
+List<Set<T>> stronglyConnectedComponents<T>(Map<T, Iterable<T>> graph) {
+  // This uses [Tarjan's algorithm][].
+  //
+  // [Tarjan's algorithm]: https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
+  var index = 0;
+  var stack = <T>[];
+  var result = <Set<T>>[];
+
+  // The order of these doesn't matter, so we use un-linked implementations to
+  // avoid unnecessary overhead.
+  var indices = new HashMap<T, int>();
+  var lowLinks = new HashMap<T, int>();
+  var onStack = new HashSet<T>();
+
+  strongConnect(T vertex) {
+    indices[vertex] = index;
+    lowLinks[vertex] = index;
+    index++;
+
+    stack.add(vertex);
+    onStack.add(vertex);
+
+    for (var successor in graph[vertex]) {
+      if (!indices.containsKey(successor)) {
+        strongConnect(successor);
+        lowLinks[vertex] = math.min(lowLinks[vertex], lowLinks[successor]);
+      } else if (onStack.contains(successor)) {
+        lowLinks[vertex] = math.min(lowLinks[vertex], lowLinks[successor]);
+      }
+    }
+
+    if (lowLinks[vertex] == indices[vertex]) {
+      var component = new Set<T>();
+      T neighbor;
+      do {
+        neighbor = stack.removeLast();
+        onStack.remove(neighbor);
+        component.add(neighbor);
+      } while (neighbor != vertex);
+      result.add(component);
+    }
+  }
+
+  for (var vertex in graph.keys) {
+    if (!indices.containsKey(vertex)) strongConnect(vertex);
+  }
+
+  // Tarjan's algorithm produces a reverse-topological sort, so we reverse it to
+  // get a normal topological sort.
+  return result.reversed.toList();
+}
diff --git a/packages/collection/lib/src/iterable_zip.dart b/packages/collection/lib/src/iterable_zip.dart
new file mode 100644
index 0000000..b983437
--- /dev/null
+++ b/packages/collection/lib/src/iterable_zip.dart
@@ -0,0 +1,52 @@
+// 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.
+
+import "dart:collection";
+
+/// Iterable that iterates over lists of values from other iterables.
+///
+/// When [iterator] is read, an [Iterator] is created for each [Iterable] in
+/// the [Iterable] passed to the constructor.
+///
+/// As long as all these iterators have a next value, those next values are
+/// combined into a single list, which becomes the next value of this
+/// [Iterable]'s [Iterator]. As soon as any of the iterators run out,
+/// the zipped iterator also stops.
+class IterableZip<T> extends IterableBase<List<T>> {
+  final Iterable<Iterable<T>> _iterables;
+
+  IterableZip(Iterable<Iterable<T>> iterables) : this._iterables = iterables;
+
+  /// Returns an iterator that combines values of the iterables' iterators
+  /// as long as they all have values.
+  Iterator<List<T>> get iterator {
+    var iterators = _iterables.map((x) => x.iterator).toList(growable: false);
+    // TODO(lrn): Return an empty iterator directly if iterators is empty?
+    return new _IteratorZip<T>(iterators);
+  }
+}
+
+class _IteratorZip<T> implements Iterator<List<T>> {
+  final List<Iterator<T>> _iterators;
+  List<T> _current;
+
+  _IteratorZip(List<Iterator<T>> iterators) : _iterators = iterators;
+
+  bool moveNext() {
+    if (_iterators.isEmpty) return false;
+    for (int i = 0; i < _iterators.length; i++) {
+      if (!_iterators[i].moveNext()) {
+        _current = null;
+        return false;
+      }
+    }
+    _current = new List(_iterators.length);
+    for (int i = 0; i < _iterators.length; i++) {
+      _current[i] = _iterators[i].current;
+    }
+    return true;
+  }
+
+  List<T> get current => _current;
+}
diff --git a/packages/collection/lib/src/priority_queue.dart b/packages/collection/lib/src/priority_queue.dart
new file mode 100644
index 0000000..89c3128
--- /dev/null
+++ b/packages/collection/lib/src/priority_queue.dart
@@ -0,0 +1,360 @@
+// 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.
+
+import "dart:collection";
+
+import "utils.dart";
+
+/// A priority queue is a priority based work-list of elements.
+///
+/// The queue allows adding elements, and removing them again in priority order.
+abstract class PriorityQueue<E> {
+  /// Creates an empty [PriorityQueue].
+  ///
+  /// The created [PriorityQueue] is a plain [HeapPriorityQueue].
+  ///
+  /// The [comparison] is a [Comparator] used to compare the priority of
+  /// elements. An element that compares as less than another element has
+  /// a higher priority.
+  ///
+  /// If [comparison] is omitted, it defaults to [Comparable.compare]. If this
+  /// is the case, `E` must implement [Comparable], and this is checked at
+  /// runtime for every comparison.
+  factory PriorityQueue([int comparison(E e1, E e2)]) = HeapPriorityQueue<E>;
+
+  /// Number of elements in the queue.
+  int get length;
+
+  /// Whether the queue is empty.
+  bool get isEmpty;
+
+  /// Whether the queue has any elements.
+  bool get isNotEmpty;
+
+  /// Checks if [object] is in the queue.
+  ///
+  /// Returns true if the element is found.
+  bool contains(E object);
+
+  /// Adds element to the queue.
+  ///
+  /// The element will become the next to be removed by [removeFirst]
+  /// when all elements with higher priority have been removed.
+  void add(E element);
+
+  /// Adds all [elements] to the queue.
+  void addAll(Iterable<E> elements);
+
+  /// Returns the next element that will be returned by [removeFirst].
+  ///
+  /// The element is not removed from the queue.
+  ///
+  /// The queue must not be empty when this method is called.
+  E get first;
+
+  /// Removes and returns the element with the highest priority.
+  ///
+  /// Repeatedly calling this method, without adding element in between,
+  /// is guaranteed to return elements in non-decreasing order as, specified by
+  /// [comparison].
+  ///
+  /// The queue must not be empty when this method is called.
+  E removeFirst();
+
+  /// Removes an element that compares equal to [element] in the queue.
+  ///
+  /// Returns true if an element is found and removed,
+  /// and false if no equal element is found.
+  bool remove(E element);
+
+  /// Removes all the elements from this queue and returns them.
+  ///
+  /// The returned iterable has no specified order.
+  Iterable<E> removeAll();
+
+  /// Removes all the elements from this queue.
+  void clear();
+
+  /// Returns a list of the elements of this queue in priority order.
+  ///
+  /// The queue is not modified.
+  ///
+  /// The order is the order that the elements would be in if they were
+  /// removed from this queue using [removeFirst].
+  List<E> toList();
+
+  /// Return a comparator based set using the comparator of this queue.
+  ///
+  /// The queue is not modified.
+  ///
+  /// The returned [Set] is currently a [SplayTreeSet],
+  /// but this may change as other ordered sets are implemented.
+  ///
+  /// The set contains all the elements of this queue.
+  /// If an element occurs more than once in the queue,
+  /// the set will contain it only once.
+  Set<E> toSet();
+}
+
+/// Heap based priority queue.
+///
+/// The elements are kept in a heap structure,
+/// where the element with the highest priority is immediately accessible,
+/// and modifying a single element takes
+/// logarithmic time in the number of elements on average.
+///
+/// * The [add] and [removeFirst] operations take amortized logarithmic time,
+///   O(log(n)), but may occasionally take linear time when growing the capacity
+///   of the heap.
+/// * The [addAll] operation works as doing repeated [add] operations.
+/// * The [first] getter takes constant time, O(1).
+/// * The [clear] and [removeAll] methods also take constant time, O(1).
+/// * The [contains] and [remove] operations may need to search the entire
+///   queue for the elements, taking O(n) time.
+/// * The [toList] operation effectively sorts the elements, taking O(n*log(n))
+///   time.
+/// * The [toSet] operation effectively adds each element to the new set, taking
+///   an expected O(n*log(n)) time.
+class HeapPriorityQueue<E> implements PriorityQueue<E> {
+  /// Initial capacity of a queue when created, or when added to after a
+  /// [clear].
+  ///
+  /// Number can be any positive value. Picking a size that gives a whole
+  /// number of "tree levels" in the heap is only done for aesthetic reasons.
+  static const int _INITIAL_CAPACITY = 7;
+
+  /// The comparison being used to compare the priority of elements.
+  final Comparator<E> comparison;
+
+  /// List implementation of a heap.
+  List<E> _queue = new List<E>(_INITIAL_CAPACITY);
+
+  /// Number of elements in queue.
+  ///
+  /// The heap is implemented in the first [_length] entries of [_queue].
+  int _length = 0;
+
+  /// Create a new priority queue.
+  ///
+  /// The [comparison] is a [Comparator] used to compare the priority of
+  /// elements. An element that compares as less than another element has
+  /// a higher priority.
+  ///
+  /// If [comparison] is omitted, it defaults to [Comparable.compare]. If this
+  /// is the case, `E` must implement [Comparable], and this is checked at
+  /// runtime for every comparison.
+  HeapPriorityQueue([int comparison(E e1, E e2)])
+      : comparison = comparison ?? defaultCompare<E>();
+
+  void add(E element) {
+    _add(element);
+  }
+
+  void addAll(Iterable<E> elements) {
+    for (E element in elements) {
+      _add(element);
+    }
+  }
+
+  void clear() {
+    _queue = const [];
+    _length = 0;
+  }
+
+  bool contains(E object) {
+    return _locate(object) >= 0;
+  }
+
+  E get first {
+    if (_length == 0) throw new StateError("No such element");
+    return _queue[0];
+  }
+
+  bool get isEmpty => _length == 0;
+
+  bool get isNotEmpty => _length != 0;
+
+  int get length => _length;
+
+  bool remove(E element) {
+    int index = _locate(element);
+    if (index < 0) return false;
+    E last = _removeLast();
+    if (index < _length) {
+      int comp = comparison(last, element);
+      if (comp <= 0) {
+        _bubbleUp(last, index);
+      } else {
+        _bubbleDown(last, index);
+      }
+    }
+    return true;
+  }
+
+  Iterable<E> removeAll() {
+    List<E> result = _queue;
+    int length = _length;
+    _queue = const [];
+    _length = 0;
+    return result.take(length);
+  }
+
+  E removeFirst() {
+    if (_length == 0) throw new StateError("No such element");
+    E result = _queue[0];
+    E last = _removeLast();
+    if (_length > 0) {
+      _bubbleDown(last, 0);
+    }
+    return result;
+  }
+
+  List<E> toList() {
+    List<E> list = new List<E>()..length = _length;
+    list.setRange(0, _length, _queue);
+    list.sort(comparison);
+    return list;
+  }
+
+  Set<E> toSet() {
+    Set<E> set = new SplayTreeSet<E>(comparison);
+    for (int i = 0; i < _length; i++) {
+      set.add(_queue[i]);
+    }
+    return set;
+  }
+
+  /// Returns some representation of the queue.
+  ///
+  /// The format isn't significant, and may change in the future.
+  String toString() {
+    return _queue.take(_length).toString();
+  }
+
+  /// Add element to the queue.
+  ///
+  /// Grows the capacity if the backing list is full.
+  void _add(E element) {
+    if (_length == _queue.length) _grow();
+    _bubbleUp(element, _length++);
+  }
+
+  /// Find the index of an object in the heap.
+  ///
+  /// Returns -1 if the object is not found.
+  int _locate(E object) {
+    if (_length == 0) return -1;
+    // Count positions from one instead of zero. This gives the numbers
+    // some nice properties. For example, all right children are odd,
+    // their left sibling is even, and the parent is found by shifting
+    // right by one.
+    // Valid range for position is [1.._length], inclusive.
+    int position = 1;
+    // Pre-order depth first search, omit child nodes if the current
+    // node has lower priority than [object], because all nodes lower
+    // in the heap will also have lower priority.
+    do {
+      int index = position - 1;
+      E element = _queue[index];
+      int comp = comparison(element, object);
+      if (comp == 0) return index;
+      if (comp < 0) {
+        // Element may be in subtree.
+        // Continue with the left child, if it is there.
+        int leftChildPosition = position * 2;
+        if (leftChildPosition <= _length) {
+          position = leftChildPosition;
+          continue;
+        }
+      }
+      // Find the next right sibling or right ancestor sibling.
+      do {
+        while (position.isOdd) {
+          // While position is a right child, go to the parent.
+          position >>= 1;
+        }
+        // Then go to the right sibling of the left-child.
+        position += 1;
+      } while (position > _length); // Happens if last element is a left child.
+    } while (position != 1); // At root again. Happens for right-most element.
+    return -1;
+  }
+
+  E _removeLast() {
+    int newLength = _length - 1;
+    E last = _queue[newLength];
+    _queue[newLength] = null;
+    _length = newLength;
+    return last;
+  }
+
+  /// Place [element] in heap at [index] or above.
+  ///
+  /// Put element into the empty cell at `index`.
+  /// While the `element` has higher priority than the
+  /// parent, swap it with the parent.
+  void _bubbleUp(E element, int index) {
+    while (index > 0) {
+      int parentIndex = (index - 1) ~/ 2;
+      E parent = _queue[parentIndex];
+      if (comparison(element, parent) > 0) break;
+      _queue[index] = parent;
+      index = parentIndex;
+    }
+    _queue[index] = element;
+  }
+
+  /// Place [element] in heap at [index] or above.
+  ///
+  /// Put element into the empty cell at `index`.
+  /// While the `element` has lower priority than either child,
+  /// swap it with the highest priority child.
+  void _bubbleDown(E element, int index) {
+    int rightChildIndex = index * 2 + 2;
+    while (rightChildIndex < _length) {
+      int leftChildIndex = rightChildIndex - 1;
+      E leftChild = _queue[leftChildIndex];
+      E rightChild = _queue[rightChildIndex];
+      int comp = comparison(leftChild, rightChild);
+      int minChildIndex;
+      E minChild;
+      if (comp < 0) {
+        minChild = leftChild;
+        minChildIndex = leftChildIndex;
+      } else {
+        minChild = rightChild;
+        minChildIndex = rightChildIndex;
+      }
+      comp = comparison(element, minChild);
+      if (comp <= 0) {
+        _queue[index] = element;
+        return;
+      }
+      _queue[index] = minChild;
+      index = minChildIndex;
+      rightChildIndex = index * 2 + 2;
+    }
+    int leftChildIndex = rightChildIndex - 1;
+    if (leftChildIndex < _length) {
+      E child = _queue[leftChildIndex];
+      int comp = comparison(element, child);
+      if (comp > 0) {
+        _queue[index] = child;
+        index = leftChildIndex;
+      }
+    }
+    _queue[index] = element;
+  }
+
+  /// Grows the capacity of the list holding the heap.
+  ///
+  /// Called when the list is full.
+  void _grow() {
+    int newCapacity = _queue.length * 2 + 1;
+    if (newCapacity < _INITIAL_CAPACITY) newCapacity = _INITIAL_CAPACITY;
+    List<E> newQueue = new List<E>(newCapacity);
+    newQueue.setRange(0, _length, _queue);
+    _queue = newQueue;
+  }
+}
diff --git a/packages/collection/lib/src/queue_list.dart b/packages/collection/lib/src/queue_list.dart
new file mode 100644
index 0000000..224ac86
--- /dev/null
+++ b/packages/collection/lib/src/queue_list.dart
@@ -0,0 +1,233 @@
+// 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.
+
+import 'dart:collection';
+
+/// A class that efficiently implements both [Queue] and [List].
+// TODO(nweiz): Currently this code is copied almost verbatim from
+// dart:collection. The only changes are to implement List and to remove methods
+// that are redundant with ListMixin. Remove or simplify it when issue 21330 is
+// fixed.
+class QueueList<E> extends Object with ListMixin<E> implements Queue<E> {
+  static const int _INITIAL_CAPACITY = 8;
+  List<E> _table;
+  int _head;
+  int _tail;
+
+  /// Create an empty queue.
+  ///
+  /// If [initialCapacity] is given, prepare the queue for at least that many
+  /// elements.
+  QueueList([int initialCapacity])
+      : _head = 0,
+        _tail = 0 {
+    if (initialCapacity == null || initialCapacity < _INITIAL_CAPACITY) {
+      initialCapacity = _INITIAL_CAPACITY;
+    } else if (!_isPowerOf2(initialCapacity)) {
+      initialCapacity = _nextPowerOf2(initialCapacity);
+    }
+    assert(_isPowerOf2(initialCapacity));
+    _table = new List<E>(initialCapacity);
+  }
+
+  /// Create a queue initially containing the elements of [source].
+  factory QueueList.from(Iterable<E> source) {
+    if (source is List) {
+      int length = source.length;
+      QueueList<E> queue = new QueueList(length + 1);
+      assert(queue._table.length > length);
+      var sourceList = source;
+      queue._table.setRange(0, length, sourceList, 0);
+      queue._tail = length;
+      return queue;
+    } else {
+      return new QueueList<E>()..addAll(source);
+    }
+  }
+
+  // Collection interface.
+
+  void add(E element) {
+    _add(element);
+  }
+
+  void addAll(Iterable<E> elements) {
+    if (elements is List) {
+      var list = elements;
+      int addCount = list.length;
+      int length = this.length;
+      if (length + addCount >= _table.length) {
+        _preGrow(length + addCount);
+        // After preGrow, all elements are at the start of the list.
+        _table.setRange(length, length + addCount, list, 0);
+        _tail += addCount;
+      } else {
+        // Adding addCount elements won't reach _head.
+        int endSpace = _table.length - _tail;
+        if (addCount < endSpace) {
+          _table.setRange(_tail, _tail + addCount, list, 0);
+          _tail += addCount;
+        } else {
+          int preSpace = addCount - endSpace;
+          _table.setRange(_tail, _tail + endSpace, list, 0);
+          _table.setRange(0, preSpace, list, endSpace);
+          _tail = preSpace;
+        }
+      }
+    } else {
+      for (E element in elements) _add(element);
+    }
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  QueueList<T> cast<T>() {
+    throw new UnimplementedError('cast');
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  QueueList<T> retype<T>() {
+    throw new UnimplementedError('retype');
+  }
+
+  String toString() => IterableBase.iterableToFullString(this, "{", "}");
+
+  // Queue interface.
+
+  void addLast(E element) {
+    _add(element);
+  }
+
+  void addFirst(E element) {
+    _head = (_head - 1) & (_table.length - 1);
+    _table[_head] = element;
+    if (_head == _tail) _grow();
+  }
+
+  E removeFirst() {
+    if (_head == _tail) throw new StateError("No element");
+    E result = _table[_head];
+    _table[_head] = null;
+    _head = (_head + 1) & (_table.length - 1);
+    return result;
+  }
+
+  E removeLast() {
+    if (_head == _tail) throw new StateError("No element");
+    _tail = (_tail - 1) & (_table.length - 1);
+    E result = _table[_tail];
+    _table[_tail] = null;
+    return result;
+  }
+
+  // List interface.
+
+  int get length => (_tail - _head) & (_table.length - 1);
+
+  set length(int value) {
+    if (value < 0) throw new RangeError("Length $value may not be negative.");
+
+    int delta = value - length;
+    if (delta >= 0) {
+      if (_table.length <= value) {
+        _preGrow(value);
+      }
+      _tail = (_tail + delta) & (_table.length - 1);
+      return;
+    }
+
+    int newTail = _tail + delta; // [delta] is negative.
+    if (newTail >= 0) {
+      _table.fillRange(newTail, _tail, null);
+    } else {
+      newTail += _table.length;
+      _table.fillRange(0, _tail, null);
+      _table.fillRange(newTail, _table.length, null);
+    }
+    _tail = newTail;
+  }
+
+  E operator [](int index) {
+    if (index < 0 || index >= length) {
+      throw new RangeError("Index $index must be in the range [0..$length).");
+    }
+
+    return _table[(_head + index) & (_table.length - 1)];
+  }
+
+  void operator []=(int index, E value) {
+    if (index < 0 || index >= length) {
+      throw new RangeError("Index $index must be in the range [0..$length).");
+    }
+
+    _table[(_head + index) & (_table.length - 1)] = value;
+  }
+
+  // Internal helper functions.
+
+  /// Whether [number] is a power of two.
+  ///
+  /// Only works for positive numbers.
+  static bool _isPowerOf2(int number) => (number & (number - 1)) == 0;
+
+  /// Rounds [number] up to the nearest power of 2.
+  ///
+  /// If [number] is a power of 2 already, it is returned.
+  ///
+  /// Only works for positive numbers.
+  static int _nextPowerOf2(int number) {
+    assert(number > 0);
+    number = (number << 1) - 1;
+    for (;;) {
+      int nextNumber = number & (number - 1);
+      if (nextNumber == 0) return number;
+      number = nextNumber;
+    }
+  }
+
+  /// Adds element at end of queue. Used by both [add] and [addAll].
+  void _add(E element) {
+    _table[_tail] = element;
+    _tail = (_tail + 1) & (_table.length - 1);
+    if (_head == _tail) _grow();
+  }
+
+  /// Grow the table when full.
+  void _grow() {
+    List<E> newTable = new List<E>(_table.length * 2);
+    int split = _table.length - _head;
+    newTable.setRange(0, split, _table, _head);
+    newTable.setRange(split, split + _head, _table, 0);
+    _head = 0;
+    _tail = _table.length;
+    _table = newTable;
+  }
+
+  int _writeToList(List<E> target) {
+    assert(target.length >= length);
+    if (_head <= _tail) {
+      int length = _tail - _head;
+      target.setRange(0, length, _table, _head);
+      return length;
+    } else {
+      int firstPartSize = _table.length - _head;
+      target.setRange(0, firstPartSize, _table, _head);
+      target.setRange(firstPartSize, firstPartSize + _tail, _table, 0);
+      return _tail + firstPartSize;
+    }
+  }
+
+  /// Grows the table even if it is not full.
+  void _preGrow(int newElementCount) {
+    assert(newElementCount >= length);
+
+    // Add 1.5x extra room to ensure that there's room for more elements after
+    // expansion.
+    newElementCount += newElementCount >> 1;
+    int newCapacity = _nextPowerOf2(newElementCount);
+    List<E> newTable = new List<E>(newCapacity);
+    _tail = _writeToList(newTable);
+    _table = newTable;
+    _head = 0;
+  }
+}
diff --git a/packages/collection/lib/src/typed_wrappers.dart b/packages/collection/lib/src/typed_wrappers.dart
new file mode 100644
index 0000000..cef2a58
--- /dev/null
+++ b/packages/collection/lib/src/typed_wrappers.dart
@@ -0,0 +1,471 @@
+// Copyright (c) 2016, 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:collection";
+import "dart:math" as math;
+
+import "wrappers.dart";
+
+typedef F _UnaryFunction<E, F>(E argument);
+
+/// The base class for delegating, type-asserting iterables.
+///
+/// Subclasses can provide a [_base] that should be delegated to. Unlike
+/// [TypeSafeIterable], this allows the base to be created on demand.
+abstract class _TypeSafeIterableBase<E> implements Iterable<E> {
+  /// The base iterable to which operations are delegated.
+  Iterable get _base;
+
+  _TypeSafeIterableBase();
+
+  bool any(bool test(E element)) => _base.any(_validate(test));
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Iterable<T> cast<T>() {
+    throw new UnimplementedError('cast');
+  }
+
+  bool contains(Object element) => _base.contains(element);
+
+  E elementAt(int index) => _base.elementAt(index) as E;
+
+  bool every(bool test(E element)) => _base.every(_validate(test));
+
+  Iterable<T> expand<T>(Iterable<T> f(E element)) => _base.expand(_validate(f));
+
+  E get first => _base.first as E;
+
+  E firstWhere(bool test(E element), {E orElse()}) =>
+      _base.firstWhere(_validate(test), orElse: orElse) as E;
+
+  T fold<T>(T initialValue, T combine(T previousValue, E element)) =>
+      _base.fold(initialValue,
+          (previousValue, element) => combine(previousValue, element as E));
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Iterable<E> followedBy(Iterable<E> other) {
+    throw new UnimplementedError('followedBy');
+  }
+
+  void forEach(void f(E element)) => _base.forEach(_validate(f));
+
+  bool get isEmpty => _base.isEmpty;
+
+  bool get isNotEmpty => _base.isNotEmpty;
+
+  Iterator<E> get iterator => _base.map((element) => element as E).iterator;
+
+  String join([String separator = ""]) => _base.join(separator);
+
+  E get last => _base.last as E;
+
+  E lastWhere(bool test(E element), {E orElse()}) =>
+      _base.lastWhere(_validate(test), orElse: orElse) as E;
+
+  int get length => _base.length;
+
+  Iterable<T> map<T>(T f(E element)) => _base.map(_validate(f));
+
+  E reduce(E combine(E value, E element)) =>
+      _base.reduce((value, element) => combine(value as E, element as E)) as E;
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Iterable<T> retype<T>() {
+    throw new UnimplementedError('retype');
+  }
+
+  E get single => _base.single as E;
+
+  E singleWhere(bool test(E element), {E orElse()}) {
+    if (orElse != null) throw new UnimplementedError('singleWhere:orElse');
+    return _base.singleWhere(_validate(test)) as E;
+  }
+
+  Iterable<E> skip(int n) => new TypeSafeIterable<E>(_base.skip(n));
+
+  Iterable<E> skipWhile(bool test(E value)) =>
+      new TypeSafeIterable<E>(_base.skipWhile(_validate(test)));
+
+  Iterable<E> take(int n) => new TypeSafeIterable<E>(_base.take(n));
+
+  Iterable<E> takeWhile(bool test(E value)) =>
+      new TypeSafeIterable<E>(_base.takeWhile(_validate(test)));
+
+  List<E> toList({bool growable: true}) =>
+      new TypeSafeList<E>(_base.toList(growable: growable));
+
+  Set<E> toSet() => new TypeSafeSet<E>(_base.toSet());
+
+  Iterable<E> where(bool test(E element)) =>
+      new TypeSafeIterable<E>(_base.where(_validate(test)));
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Iterable<T> whereType<T>() {
+    throw new UnimplementedError('whereType');
+  }
+
+  String toString() => _base.toString();
+
+  /// Returns a version of [function] that asserts that its argument is an
+  /// instance of `E`.
+  _UnaryFunction<dynamic, F> _validate<F>(F function(E value)) =>
+      (value) => function(value as E);
+}
+
+/// An [Iterable] that asserts the types of values in a base iterable.
+///
+/// This is instantiated using [DelegatingIterable.typed].
+class TypeSafeIterable<E> extends _TypeSafeIterableBase<E>
+    implements DelegatingIterable<E> {
+  final Iterable _base;
+
+  TypeSafeIterable(Iterable base) : _base = base;
+}
+
+/// A [List] that asserts the types of values in a base list.
+///
+/// This is instantiated using [DelegatingList.typed].
+class TypeSafeList<E> extends TypeSafeIterable<E> implements DelegatingList<E> {
+  TypeSafeList(List base) : super(base);
+
+  /// A [List]-typed getter for [_base].
+  List get _listBase => _base;
+
+  E operator [](int index) => _listBase[index] as E;
+
+  void operator []=(int index, E value) {
+    _listBase[index] = value;
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  List<E> operator +(List<E> other) {
+    throw new UnimplementedError('+');
+  }
+
+  void add(E value) {
+    _listBase.add(value);
+  }
+
+  void addAll(Iterable<E> iterable) {
+    _listBase.addAll(iterable);
+  }
+
+  Map<int, E> asMap() => new TypeSafeMap<int, E>(_listBase.asMap());
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  List<T> cast<T>() {
+    throw new UnimplementedError('cast');
+  }
+
+  void clear() {
+    _listBase.clear();
+  }
+
+  void fillRange(int start, int end, [E fillValue]) {
+    _listBase.fillRange(start, end, fillValue);
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  set first(E value) {
+    if (this.isEmpty) throw new RangeError.index(0, this);
+    this[0] = value;
+  }
+
+  Iterable<E> getRange(int start, int end) =>
+      new TypeSafeIterable<E>(_listBase.getRange(start, end));
+
+  int indexOf(E element, [int start = 0]) => _listBase.indexOf(element, start);
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  int indexWhere(bool test(E element), [int start = 0]) {
+    throw new UnimplementedError('indexWhere');
+  }
+
+  void insert(int index, E element) {
+    _listBase.insert(index, element);
+  }
+
+  void insertAll(int index, Iterable<E> iterable) {
+    _listBase.insertAll(index, iterable);
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  set last(E value) {
+    if (this.isEmpty) throw new RangeError.index(0, this);
+    this[this.length - 1] = value;
+  }
+
+  int lastIndexOf(E element, [int start]) =>
+      _listBase.lastIndexOf(element, start);
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  int lastIndexWhere(bool test(E element), [int start]) {
+    throw new UnimplementedError('lastIndexWhere');
+  }
+
+  set length(int newLength) {
+    _listBase.length = newLength;
+  }
+
+  bool remove(Object value) => _listBase.remove(value);
+
+  E removeAt(int index) => _listBase.removeAt(index) as E;
+
+  E removeLast() => _listBase.removeLast() as E;
+
+  void removeRange(int start, int end) {
+    _listBase.removeRange(start, end);
+  }
+
+  void removeWhere(bool test(E element)) {
+    _listBase.removeWhere(_validate(test));
+  }
+
+  void replaceRange(int start, int end, Iterable<E> iterable) {
+    _listBase.replaceRange(start, end, iterable);
+  }
+
+  void retainWhere(bool test(E element)) {
+    _listBase.retainWhere(_validate(test));
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  List<T> retype<T>() {
+    throw new UnimplementedError('retype');
+  }
+
+  Iterable<E> get reversed => new TypeSafeIterable<E>(_listBase.reversed);
+
+  void setAll(int index, Iterable<E> iterable) {
+    _listBase.setAll(index, iterable);
+  }
+
+  void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) {
+    _listBase.setRange(start, end, iterable, skipCount);
+  }
+
+  void shuffle([math.Random random]) {
+    _listBase.shuffle(random);
+  }
+
+  void sort([int compare(E a, E b)]) {
+    if (compare == null) {
+      _listBase.sort();
+    } else {
+      _listBase.sort((a, b) => compare(a as E, b as E));
+    }
+  }
+
+  List<E> sublist(int start, [int end]) =>
+      new TypeSafeList<E>(_listBase.sublist(start, end));
+}
+
+/// A [Set] that asserts the types of values in a base set.
+///
+/// This is instantiated using [DelegatingSet.typed].
+class TypeSafeSet<E> extends TypeSafeIterable<E> implements DelegatingSet<E> {
+  TypeSafeSet(Set base) : super(base);
+
+  /// A [Set]-typed getter for [_base].
+  Set get _setBase => _base;
+
+  bool add(E value) => _setBase.add(value);
+
+  void addAll(Iterable<E> elements) {
+    _setBase.addAll(elements);
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Set<T> cast<T>() {
+    throw new UnimplementedError('cast');
+  }
+
+  void clear() {
+    _setBase.clear();
+  }
+
+  bool containsAll(Iterable<Object> other) => _setBase.containsAll(other);
+
+  Set<E> difference(Set<Object> other) =>
+      new TypeSafeSet<E>(_setBase.difference(other));
+
+  Set<E> intersection(Set<Object> other) =>
+      new TypeSafeSet<E>(_setBase.intersection(other));
+
+  E lookup(Object element) => _setBase.lookup(element) as E;
+
+  bool remove(Object value) => _setBase.remove(value);
+
+  void removeAll(Iterable<Object> elements) {
+    _setBase.removeAll(elements);
+  }
+
+  void removeWhere(bool test(E element)) {
+    _setBase.removeWhere(_validate(test));
+  }
+
+  void retainAll(Iterable<Object> elements) {
+    _setBase.retainAll(elements);
+  }
+
+  void retainWhere(bool test(E element)) {
+    _setBase.retainWhere(_validate(test));
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Set<T> retype<T>() {
+    throw new UnimplementedError('retype');
+  }
+
+  Set<E> union(Set<E> other) => new TypeSafeSet<E>(_setBase.union(other));
+}
+
+/// A [Queue] that asserts the types of values in a base queue.
+///
+/// This is instantiated using [DelegatingQueue.typed].
+class TypeSafeQueue<E> extends TypeSafeIterable<E>
+    implements DelegatingQueue<E> {
+  TypeSafeQueue(Queue queue) : super(queue);
+
+  /// A [Queue]-typed getter for [_base].
+  Queue get _baseQueue => _base;
+
+  void add(E value) {
+    _baseQueue.add(value);
+  }
+
+  void addAll(Iterable<E> iterable) {
+    _baseQueue.addAll(iterable);
+  }
+
+  void addFirst(E value) {
+    _baseQueue.addFirst(value);
+  }
+
+  void addLast(E value) {
+    _baseQueue.addLast(value);
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Queue<T> cast<T>() {
+    throw new UnimplementedError('cast');
+  }
+
+  void clear() {
+    _baseQueue.clear();
+  }
+
+  bool remove(Object object) => _baseQueue.remove(object);
+
+  void removeWhere(bool test(E element)) {
+    _baseQueue.removeWhere(_validate(test));
+  }
+
+  void retainWhere(bool test(E element)) {
+    _baseQueue.retainWhere(_validate(test));
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Queue<T> retype<T>() {
+    throw new UnimplementedError('retype');
+  }
+
+  E removeFirst() => _baseQueue.removeFirst() as E;
+
+  E removeLast() => _baseQueue.removeLast() as E;
+}
+
+/// A [Map] that asserts the types of keys and values in a base map.
+///
+/// This is instantiated using [DelegatingMap.typed].
+class TypeSafeMap<K, V> implements DelegatingMap<K, V> {
+  /// The base map to which operations are delegated.
+  final Map _base;
+
+  TypeSafeMap(Map base) : _base = base;
+
+  V operator [](Object key) => _base[key] as V;
+
+  void operator []=(K key, V value) {
+    _base[key] = value;
+  }
+
+  void addAll(Map<K, V> other) {
+    _base.addAll(other);
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  void addEntries(Iterable<Object> entries) {
+    // Change Iterable<Object> to Iterable<MapEntry<K, V>> when
+    // the MapEntry class has been added.
+    throw new UnimplementedError('addEntries');
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Map<K2, V2> cast<K2, V2>() {
+    throw new UnimplementedError('cast');
+  }
+
+  void clear() {
+    _base.clear();
+  }
+
+  bool containsKey(Object key) => _base.containsKey(key);
+
+  bool containsValue(Object value) => _base.containsValue(value);
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Iterable<Null> get entries {
+    // Change Iterable<Null> to Iterable<MapEntry<K, V>> when
+    // the MapEntry class has been added.
+    throw new UnimplementedError('entries');
+  }
+
+  void forEach(void f(K key, V value)) {
+    _base.forEach((key, value) => f(key as K, value as V));
+  }
+
+  bool get isEmpty => _base.isEmpty;
+
+  bool get isNotEmpty => _base.isNotEmpty;
+
+  Iterable<K> get keys => new TypeSafeIterable<K>(_base.keys);
+
+  int get length => _base.length;
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Map<K2, V2> map<K2, V2>(Object transform(K key, V value)) {
+    // Change Object to MapEntry<K2, V2> when
+    // the MapEntry class has been added.
+    throw new UnimplementedError('map');
+  }
+
+  V putIfAbsent(K key, V ifAbsent()) => _base.putIfAbsent(key, ifAbsent) as V;
+
+  V remove(Object key) => _base.remove(key) as V;
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  void removeWhere(bool test(K key, V value)) {
+    throw new UnimplementedError('removeWhere');
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Map<K2, V2> retype<K2, V2>() {
+    throw new UnimplementedError('retype');
+  }
+
+  Iterable<V> get values => new TypeSafeIterable<V>(_base.values);
+
+  String toString() => _base.toString();
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  V update(K key, V update(V value), {V ifAbsent()}) {
+    throw new UnimplementedError('update');
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  void updateAll(V update(K key, V value)) {
+    throw new UnimplementedError('updateAll');
+  }
+}
diff --git a/packages/collection/lib/src/union_set.dart b/packages/collection/lib/src/union_set.dart
new file mode 100644
index 0000000..5d88b6b
--- /dev/null
+++ b/packages/collection/lib/src/union_set.dart
@@ -0,0 +1,88 @@
+// Copyright (c) 2016, 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:collection';
+
+import 'unmodifiable_wrappers.dart';
+
+/// A single set that provides a view of the union over a set of sets.
+///
+/// Since this is just a view, it reflects all changes in the underlying sets.
+///
+/// If an element is in multiple sets and the outer set is ordered, the version
+/// in the earliest inner set is preferred. Component sets are assumed to use
+/// `==` and `hashCode` for equality.
+class UnionSet<E> extends SetBase<E> with UnmodifiableSetMixin<E> {
+  /// The set of sets that this provides a view of.
+  final Set<Set<E>> _sets;
+
+  /// Whether the sets in [_sets] are guaranteed to be disjoint.
+  final bool _disjoint;
+
+  /// Creates a new set that's a view of the union of all sets in [sets].
+  ///
+  /// If any sets in [sets] change, this [UnionSet] reflects that change. If a
+  /// new set is added to [sets], this [UnionSet] reflects that as well.
+  ///
+  /// If [disjoint] is `true`, then all component sets must be disjoint. That
+  /// is, that they contain no elements in common. This makes many operations
+  /// including [length] more efficient. If the component sets turn out not to
+  /// be disjoint, some operations may behave inconsistently.
+  UnionSet(this._sets, {bool disjoint: false}) : _disjoint = disjoint;
+
+  /// Creates a new set that's a view of the union of all sets in [sets].
+  ///
+  /// If any sets in [sets] change, this [UnionSet] reflects that change.
+  /// However, unlike [new UnionSet], this creates a copy of its parameter, so
+  /// changes in [sets] aren't reflected in this [UnionSet].
+  ///
+  /// If [disjoint] is `true`, then all component sets must be disjoint. That
+  /// is, that they contain no elements in common. This makes many operations
+  /// including [length] more efficient. If the component sets turn out not to
+  /// be disjoint, some operations may behave inconsistently.
+  UnionSet.from(Iterable<Set<E>> sets, {bool disjoint: false})
+      : this(sets.toSet(), disjoint: disjoint);
+
+  int get length => _disjoint
+      ? _sets.fold(0, (length, set) => length + set.length)
+      : _iterable.length;
+
+  Iterator<E> get iterator => _iterable.iterator;
+
+  /// Returns an iterable over the contents of all the sets in [this].
+  Iterable<E> get _iterable =>
+      _disjoint ? _sets.expand((set) => set) : _dedupIterable;
+
+  /// Returns an iterable over the contents of all the sets in [this] that
+  /// de-duplicates elements.
+  ///
+  /// If the sets aren't guaranteed to be disjoint, this keeps track of the
+  /// elements we've already emitted so that we can de-duplicate them.
+  Iterable<E> get _dedupIterable {
+    var seen = new Set<E>();
+    return _sets.expand((set) => set).where((element) {
+      if (seen.contains(element)) return false;
+      seen.add(element);
+      return true;
+    });
+  }
+
+  bool contains(Object element) => _sets.any((set) => set.contains(element));
+
+  E lookup(Object element) {
+    if (element == null) return null;
+
+    return _sets
+        .map((set) => set.lookup(element))
+        .firstWhere((result) => result != null, orElse: () => null);
+  }
+
+  Set<E> toSet() {
+    var result = new Set<E>();
+    for (var set in _sets) {
+      result.addAll(set);
+    }
+    return result;
+  }
+}
diff --git a/packages/collection/lib/src/union_set_controller.dart b/packages/collection/lib/src/union_set_controller.dart
new file mode 100644
index 0000000..1d0eb74
--- /dev/null
+++ b/packages/collection/lib/src/union_set_controller.dart
@@ -0,0 +1,54 @@
+// Copyright (c) 2016, 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 'union_set.dart';
+
+/// A controller that exposes a view of the union of a collection of sets.
+///
+/// This is a convenience class for creating a [UnionSet] whose contents change
+/// over the lifetime of a class. For example:
+///
+/// ```dart
+/// class Engine {
+///   Set<Test> get activeTests => _activeTestsGroup.set;
+///   final _activeTestsGroup = new UnionSetController<Test>();
+///
+///   void addSuite(Suite suite) {
+///     _activeTestsGroup.add(suite.tests);
+///     _runSuite(suite);
+///     _activeTestsGroup.remove(suite.tests);
+///   }
+/// }
+/// ```
+class UnionSetController<E> {
+  /// The [UnionSet] that provides a view of the union of sets in [this].
+  UnionSet<E> get set => _set;
+  UnionSet<E> _set;
+
+  /// The sets whose union is exposed through [set].
+  final _sets = new Set<Set<E>>();
+
+  /// Creates a set of sets that provides a view of the union of those sets.
+  ///
+  /// If [disjoint] is `true`, this assumes that all component sets are
+  /// disjoint—that is, that they contain no elements in common. This makes
+  /// many operations including [length] more efficient.
+  UnionSetController({bool disjoint: false}) {
+    _set = new UnionSet<E>(_sets, disjoint: disjoint);
+  }
+
+  /// Adds the contents of [component] to [set].
+  ///
+  /// If the contents of [component] change over time, [set] will change
+  /// accordingly.
+  void add(Set<E> component) {
+    _sets.add(component);
+  }
+
+  /// Removes the contents of [component] to [set].
+  ///
+  /// If another set in [this] has overlapping elements with [component], those
+  /// elements will remain in [set].
+  bool remove(Set<E> component) => _sets.remove(component);
+}
diff --git a/packages/collection/lib/src/unmodifiable_wrappers.dart b/packages/collection/lib/src/unmodifiable_wrappers.dart
new file mode 100644
index 0000000..b0b5440
--- /dev/null
+++ b/packages/collection/lib/src/unmodifiable_wrappers.dart
@@ -0,0 +1,179 @@
+// 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.
+
+import 'empty_unmodifiable_set.dart';
+import 'wrappers.dart';
+
+export "dart:collection" show UnmodifiableListView, UnmodifiableMapView;
+
+/// A fixed-length list.
+///
+/// A `NonGrowableListView` contains a [List] object and ensures that
+/// its length does not change.
+/// Methods that would change the length of the list,
+/// such as [add] and [remove], throw an [UnsupportedError].
+/// All other methods work directly on the underlying list.
+///
+/// This class _does_ allow changes to the contents of the wrapped list.
+/// You can, for example, [sort] the list.
+/// Permitted operations defer to the wrapped list.
+class NonGrowableListView<E> extends DelegatingList<E>
+    with NonGrowableListMixin<E> {
+  NonGrowableListView(List<E> listBase) : super(listBase);
+}
+
+/// Mixin class that implements a throwing version of all list operations that
+/// change the List's length.
+abstract class NonGrowableListMixin<E> implements List<E> {
+  static T _throw<T>() {
+    throw new UnsupportedError(
+        "Cannot change the length of a fixed-length list");
+  }
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the length of the list are disallowed.
+  set length(int newLength) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the length of the list are disallowed.
+  bool add(E value) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the length of the list are disallowed.
+  void addAll(Iterable<E> iterable) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the length of the list are disallowed.
+  void insert(int index, E element) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the length of the list are disallowed.
+  void insertAll(int index, Iterable<E> iterable) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the length of the list are disallowed.
+  bool remove(Object value) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the length of the list are disallowed.
+  E removeAt(int index) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the length of the list are disallowed.
+  E removeLast() => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the length of the list are disallowed.
+  void removeWhere(bool test(E element)) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the length of the list are disallowed.
+  void retainWhere(bool test(E element)) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the length of the list are disallowed.
+  void removeRange(int start, int end) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the length of the list are disallowed.
+  void replaceRange(int start, int end, Iterable<E> iterable) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the length of the list are disallowed.
+  void clear() => _throw();
+}
+
+/// An unmodifiable set.
+///
+/// An UnmodifiableSetView contains a [Set] object and ensures
+/// that it does not change.
+/// Methods that would change the set,
+/// such as [add] and [remove], throw an [UnsupportedError].
+/// Permitted operations defer to the wrapped set.
+class UnmodifiableSetView<E> extends DelegatingSet<E>
+    with UnmodifiableSetMixin<E> {
+  UnmodifiableSetView(Set<E> setBase) : super(setBase);
+
+  /// An unmodifiable empty set.
+  ///
+  /// This is the same as `new UnmodifiableSetView(new Set())`, except that it
+  /// can be used in const contexts.
+  const factory UnmodifiableSetView.empty() = EmptyUnmodifiableSet<E>;
+}
+
+/// Mixin class that implements a throwing version of all set operations that
+/// change the Set.
+abstract class UnmodifiableSetMixin<E> implements Set<E> {
+  static T _throw<T>() {
+    throw new UnsupportedError("Cannot modify an unmodifiable Set");
+  }
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the set are disallowed.
+  bool add(E value) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the set are disallowed.
+  void addAll(Iterable<E> elements) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the set are disallowed.
+  bool remove(Object value) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the set are disallowed.
+  void removeAll(Iterable elements) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the set are disallowed.
+  void retainAll(Iterable elements) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the set are disallowed.
+  void removeWhere(bool test(E element)) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the set are disallowed.
+  void retainWhere(bool test(E element)) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the set are disallowed.
+  void clear() => _throw();
+}
+
+/// Mixin class that implements a throwing version of all map operations that
+/// change the Map.
+abstract class UnmodifiableMapMixin<K, V> implements Map<K, V> {
+  static T _throw<T>() {
+    throw new UnsupportedError("Cannot modify an unmodifiable Map");
+  }
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the map are disallowed.
+  void operator []=(K key, V value) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the map are disallowed.
+  V putIfAbsent(K key, V ifAbsent()) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the map are disallowed.
+  void addAll(Map<K, V> other) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the map are disallowed.
+  V remove(Object key) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the map are disallowed.
+  void clear() => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the map are disallowed.
+  set first(_) => _throw();
+
+  /// Throws an [UnsupportedError];
+  /// operations that change the map are disallowed.
+  set last(_) => _throw();
+}
diff --git a/packages/collection/lib/src/utils.dart b/packages/collection/lib/src/utils.dart
new file mode 100644
index 0000000..194f99c
--- /dev/null
+++ b/packages/collection/lib/src/utils.dart
@@ -0,0 +1,15 @@
+// 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.
+
+/// A pair of values.
+class Pair<E, F> {
+  E first;
+  F last;
+
+  Pair(this.first, this.last);
+}
+
+/// Returns a [Comparator] that asserts that its first argument is comparable.
+Comparator<T> defaultCompare<T>() =>
+    (value1, value2) => (value1 as Comparable).compareTo(value2);
diff --git a/packages/collection/lib/src/wrappers.dart b/packages/collection/lib/src/wrappers.dart
new file mode 100644
index 0000000..1abfd45
--- /dev/null
+++ b/packages/collection/lib/src/wrappers.dart
@@ -0,0 +1,751 @@
+// 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.
+
+import "dart:collection";
+import "dart:math" as math;
+
+import "typed_wrappers.dart";
+import "unmodifiable_wrappers.dart";
+
+typedef K _KeyForValue<K, V>(V value);
+
+/// A base class for delegating iterables.
+///
+/// Subclasses can provide a [_base] that should be delegated to. Unlike
+/// [DelegatingIterable], this allows the base to be created on demand.
+abstract class _DelegatingIterableBase<E> implements Iterable<E> {
+  Iterable<E> get _base;
+
+  const _DelegatingIterableBase();
+
+  bool any(bool test(E element)) => _base.any(test);
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Iterable<T> cast<T>() {
+    throw new UnimplementedError('cast');
+  }
+
+  bool contains(Object element) => _base.contains(element);
+
+  E elementAt(int index) => _base.elementAt(index);
+
+  bool every(bool test(E element)) => _base.every(test);
+
+  Iterable<T> expand<T>(Iterable<T> f(E element)) => _base.expand(f);
+
+  E get first => _base.first;
+
+  E firstWhere(bool test(E element), {E orElse()}) =>
+      _base.firstWhere(test, orElse: orElse);
+
+  T fold<T>(T initialValue, T combine(T previousValue, E element)) =>
+      _base.fold(initialValue, combine);
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Iterable<E> followedBy(Iterable<E> other) {
+    throw new UnimplementedError('followedBy');
+  }
+
+  void forEach(void f(E element)) => _base.forEach(f);
+
+  bool get isEmpty => _base.isEmpty;
+
+  bool get isNotEmpty => _base.isNotEmpty;
+
+  Iterator<E> get iterator => _base.iterator;
+
+  String join([String separator = ""]) => _base.join(separator);
+
+  E get last => _base.last;
+
+  E lastWhere(bool test(E element), {E orElse()}) =>
+      _base.lastWhere(test, orElse: orElse);
+
+  int get length => _base.length;
+
+  Iterable<T> map<T>(T f(E element)) => _base.map(f);
+
+  E reduce(E combine(E value, E element)) => _base.reduce(combine);
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Iterable<T> retype<T>() {
+    throw new UnimplementedError('retype');
+  }
+
+  E get single => _base.single;
+
+  E singleWhere(bool test(E element), {E orElse()}) {
+    if (orElse != null) throw new UnimplementedError('singleWhere:orElse');
+    return _base.singleWhere(test);
+  }
+
+  Iterable<E> skip(int n) => _base.skip(n);
+
+  Iterable<E> skipWhile(bool test(E value)) => _base.skipWhile(test);
+
+  Iterable<E> take(int n) => _base.take(n);
+
+  Iterable<E> takeWhile(bool test(E value)) => _base.takeWhile(test);
+
+  List<E> toList({bool growable: true}) => _base.toList(growable: growable);
+
+  Set<E> toSet() => _base.toSet();
+
+  Iterable<E> where(bool test(E element)) => _base.where(test);
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Iterable<T> whereType<T>() {
+    throw new UnimplementedError("whereType");
+  }
+
+  String toString() => _base.toString();
+}
+
+/// An [Iterable] that delegates all operations to a base iterable.
+///
+/// This class can be used to hide non-`Iterable` methods of an iterable object,
+/// or it can be extended to add extra functionality on top of an existing
+/// iterable object.
+class DelegatingIterable<E> extends _DelegatingIterableBase<E> {
+  final Iterable<E> _base;
+
+  /// Creates a wrapper that forwards operations to [base].
+  const DelegatingIterable(Iterable<E> base) : _base = base;
+
+  /// Creates a wrapper that asserts the types of values in [base].
+  ///
+  /// This soundly converts an [Iterable] without a generic type to an
+  /// `Iterable<E>` by asserting that its elements are instances of `E` whenever
+  /// they're accessed. If they're not, it throws a [CastError].
+  ///
+  /// This forwards all operations to [base], so any changes in [base] will be
+  /// reflected in [this]. If [base] is already an `Iterable<E>`, it's returned
+  /// unmodified.
+  static Iterable<E> typed<E>(Iterable base) =>
+      base is Iterable<E> ? base : new TypeSafeIterable<E>(base);
+}
+
+/// A [List] that delegates all operations to a base list.
+///
+/// This class can be used to hide non-`List` methods of a list object, or it
+/// can be extended to add extra functionality on top of an existing list
+/// object.
+class DelegatingList<E> extends DelegatingIterable<E> implements List<E> {
+  const DelegatingList(List<E> base) : super(base);
+
+  /// Creates a wrapper that asserts the types of values in [base].
+  ///
+  /// This soundly converts a [List] without a generic type to a `List<E>` by
+  /// asserting that its elements are instances of `E` whenever they're
+  /// accessed. If they're not, it throws a [CastError]. Note that even if an
+  /// operation throws a [CastError], it may still mutate the underlying
+  /// collection.
+  ///
+  /// This forwards all operations to [base], so any changes in [base] will be
+  /// reflected in [this]. If [base] is already a `List<E>`, it's returned
+  /// unmodified.
+  static List<E> typed<E>(List base) =>
+      base is List<E> ? base : new TypeSafeList<E>(base);
+
+  List<E> get _listBase => _base;
+
+  E operator [](int index) => _listBase[index];
+
+  void operator []=(int index, E value) {
+    _listBase[index] = value;
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  List<E> operator +(List<E> other) {
+    throw new UnimplementedError('+');
+  }
+
+  void add(E value) {
+    _listBase.add(value);
+  }
+
+  void addAll(Iterable<E> iterable) {
+    _listBase.addAll(iterable);
+  }
+
+  Map<int, E> asMap() => _listBase.asMap();
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  List<T> cast<T>() {
+    throw new UnimplementedError('cast');
+  }
+
+  void clear() {
+    _listBase.clear();
+  }
+
+  void fillRange(int start, int end, [E fillValue]) {
+    _listBase.fillRange(start, end, fillValue);
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  set first(E value) {
+    if (this.isEmpty) throw new RangeError.index(0, this);
+    this[0] = value;
+  }
+
+  Iterable<E> getRange(int start, int end) => _listBase.getRange(start, end);
+
+  int indexOf(E element, [int start = 0]) => _listBase.indexOf(element, start);
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  int indexWhere(bool test(E element), [int start = 0]) {
+    throw new UnimplementedError('indexWhere');
+  }
+
+  void insert(int index, E element) {
+    _listBase.insert(index, element);
+  }
+
+  insertAll(int index, Iterable<E> iterable) {
+    _listBase.insertAll(index, iterable);
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  set last(E value) {
+    if (this.isEmpty) throw new RangeError.index(0, this);
+    this[this.length - 1] = value;
+  }
+
+  int lastIndexOf(E element, [int start]) =>
+      _listBase.lastIndexOf(element, start);
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  int lastIndexWhere(bool test(E element), [int start]) {
+    throw new UnimplementedError('lastIndexWhere');
+  }
+
+  set length(int newLength) {
+    _listBase.length = newLength;
+  }
+
+  bool remove(Object value) => _listBase.remove(value);
+
+  E removeAt(int index) => _listBase.removeAt(index);
+
+  E removeLast() => _listBase.removeLast();
+
+  void removeRange(int start, int end) {
+    _listBase.removeRange(start, end);
+  }
+
+  void removeWhere(bool test(E element)) {
+    _listBase.removeWhere(test);
+  }
+
+  void replaceRange(int start, int end, Iterable<E> iterable) {
+    _listBase.replaceRange(start, end, iterable);
+  }
+
+  void retainWhere(bool test(E element)) {
+    _listBase.retainWhere(test);
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  List<T> retype<T>() {
+    throw new UnimplementedError('retype');
+  }
+
+  Iterable<E> get reversed => _listBase.reversed;
+
+  void setAll(int index, Iterable<E> iterable) {
+    _listBase.setAll(index, iterable);
+  }
+
+  void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) {
+    _listBase.setRange(start, end, iterable, skipCount);
+  }
+
+  void shuffle([math.Random random]) {
+    _listBase.shuffle(random);
+  }
+
+  void sort([int compare(E a, E b)]) {
+    _listBase.sort(compare);
+  }
+
+  List<E> sublist(int start, [int end]) => _listBase.sublist(start, end);
+}
+
+/// A [Set] that delegates all operations to a base set.
+///
+/// This class can be used to hide non-`Set` methods of a set object, or it can
+/// be extended to add extra functionality on top of an existing set object.
+class DelegatingSet<E> extends DelegatingIterable<E> implements Set<E> {
+  const DelegatingSet(Set<E> base) : super(base);
+
+  /// Creates a wrapper that asserts the types of values in [base].
+  ///
+  /// This soundly converts a [Set] without a generic type to a `Set<E>` by
+  /// asserting that its elements are instances of `E` whenever they're
+  /// accessed. If they're not, it throws a [CastError]. Note that even if an
+  /// operation throws a [CastError], it may still mutate the underlying
+  /// collection.
+  ///
+  /// This forwards all operations to [base], so any changes in [base] will be
+  /// reflected in [this]. If [base] is already a `Set<E>`, it's returned
+  /// unmodified.
+  static Set<E> typed<E>(Set base) =>
+      base is Set<E> ? base : new TypeSafeSet<E>(base);
+
+  Set<E> get _setBase => _base;
+
+  bool add(E value) => _setBase.add(value);
+
+  void addAll(Iterable<E> elements) {
+    _setBase.addAll(elements);
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Set<T> cast<T>() {
+    throw new UnimplementedError('cast');
+  }
+
+  void clear() {
+    _setBase.clear();
+  }
+
+  bool containsAll(Iterable<Object> other) => _setBase.containsAll(other);
+
+  Set<E> difference(Set<Object> other) => _setBase.difference(other);
+
+  Set<E> intersection(Set<Object> other) => _setBase.intersection(other);
+
+  E lookup(Object element) => _setBase.lookup(element);
+
+  bool remove(Object value) => _setBase.remove(value);
+
+  void removeAll(Iterable<Object> elements) {
+    _setBase.removeAll(elements);
+  }
+
+  void removeWhere(bool test(E element)) {
+    _setBase.removeWhere(test);
+  }
+
+  void retainAll(Iterable<Object> elements) {
+    _setBase.retainAll(elements);
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Set<T> retype<T>() {
+    throw new UnimplementedError('retype');
+  }
+
+  void retainWhere(bool test(E element)) {
+    _setBase.retainWhere(test);
+  }
+
+  Set<E> union(Set<E> other) => _setBase.union(other);
+
+  Set<E> toSet() => new DelegatingSet<E>(_setBase.toSet());
+}
+
+/// A [Queue] that delegates all operations to a base queue.
+///
+/// This class can be used to hide non-`Queue` methods of a queue object, or it
+/// can be extended to add extra functionality on top of an existing queue
+/// object.
+class DelegatingQueue<E> extends DelegatingIterable<E> implements Queue<E> {
+  const DelegatingQueue(Queue<E> queue) : super(queue);
+
+  /// Creates a wrapper that asserts the types of values in [base].
+  ///
+  /// This soundly converts a [Queue] without a generic type to a `Queue<E>` by
+  /// asserting that its elements are instances of `E` whenever they're
+  /// accessed. If they're not, it throws a [CastError]. Note that even if an
+  /// operation throws a [CastError], it may still mutate the underlying
+  /// collection.
+  ///
+  /// This forwards all operations to [base], so any changes in [base] will be
+  /// reflected in [this]. If [base] is already a `Queue<E>`, it's returned
+  /// unmodified.
+  static Queue<E> typed<E>(Queue base) =>
+      base is Queue<E> ? base : new TypeSafeQueue<E>(base);
+
+  Queue<E> get _baseQueue => _base;
+
+  void add(E value) {
+    _baseQueue.add(value);
+  }
+
+  void addAll(Iterable<E> iterable) {
+    _baseQueue.addAll(iterable);
+  }
+
+  void addFirst(E value) {
+    _baseQueue.addFirst(value);
+  }
+
+  void addLast(E value) {
+    _baseQueue.addLast(value);
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Queue<T> cast<T>() {
+    throw new UnimplementedError('cast');
+  }
+
+  void clear() {
+    _baseQueue.clear();
+  }
+
+  bool remove(Object object) => _baseQueue.remove(object);
+
+  void removeWhere(bool test(E element)) {
+    _baseQueue.removeWhere(test);
+  }
+
+  void retainWhere(bool test(E element)) {
+    _baseQueue.retainWhere(test);
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Queue<T> retype<T>() {
+    throw new UnimplementedError('retype');
+  }
+
+  E removeFirst() => _baseQueue.removeFirst();
+
+  E removeLast() => _baseQueue.removeLast();
+}
+
+/// A [Map] that delegates all operations to a base map.
+///
+/// This class can be used to hide non-`Map` methods of an object that extends
+/// `Map`, or it can be extended to add extra functionality on top of an
+/// existing map object.
+class DelegatingMap<K, V> implements Map<K, V> {
+  final Map<K, V> _base;
+
+  const DelegatingMap(Map<K, V> base) : _base = base;
+
+  /// Creates a wrapper that asserts the types of keys and values in [base].
+  ///
+  /// This soundly converts a [Map] without generic types to a `Map<K, V>` by
+  /// asserting that its keys are instances of `E` and its values are instances
+  /// of `V` whenever they're accessed. If they're not, it throws a [CastError].
+  /// Note that even if an operation throws a [CastError], it may still mutate
+  /// the underlying collection.
+  ///
+  /// This forwards all operations to [base], so any changes in [base] will be
+  /// reflected in [this]. If [base] is already a `Map<K, V>`, it's returned
+  /// unmodified.
+  static Map<K, V> typed<K, V>(Map base) =>
+      base is Map<K, V> ? base : new TypeSafeMap<K, V>(base);
+
+  V operator [](Object key) => _base[key];
+
+  void operator []=(K key, V value) {
+    _base[key] = value;
+  }
+
+  void addAll(Map<K, V> other) {
+    _base.addAll(other);
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  void addEntries(Iterable<Object> entries) {
+    // Change Iterable<Object> to Iterable<MapEntry<K, V>> when
+    // the MapEntry class has been added.
+    throw new UnimplementedError('addEntries');
+  }
+
+  void clear() {
+    _base.clear();
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Map<K2, V2> cast<K2, V2>() {
+    throw new UnimplementedError('cast');
+  }
+
+  bool containsKey(Object key) => _base.containsKey(key);
+
+  bool containsValue(Object value) => _base.containsValue(value);
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Iterable<Null> get entries {
+    // Change Iterable<Null> to Iterable<MapEntry<K, V>> when
+    // the MapEntry class has been added.
+    throw new UnimplementedError('entries');
+  }
+
+  void forEach(void f(K key, V value)) {
+    _base.forEach(f);
+  }
+
+  bool get isEmpty => _base.isEmpty;
+
+  bool get isNotEmpty => _base.isNotEmpty;
+
+  Iterable<K> get keys => _base.keys;
+
+  int get length => _base.length;
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Map<K2, V2> map<K2, V2>(Object transform(K key, V value)) {
+    // Change Object to MapEntry<K2, V2> when
+    // the MapEntry class has been added.
+    throw new UnimplementedError('map');
+  }
+
+  V putIfAbsent(K key, V ifAbsent()) => _base.putIfAbsent(key, ifAbsent);
+
+  V remove(Object key) => _base.remove(key);
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  void removeWhere(bool test(K key, V value)) {
+    throw new UnimplementedError('removeWhere');
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Map<K2, V2> retype<K2, V2>() {
+    throw new UnimplementedError('retype');
+  }
+
+  Iterable<V> get values => _base.values;
+
+  String toString() => _base.toString();
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  V update(K key, V update(V value), {V ifAbsent()}) {
+    throw new UnimplementedError('update');
+  }
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  void updateAll(V update(K key, V value)) {
+    throw new UnimplementedError('updateAll');
+  }
+}
+
+/// An unmodifiable [Set] view of the keys of a [Map].
+///
+/// The set delegates all operations to the underlying map.
+///
+/// A `Map` can only contain each key once, so its keys can always
+/// be viewed as a `Set` without any loss, even if the [Map.keys]
+/// getter only shows an [Iterable] view of the keys.
+///
+/// Note that [lookup] is not supported for this set.
+class MapKeySet<E> extends _DelegatingIterableBase<E>
+    with UnmodifiableSetMixin<E> {
+  final Map<E, dynamic> _baseMap;
+
+  MapKeySet(Map<E, dynamic> base) : _baseMap = base;
+
+  Iterable<E> get _base => _baseMap.keys;
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Set<T> cast<T>() {
+    throw new UnimplementedError('cast');
+  }
+
+  bool contains(Object element) => _baseMap.containsKey(element);
+
+  bool get isEmpty => _baseMap.isEmpty;
+
+  bool get isNotEmpty => _baseMap.isNotEmpty;
+
+  int get length => _baseMap.length;
+
+  String toString() => "{${_base.join(', ')}}";
+
+  bool containsAll(Iterable<Object> other) => other.every(contains);
+
+  /// Returns a new set with the the elements of [this] that are not in [other].
+  ///
+  /// That is, the returned set contains all the elements of this [Set] that are
+  /// not elements of [other] according to `other.contains`.
+  ///
+  /// Note that the returned set will use the default equality operation, which
+  /// may be different than the equality operation [this] uses.
+  Set<E> difference(Set<Object> other) =>
+      where((element) => !other.contains(element)).toSet();
+
+  /// Returns a new set which is the intersection between [this] and [other].
+  ///
+  /// That is, the returned set contains all the elements of this [Set] that are
+  /// also elements of [other] according to `other.contains`.
+  ///
+  /// Note that the returned set will use the default equality operation, which
+  /// may be different than the equality operation [this] uses.
+  Set<E> intersection(Set<Object> other) => where(other.contains).toSet();
+
+  /// Throws an [UnsupportedError] since there's no corresponding method for
+  /// [Map]s.
+  E lookup(Object element) =>
+      throw new UnsupportedError("MapKeySet doesn't support lookup().");
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Set<T> retype<T>() {
+    throw new UnimplementedError('retype');
+  }
+
+  /// Returns a new set which contains all the elements of [this] and [other].
+  ///
+  /// That is, the returned set contains all the elements of this [Set] and all
+  /// the elements of [other].
+  ///
+  /// Note that the returned set will use the default equality operation, which
+  /// may be different than the equality operation [this] uses.
+  Set<E> union(Set<E> other) => toSet()..addAll(other);
+}
+
+/// Creates a modifiable [Set] view of the values of a [Map].
+///
+/// The `Set` view assumes that the keys of the `Map` can be uniquely determined
+/// from the values. The `keyForValue` function passed to the constructor finds
+/// the key for a single value. The `keyForValue` function should be consistent
+/// with equality. If `value1 == value2` then `keyForValue(value1)` and
+/// `keyForValue(value2)` should be considered equal keys by the underlying map,
+/// and vice versa.
+///
+/// Modifying the set will modify the underlying map based on the key returned
+/// by `keyForValue`.
+///
+/// If the `Map` contents are not compatible with the `keyForValue` function,
+/// the set will not work consistently, and may give meaningless responses or do
+/// inconsistent updates.
+///
+/// This set can, for example, be used on a map from database record IDs to the
+/// records. It exposes the records as a set, and allows for writing both
+/// `recordSet.add(databaseRecord)` and `recordMap[id]`.
+///
+/// Effectively, the map will act as a kind of index for the set.
+class MapValueSet<K, V> extends _DelegatingIterableBase<V> implements Set<V> {
+  final Map<K, V> _baseMap;
+  final _KeyForValue<K, V> _keyForValue;
+
+  /// Creates a new [MapValueSet] based on [base].
+  ///
+  /// [keyForValue] returns the key in the map that should be associated with
+  /// the given value. The set's notion of equality is identical to the equality
+  /// of the return values of [keyForValue].
+  MapValueSet(Map<K, V> base, K keyForValue(V value))
+      : _baseMap = base,
+        _keyForValue = keyForValue;
+
+  Iterable<V> get _base => _baseMap.values;
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Set<T> cast<T>() {
+    throw new UnimplementedError('cast');
+  }
+
+  bool contains(Object element) {
+    if (element != null && element is! V) return false;
+    var key = _keyForValue(element as V);
+
+    return _baseMap.containsKey(key);
+  }
+
+  bool get isEmpty => _baseMap.isEmpty;
+
+  bool get isNotEmpty => _baseMap.isNotEmpty;
+
+  int get length => _baseMap.length;
+
+  String toString() => toSet().toString();
+
+  bool add(V value) {
+    K key = _keyForValue(value);
+    bool result = false;
+    _baseMap.putIfAbsent(key, () {
+      result = true;
+      return value;
+    });
+    return result;
+  }
+
+  void addAll(Iterable<V> elements) => elements.forEach(add);
+
+  void clear() => _baseMap.clear();
+
+  bool containsAll(Iterable<Object> other) => other.every(contains);
+
+  /// Returns a new set with the the elements of [this] that are not in [other].
+  ///
+  /// That is, the returned set contains all the elements of this [Set] that are
+  /// not elements of [other] according to `other.contains`.
+  ///
+  /// Note that the returned set will use the default equality operation, which
+  /// may be different than the equality operation [this] uses.
+  Set<V> difference(Set<Object> other) =>
+      where((element) => !other.contains(element)).toSet();
+
+  /// Returns a new set which is the intersection between [this] and [other].
+  ///
+  /// That is, the returned set contains all the elements of this [Set] that are
+  /// also elements of [other] according to `other.contains`.
+  ///
+  /// Note that the returned set will use the default equality operation, which
+  /// may be different than the equality operation [this] uses.
+  Set<V> intersection(Set<Object> other) => where(other.contains).toSet();
+
+  V lookup(Object element) {
+    if (element != null && element is! V) return null;
+    var key = _keyForValue(element as V);
+
+    return _baseMap[key];
+  }
+
+  bool remove(Object element) {
+    if (element != null && element is! V) return false;
+    var key = _keyForValue(element as V);
+
+    if (!_baseMap.containsKey(key)) return false;
+    _baseMap.remove(key);
+    return true;
+  }
+
+  void removeAll(Iterable<Object> elements) => elements.forEach(remove);
+
+  void removeWhere(bool test(V element)) {
+    var toRemove = [];
+    _baseMap.forEach((key, value) {
+      if (test(value)) toRemove.add(key);
+    });
+    toRemove.forEach(_baseMap.remove);
+  }
+
+  void retainAll(Iterable<Object> elements) {
+    var valuesToRetain = new Set<V>.identity();
+    for (var element in elements) {
+      if (element != null && element is! V) continue;
+      var key = _keyForValue(element as V);
+
+      if (!_baseMap.containsKey(key)) continue;
+      valuesToRetain.add(_baseMap[key]);
+    }
+
+    var keysToRemove = [];
+    _baseMap.forEach((k, v) {
+      if (!valuesToRetain.contains(v)) keysToRemove.add(k);
+    });
+    keysToRemove.forEach(_baseMap.remove);
+  }
+
+  void retainWhere(bool test(V element)) =>
+      removeWhere((element) => !test(element));
+
+  // TODO: Dart 2.0 requires this method to be implemented.
+  Set<T> retype<T>() {
+    throw new UnimplementedError('retype');
+  }
+
+  /// Returns a new set which contains all the elements of [this] and [other].
+  ///
+  /// That is, the returned set contains all the elements of this [Set] and all
+  /// the elements of [other].
+  ///
+  /// Note that the returned set will use the default equality operation, which
+  /// may be different than the equality operation [this] uses.
+  Set<V> union(Set<V> other) => toSet()..addAll(other);
+}
diff --git a/packages/collection/lib/wrappers.dart b/packages/collection/lib/wrappers.dart
new file mode 100644
index 0000000..13031f5
--- /dev/null
+++ b/packages/collection/lib/wrappers.dart
@@ -0,0 +1,11 @@
+// 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.
+
+/// Import `collection.dart` instead.
+@Deprecated("Will be removed in collection 2.0.0.")
+library dart.pkg.collection.wrappers;
+
+export "src/canonicalized_map.dart";
+export "src/unmodifiable_wrappers.dart";
+export "src/wrappers.dart";
diff --git a/packages/collection/pubspec.yaml b/packages/collection/pubspec.yaml
new file mode 100644
index 0000000..e97a358
--- /dev/null
+++ b/packages/collection/pubspec.yaml
@@ -0,0 +1,9 @@
+name: collection
+version: 1.14.6
+author: Dart Team <misc@dartlang.org>
+description: Collections and utilities functions and classes related to collections.
+homepage: https://www.github.com/dart-lang/collection
+environment:
+  sdk: '>=1.21.0 <2.0.0'
+dev_dependencies:
+  test: '^0.12.0'
diff --git a/packages/collection/test/algorithms_test.dart b/packages/collection/test/algorithms_test.dart
new file mode 100644
index 0000000..c7f5af1
--- /dev/null
+++ b/packages/collection/test/algorithms_test.dart
@@ -0,0 +1,328 @@
+// 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.
+
+/// Tests algorithm utilities.
+import 'dart:math';
+
+import "package:collection/collection.dart";
+import "package:test/test.dart";
+
+void main() {
+  void testShuffle(List list) {
+    List copy = list.toList();
+    shuffle(list);
+    expect(new UnorderedIterableEquality().equals(list, copy), isTrue);
+  }
+
+  test("Shuffle 0", () {
+    testShuffle([]);
+  });
+  test("Shuffle 1", () {
+    testShuffle([1]);
+  });
+  test("Shuffle 3", () {
+    testShuffle([1, 2, 3]);
+  });
+  test("Shuffle 10", () {
+    testShuffle([1, 2, 3, 4, 5, 1, 3, 5, 7, 9]);
+  });
+  test("Shuffle shuffles", () {
+    List l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
+    List c = l.toList();
+    int count = 0;
+    do {
+      shuffle(l);
+      if (!const ListEquality().equals(c, l)) return;
+      // Odds of not changing the order should be one in ~ 16! ~= 2e+13.
+      // Repeat this 10 times, and the odds of accidentally shuffling to the
+      // same result every time is disappearingly tiny.
+      count++;
+      // If this happens even once, it's ok to report it.
+      print("Failed shuffle $count times");
+      if (count == 10) fail("Shuffle didn't change order.");
+    } while (true);
+  });
+  test("Shuffle sublist", () {
+    List l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
+    List c = l.toList();
+    shuffle(l, 4, 12);
+    expect(const IterableEquality().equals(l.getRange(0, 4), c.getRange(0, 4)),
+        isTrue);
+    expect(
+        const IterableEquality().equals(l.getRange(12, 16), c.getRange(12, 16)),
+        isTrue);
+    expect(
+        const UnorderedIterableEquality()
+            .equals(l.getRange(4, 12), c.getRange(4, 12)),
+        isTrue);
+  });
+
+  test("binsearch0", () {
+    expect(binarySearch([], 2), equals(-1));
+  });
+
+  test("binsearch1", () {
+    expect(binarySearch([5], 2), equals(-1));
+    expect(binarySearch([5], 5), equals(0));
+    expect(binarySearch([5], 7), equals(-1));
+  });
+
+  test("binsearch3", () {
+    expect(binarySearch([0, 5, 10], -1), equals(-1));
+    expect(binarySearch([0, 5, 10], 0), equals(0));
+    expect(binarySearch([0, 5, 10], 2), equals(-1));
+    expect(binarySearch([0, 5, 10], 5), equals(1));
+    expect(binarySearch([0, 5, 10], 7), equals(-1));
+    expect(binarySearch([0, 5, 10], 10), equals(2));
+    expect(binarySearch([0, 5, 10], 12), equals(-1));
+  });
+
+  test("binsearchCompare0", () {
+    expect(binarySearch(<C>[], new C(2), compare: compareC), equals(-1));
+  });
+
+  test("binsearchCompare1", () {
+    var l1 = [new C(5)];
+    expect(binarySearch(l1, new C(2), compare: compareC), equals(-1));
+    expect(binarySearch(l1, new C(5), compare: compareC), equals(0));
+    expect(binarySearch(l1, new C(7), compare: compareC), equals(-1));
+  });
+
+  test("binsearchCompare3", () {
+    var l3 = [new C(0), new C(5), new C(10)];
+    expect(binarySearch(l3, new C(-1), compare: compareC), equals(-1));
+    expect(binarySearch(l3, new C(0), compare: compareC), equals(0));
+    expect(binarySearch(l3, new C(2), compare: compareC), equals(-1));
+    expect(binarySearch(l3, new C(5), compare: compareC), equals(1));
+    expect(binarySearch(l3, new C(7), compare: compareC), equals(-1));
+    expect(binarySearch(l3, new C(10), compare: compareC), equals(2));
+    expect(binarySearch(l3, new C(12), compare: compareC), equals(-1));
+  });
+
+  test("lowerbound0", () {
+    expect(lowerBound([], 2), equals(0));
+  });
+
+  test("lowerbound1", () {
+    expect(lowerBound([5], 2), equals(0));
+    expect(lowerBound([5], 5), equals(0));
+    expect(lowerBound([5], 7), equals(1));
+  });
+
+  test("lowerbound3", () {
+    expect(lowerBound([0, 5, 10], -1), equals(0));
+    expect(lowerBound([0, 5, 10], 0), equals(0));
+    expect(lowerBound([0, 5, 10], 2), equals(1));
+    expect(lowerBound([0, 5, 10], 5), equals(1));
+    expect(lowerBound([0, 5, 10], 7), equals(2));
+    expect(lowerBound([0, 5, 10], 10), equals(2));
+    expect(lowerBound([0, 5, 10], 12), equals(3));
+  });
+
+  test("lowerboundRepeat", () {
+    expect(lowerBound([5, 5, 5], 5), equals(0));
+    expect(lowerBound([0, 5, 5, 5, 10], 5), equals(1));
+  });
+
+  test("lowerboundCompare0", () {
+    expect(lowerBound(<C>[], new C(2), compare: compareC), equals(0));
+  });
+
+  test("lowerboundCompare1", () {
+    var l1 = [new C(5)];
+    expect(lowerBound(l1, new C(2), compare: compareC), equals(0));
+    expect(lowerBound(l1, new C(5), compare: compareC), equals(0));
+    expect(lowerBound(l1, new C(7), compare: compareC), equals(1));
+  });
+
+  test("lowerboundCompare3", () {
+    var l3 = [new C(0), new C(5), new C(10)];
+    expect(lowerBound(l3, new C(-1), compare: compareC), equals(0));
+    expect(lowerBound(l3, new C(0), compare: compareC), equals(0));
+    expect(lowerBound(l3, new C(2), compare: compareC), equals(1));
+    expect(lowerBound(l3, new C(5), compare: compareC), equals(1));
+    expect(lowerBound(l3, new C(7), compare: compareC), equals(2));
+    expect(lowerBound(l3, new C(10), compare: compareC), equals(2));
+    expect(lowerBound(l3, new C(12), compare: compareC), equals(3));
+  });
+
+  test("lowerboundCompareRepeat", () {
+    var l1 = [new C(5), new C(5), new C(5)];
+    var l2 = [new C(0), new C(5), new C(5), new C(5), new C(10)];
+    expect(lowerBound(l1, new C(5), compare: compareC), equals(0));
+    expect(lowerBound(l2, new C(5), compare: compareC), equals(1));
+  });
+
+  test("insertionSortRandom", () {
+    Random random = new Random();
+    for (int i = 0; i < 25; i++) {
+      List list = new List(i);
+      for (int j = 0; j < i; j++) {
+        list[j] = random.nextInt(25); // Expect some equal elements.
+      }
+      insertionSort(list);
+      for (int j = 1; j < i; j++) {
+        expect(list[j - 1], lessThanOrEqualTo(list[j]));
+      }
+    }
+  });
+
+  test("insertionSortSubRanges", () {
+    List l = [6, 5, 4, 3, 2, 1];
+    insertionSort(l, start: 2, end: 4);
+    expect(l, equals([6, 5, 3, 4, 2, 1]));
+    insertionSort(l, start: 1, end: 1);
+    expect(l, equals([6, 5, 3, 4, 2, 1]));
+    insertionSort(l, start: 4, end: 6);
+    expect(l, equals([6, 5, 3, 4, 1, 2]));
+    insertionSort(l, start: 0, end: 2);
+    expect(l, equals([5, 6, 3, 4, 1, 2]));
+    insertionSort(l, start: 0, end: 6);
+    expect(l, equals([1, 2, 3, 4, 5, 6]));
+  });
+
+  test("insertionSortSpecialCases", () {
+    List l = [6, 6, 6, 6, 6, 6];
+    insertionSort(l);
+    expect(l, equals([6, 6, 6, 6, 6, 6]));
+
+    l = [6, 6, 3, 3, 0, 0];
+    insertionSort(l);
+    expect(l, equals([0, 0, 3, 3, 6, 6]));
+  });
+
+  test("MergeSortRandom", () {
+    Random random = new Random();
+    for (int i = 0; i < 250; i += 1) {
+      List list = new List(i);
+      for (int j = 0; j < i; j++) {
+        list[j] = random.nextInt(i); // Expect some equal elements.
+      }
+      mergeSort(list);
+      for (int j = 1; j < i; j++) {
+        expect(list[j - 1], lessThanOrEqualTo(list[j]));
+      }
+    }
+  });
+
+  test("MergeSortPreservesOrder", () {
+    Random random = new Random();
+    // Small case where only insertion call is called,
+    // larger case where the internal moving insertion sort is used
+    // larger cases with multiple splittings, numbers just around a power of 2.
+    for (int size in [8, 50, 511, 512, 513]) {
+      var list = new List<OC>(size);
+      // Class OC compares using id.
+      // With size elements with id's in the range 0..size/4, a number of
+      // collisions are guaranteed. These should be sorted so that the "order"
+      // part of the objects are still in order.
+      for (int i = 0; i < size; i++) {
+        list[i] = new OC(random.nextInt(size >> 2), i);
+      }
+      mergeSort(list);
+      OC prev = list[0];
+      for (int i = 1; i < size; i++) {
+        OC next = list[i];
+        expect(prev.id, lessThanOrEqualTo(next.id));
+        if (next.id == prev.id) {
+          expect(prev.order, lessThanOrEqualTo(next.order));
+        }
+        prev = next;
+      }
+      // Reverse compare on part of list.
+      List copy = list.toList();
+      int min = size >> 2;
+      int max = size - min;
+      mergeSort<OC>(list,
+          start: min, end: max, compare: (a, b) => b.compareTo(a));
+      prev = list[min];
+      for (int i = min + 1; i < max; i++) {
+        OC next = list[i];
+        expect(prev.id, greaterThanOrEqualTo(next.id));
+        if (next.id == prev.id) {
+          expect(prev.order, lessThanOrEqualTo(next.order));
+        }
+        prev = next;
+      }
+      // Equals on OC objects is identity, so this means the parts before min,
+      // and the parts after max, didn't change at all.
+      expect(list.sublist(0, min), equals(copy.sublist(0, min)));
+      expect(list.sublist(max), equals(copy.sublist(max)));
+    }
+  });
+
+  test("MergeSortSpecialCases", () {
+    for (int size in [511, 512, 513]) {
+      // All equal.
+      List list = new List(size);
+      for (int i = 0; i < size; i++) {
+        list[i] = new OC(0, i);
+      }
+      mergeSort(list);
+      for (int i = 0; i < size; i++) {
+        expect(list[i].order, equals(i));
+      }
+      // All but one equal, first.
+      list[0] = new OC(1, 0);
+      for (int i = 1; i < size; i++) {
+        list[i] = new OC(0, i);
+      }
+      mergeSort(list);
+      for (int i = 0; i < size - 1; i++) {
+        expect(list[i].order, equals(i + 1));
+      }
+      expect(list[size - 1].order, equals(0));
+
+      // All but one equal, last.
+      for (int i = 0; i < size - 1; i++) {
+        list[i] = new OC(0, i);
+      }
+      list[size - 1] = new OC(-1, size - 1);
+      mergeSort(list);
+      expect(list[0].order, equals(size - 1));
+      for (int i = 1; i < size; i++) {
+        expect(list[i].order, equals(i - 1));
+      }
+
+      // Reversed.
+      for (int i = 0; i < size; i++) {
+        list[i] = new OC(size - 1 - i, i);
+      }
+      mergeSort(list);
+      for (int i = 0; i < size; i++) {
+        expect(list[i].id, equals(i));
+        expect(list[i].order, equals(size - 1 - i));
+      }
+    }
+  });
+
+  test("Reverse", () {
+    List l = [6, 5, 4, 3, 2, 1];
+    reverse(l, 2, 4);
+    expect(l, equals([6, 5, 3, 4, 2, 1]));
+    reverse(l, 1, 1);
+    expect(l, equals([6, 5, 3, 4, 2, 1]));
+    reverse(l, 4, 6);
+    expect(l, equals([6, 5, 3, 4, 1, 2]));
+    reverse(l, 0, 2);
+    expect(l, equals([5, 6, 3, 4, 1, 2]));
+    reverse(l, 0, 6);
+    expect(l, equals([2, 1, 4, 3, 6, 5]));
+  });
+}
+
+class C {
+  final int id;
+  C(this.id);
+}
+
+int compareC(C one, C other) => one.id - other.id;
+
+class OC implements Comparable<OC> {
+  final int id;
+  final int order;
+  OC(this.id, this.order);
+  int compareTo(OC other) => id - other.id;
+  String toString() => "OC[$id,$order]";
+}
diff --git a/packages/collection/test/canonicalized_map_test.dart b/packages/collection/test/canonicalized_map_test.dart
new file mode 100644
index 0000000..f48778d
--- /dev/null
+++ b/packages/collection/test/canonicalized_map_test.dart
@@ -0,0 +1,180 @@
+// 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.
+
+import "package:collection/collection.dart";
+import "package:test/test.dart";
+
+void main() {
+  group("with an empty canonicalized map", () {
+    var map;
+    setUp(() {
+      map = new CanonicalizedMap<int, String, String>(int.parse,
+          isValidKey: (s) => new RegExp(r"^\d+$").hasMatch(s as String));
+    });
+
+    test("canonicalizes keys on set and get", () {
+      map["1"] = "value";
+      expect(map["01"], equals("value"));
+    });
+
+    test("get returns null for uncanonicalizable key", () {
+      expect(map["foo"], isNull);
+    });
+
+    test("set affects nothing for uncanonicalizable key", () {
+      map["foo"] = "value";
+      expect(map["foo"], isNull);
+      expect(map.containsKey("foo"), isFalse);
+      expect(map.length, equals(0));
+    });
+
+    test("canonicalizes keys for addAll", () {
+      map.addAll({"1": "value 1", "2": "value 2", "3": "value 3"});
+      expect(map["01"], equals("value 1"));
+      expect(map["02"], equals("value 2"));
+      expect(map["03"], equals("value 3"));
+    });
+
+    test("uses the final value for addAll collisions", () {
+      map.addAll({"1": "value 1", "01": "value 2", "001": "value 3"});
+      expect(map.length, equals(1));
+      expect(map["0001"], equals("value 3"));
+    });
+
+    test("clear clears the map", () {
+      map.addAll({"1": "value 1", "2": "value 2", "3": "value 3"});
+      expect(map, isNot(isEmpty));
+      map.clear();
+      expect(map, isEmpty);
+    });
+
+    test("canonicalizes keys for containsKey", () {
+      map["1"] = "value";
+      expect(map.containsKey("01"), isTrue);
+      expect(map.containsKey("2"), isFalse);
+    });
+
+    test("containsKey returns false for uncanonicalizable key", () {
+      expect(map.containsKey("foo"), isFalse);
+    });
+
+    test("canonicalizes keys for putIfAbsent", () {
+      map["1"] = "value";
+      expect(map.putIfAbsent("01", () => throw new Exception("shouldn't run")),
+          equals("value"));
+      expect(map.putIfAbsent("2", () => "new value"), equals("new value"));
+    });
+
+    test("canonicalizes keys for remove", () {
+      map["1"] = "value";
+      expect(map.remove("2"), isNull);
+      expect(map.remove("01"), equals("value"));
+      expect(map, isEmpty);
+    });
+
+    test("remove returns null for uncanonicalizable key", () {
+      expect(map.remove("foo"), isNull);
+    });
+
+    test("containsValue returns whether a value is in the map", () {
+      map["1"] = "value";
+      expect(map.containsValue("value"), isTrue);
+      expect(map.containsValue("not value"), isFalse);
+    });
+
+    test("isEmpty returns whether the map is empty", () {
+      expect(map.isEmpty, isTrue);
+      map["1"] = "value";
+      expect(map.isEmpty, isFalse);
+      map.remove("01");
+      expect(map.isEmpty, isTrue);
+    });
+
+    test("isNotEmpty returns whether the map isn't empty", () {
+      expect(map.isNotEmpty, isFalse);
+      map["1"] = "value";
+      expect(map.isNotEmpty, isTrue);
+      map.remove("01");
+      expect(map.isNotEmpty, isFalse);
+    });
+
+    test("length returns the number of pairs in the map", () {
+      expect(map.length, equals(0));
+      map["1"] = "value 1";
+      expect(map.length, equals(1));
+      map["01"] = "value 01";
+      expect(map.length, equals(1));
+      map["02"] = "value 02";
+      expect(map.length, equals(2));
+    });
+
+    test("uses original keys for keys", () {
+      map["001"] = "value 1";
+      map["02"] = "value 2";
+      expect(map.keys, equals(["001", "02"]));
+    });
+
+    test("uses original keys for forEach", () {
+      map["001"] = "value 1";
+      map["02"] = "value 2";
+
+      var keys = [];
+      map.forEach((key, value) => keys.add(key));
+      expect(keys, equals(["001", "02"]));
+    });
+
+    test("values returns all values in the map", () {
+      map.addAll(
+          {"1": "value 1", "01": "value 01", "2": "value 2", "03": "value 03"});
+
+      expect(map.values, equals(["value 01", "value 2", "value 03"]));
+    });
+  });
+
+  group("CanonicalizedMap builds an informative string representation", () {
+    var map;
+    setUp(() {
+      map = new CanonicalizedMap<int, String, dynamic>(int.parse,
+          isValidKey: (s) => new RegExp(r"^\d+$").hasMatch(s as String));
+    });
+
+    test("for an empty map", () {
+      expect(map.toString(), equals('{}'));
+    });
+
+    test("for a map with one value", () {
+      map.addAll({"1": "value 1"});
+      expect(map.toString(), equals('{1: value 1}'));
+    });
+
+    test("for a map with multiple values", () {
+      map.addAll(
+          {"1": "value 1", "01": "value 01", "2": "value 2", "03": "value 03"});
+      expect(
+          map.toString(), equals('{01: value 01, 2: value 2, 03: value 03}'));
+    });
+
+    test("for a map with a loop", () {
+      map.addAll({"1": "value 1", "2": map});
+      expect(map.toString(), equals('{1: value 1, 2: {...}}'));
+    });
+  });
+
+  group("CanonicalizedMap.from", () {
+    test("canonicalizes its keys", () {
+      var map = new CanonicalizedMap.from(
+          {"1": "value 1", "2": "value 2", "3": "value 3"}, int.parse);
+      expect(map["01"], equals("value 1"));
+      expect(map["02"], equals("value 2"));
+      expect(map["03"], equals("value 3"));
+    });
+
+    test("uses the final value for collisions", () {
+      var map = new CanonicalizedMap.from(
+          {"1": "value 1", "01": "value 2", "001": "value 3"}, int.parse);
+      expect(map.length, equals(1));
+      expect(map["0001"], equals("value 3"));
+    });
+  });
+}
diff --git a/packages/collection/test/combined_wrapper/iterable_test.dart b/packages/collection/test/combined_wrapper/iterable_test.dart
new file mode 100644
index 0000000..3301821
--- /dev/null
+++ b/packages/collection/test/combined_wrapper/iterable_test.dart
@@ -0,0 +1,60 @@
+// Copyright (c) 2017, 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:collection/collection.dart';
+import 'package:test/test.dart';
+
+void main() {
+  var iterable1 = new Iterable.generate(3);
+  var iterable2 = new Iterable.generate(3, (i) => i + 3);
+  var iterable3 = new Iterable.generate(3, (i) => i + 6);
+
+  test('should combine multiple iterables when iterating', () {
+    var combined = new CombinedIterableView([iterable1, iterable2, iterable3]);
+    expect(combined, [0, 1, 2, 3, 4, 5, 6, 7, 8]);
+  });
+
+  test('should combine multiple iterables with some empty ones', () {
+    var combined =
+        new CombinedIterableView([iterable1, [], iterable2, [], iterable3, []]);
+    expect(combined, [0, 1, 2, 3, 4, 5, 6, 7, 8]);
+  });
+
+  test('should function as an empty iterable when no iterables are passed', () {
+    var empty = new CombinedIterableView([]);
+    expect(empty, isEmpty);
+  });
+
+  test('should function as an empty iterable with all empty iterables', () {
+    var empty = new CombinedIterableView([[], [], []]);
+    expect(empty, isEmpty);
+  });
+
+  test('should reflect changes from the underlying iterables', () {
+    var list1 = [];
+    var list2 = [];
+    var combined = new CombinedIterableView([list1, list2]);
+    expect(combined, isEmpty);
+    list1.addAll([1, 2]);
+    list2.addAll([3, 4]);
+    expect(combined, [1, 2, 3, 4]);
+    expect(combined.last, 4);
+    expect(combined.first, 1);
+  });
+
+  test('should reflect changes from the iterable of iterables', () {
+    var iterables = <Iterable>[];
+    var combined = new CombinedIterableView(iterables);
+    expect(combined, isEmpty);
+    expect(combined, hasLength(0));
+
+    iterables.add(iterable1);
+    expect(combined, isNotEmpty);
+    expect(combined, hasLength(3));
+
+    iterables.clear();
+    expect(combined, isEmpty);
+    expect(combined, hasLength(0));
+  });
+}
diff --git a/packages/collection/test/combined_wrapper/list_test.dart b/packages/collection/test/combined_wrapper/list_test.dart
new file mode 100644
index 0000000..9e90925
--- /dev/null
+++ b/packages/collection/test/combined_wrapper/list_test.dart
@@ -0,0 +1,67 @@
+// Copyright (c) 2017, 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:collection/collection.dart';
+import 'package:test/test.dart';
+
+import '../unmodifiable_collection_test.dart' as common;
+
+void main() {
+  var list1 = [1, 2, 3];
+  var list2 = [4, 5, 6];
+  var list3 = [7, 8, 9];
+  var concat = <int>[]..addAll(list1)..addAll(list2)..addAll(list3);
+
+  // In every way possible this should test the same as an UnmodifiableListView.
+  common.testUnmodifiableList(
+      concat, new CombinedListView([list1, list2, list3]), 'combineLists');
+
+  common.testUnmodifiableList(concat,
+      new CombinedListView([list1, [], list2, [], list3, []]), 'combineLists');
+
+  test('should function as an empty list when no lists are passed', () {
+    var empty = new CombinedListView([]);
+    expect(empty, isEmpty);
+    expect(empty.length, 0);
+    expect(() => empty[0], throwsRangeError);
+  });
+
+  test('should function as an empty list when only empty lists are passed', () {
+    var empty = new CombinedListView([[], [], []]);
+    expect(empty, isEmpty);
+    expect(empty.length, 0);
+    expect(() => empty[0], throwsRangeError);
+  });
+
+  test('should reflect underlying changes back to the combined list', () {
+    var backing1 = <int>[];
+    var backing2 = <int>[];
+    var combined = new CombinedListView([backing1, backing2]);
+    expect(combined, isEmpty);
+    backing1.addAll(list1);
+    expect(combined, list1);
+    backing2.addAll(list2);
+    expect(combined, backing1.toList()..addAll(backing2));
+  });
+
+  test('should reflect underlying changes from the list of lists', () {
+    var listOfLists = <List<int>>[];
+    var combined = new CombinedListView(listOfLists);
+    expect(combined, isEmpty);
+    listOfLists.add(list1);
+    expect(combined, list1);
+    listOfLists.add(list2);
+    expect(combined, []..addAll(list1)..addAll(list2));
+    listOfLists.clear();
+    expect(combined, isEmpty);
+  });
+
+  test('should reflect underlying changes with a single list', () {
+    var backing1 = <int>[];
+    var combined = new CombinedListView([backing1]);
+    expect(combined, isEmpty);
+    backing1.addAll(list1);
+    expect(combined, list1);
+  });
+}
diff --git a/packages/collection/test/combined_wrapper/map_test.dart b/packages/collection/test/combined_wrapper/map_test.dart
new file mode 100644
index 0000000..ecafb4b
--- /dev/null
+++ b/packages/collection/test/combined_wrapper/map_test.dart
@@ -0,0 +1,55 @@
+// Copyright (c) 2017, 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:collection/collection.dart';
+import 'package:test/test.dart';
+
+import '../unmodifiable_collection_test.dart' as common;
+
+void main() {
+  var map1 = const {1: 1, 2: 2, 3: 3};
+  var map2 = const {4: 4, 5: 5, 6: 6};
+  var map3 = const {7: 7, 8: 8, 9: 9};
+  var concat = <int, int>{}..addAll(map1)..addAll(map2)..addAll(map3);
+
+  // In every way possible this should test the same as an UnmodifiableMapView.
+  common.testReadMap(
+      concat, new CombinedMapView([map1, map2, map3]), 'CombinedMapView');
+
+  common.testReadMap(
+      concat,
+      new CombinedMapView([map1, {}, map2, {}, map3, {}]),
+      'CombinedMapView (some empty)');
+
+  test('should function as an empty map when no maps are passed', () {
+    var empty = new CombinedMapView([]);
+    expect(empty, isEmpty);
+    expect(empty.length, 0);
+  });
+
+  test('should function as an empty map when only empty maps are passed', () {
+    var empty = new CombinedMapView([{}, {}, {}]);
+    expect(empty, isEmpty);
+    expect(empty.length, 0);
+  });
+
+  test('should reflect underlying changes back to the combined map', () {
+    var backing1 = <int, int>{};
+    var backing2 = <int, int>{};
+    var combined = new CombinedMapView([backing1, backing2]);
+    expect(combined, isEmpty);
+    backing1.addAll(map1);
+    expect(combined, map1);
+    backing2.addAll(map2);
+    expect(combined, new Map.from(backing1)..addAll(backing2));
+  });
+
+  test('should reflect underlying changes with a single map', () {
+    var backing1 = <int, int>{};
+    var combined = new CombinedMapView([backing1]);
+    expect(combined, isEmpty);
+    backing1.addAll(map1);
+    expect(combined, map1);
+  });
+}
diff --git a/packages/collection/test/comparators_test.dart b/packages/collection/test/comparators_test.dart
new file mode 100644
index 0000000..3df0812
--- /dev/null
+++ b/packages/collection/test/comparators_test.dart
@@ -0,0 +1,119 @@
+// Copyright (c) 2015, 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:collection/collection.dart";
+import "package:test/test.dart";
+
+void main() {
+  List<String> strings = [
+    "",
+    "\x00",
+    " ",
+    "+",
+    "/",
+    "0",
+    "00",
+    "000",
+    "001",
+    "01",
+    "011",
+    "1",
+    "100",
+    "11",
+    "110",
+    "9",
+    ":",
+    "=",
+    "@",
+    "A",
+    "A0",
+    "A000A",
+    "A001A",
+    "A00A",
+    "A01A",
+    "A0A",
+    "A1A",
+    "AA",
+    "AAB",
+    "AB",
+    "Z",
+    "[",
+    "_",
+    "`",
+    "a",
+    "a0",
+    "a000a",
+    "a001a",
+    "a00a",
+    "a01a",
+    "a0a",
+    "a1a",
+    "aa",
+    "aab",
+    "ab",
+    "z",
+    "{",
+    "~"
+  ];
+
+  List<String> sortedBy(int compare(String a, String b)) => strings.toList()
+    ..shuffle()
+    ..sort(compare);
+
+  test("String.compareTo", () {
+    expect(sortedBy(null), strings);
+  });
+  test("compareAsciiLowerCase", () {
+    expect(sortedBy(compareAsciiLowerCase), sortedBy((a, b) {
+      int delta = a.toLowerCase().compareTo(b.toLowerCase());
+      if (delta != 0) return delta;
+      if (a == b) return 0;
+      return a.compareTo(b);
+    }));
+  });
+  test("compareAsciiUpperCase", () {
+    expect(sortedBy(compareAsciiUpperCase), sortedBy((a, b) {
+      int delta = a.toUpperCase().compareTo(b.toUpperCase());
+      if (delta != 0) return delta;
+      if (a == b) return 0;
+      return a.compareTo(b);
+    }));
+  });
+
+  // Replace any digit sequence by ("0", value, length) as char codes.
+  // This will sort alphabetically (by charcode) the way digits sort
+  // numerically, and the leading 0 means it sorts like a digit
+  // compared to non-digits.
+  replaceNumbers(String string) =>
+      string.replaceAllMapped(new RegExp(r"\d+"), (m) {
+        var digits = m[0];
+        return new String.fromCharCodes(
+            [0x30, int.parse(digits), digits.length]);
+      });
+
+  test("compareNatural", () {
+    expect(sortedBy(compareNatural),
+        sortedBy((a, b) => replaceNumbers(a).compareTo(replaceNumbers(b))));
+  });
+
+  test("compareAsciiLowerCaseNatural", () {
+    expect(sortedBy(compareAsciiLowerCaseNatural), sortedBy((a, b) {
+      int delta = replaceNumbers(a.toLowerCase())
+          .compareTo(replaceNumbers(b.toLowerCase()));
+      if (delta != 0) return delta;
+      if (a == b) return 0;
+      return a.compareTo(b);
+    }));
+  });
+
+  test("compareAsciiUpperCaseNatural", () {
+    expect(sortedBy(compareAsciiUpperCaseNatural), sortedBy((a, b) {
+      int delta = replaceNumbers(a.toUpperCase())
+          .compareTo(replaceNumbers(b.toUpperCase()));
+      if (delta != 0) return delta;
+      if (a == b) return 0;
+      return a.compareTo(b);
+    }));
+  });
+}
diff --git a/packages/collection/test/equality_map_test.dart b/packages/collection/test/equality_map_test.dart
new file mode 100644
index 0000000..8225efd
--- /dev/null
+++ b/packages/collection/test/equality_map_test.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2016, 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:collection/collection.dart';
+import 'package:test/test.dart';
+
+void main() {
+  test("uses the given equality", () {
+    var map = new EqualityMap(const IterableEquality());
+    expect(map, isEmpty);
+
+    map[[1, 2, 3]] = 1;
+    expect(map, containsPair([1, 2, 3], 1));
+
+    map[[1, 2, 3]] = 2;
+    expect(map, containsPair([1, 2, 3], 2));
+
+    map[[2, 3, 4]] = 3;
+    expect(map, containsPair([1, 2, 3], 2));
+    expect(map, containsPair([2, 3, 4], 3));
+  });
+
+  test("EqualityMap.from() prefers the lattermost equivalent key", () {
+    var map = new EqualityMap.from(const IterableEquality(), {
+      [1, 2, 3]: 1,
+      [2, 3, 4]: 2,
+      [1, 2, 3]: 3,
+      [2, 3, 4]: 4,
+      [1, 2, 3]: 5,
+      [1, 2, 3]: 6,
+    });
+
+    expect(map, containsPair([1, 2, 3], 6));
+    expect(map, containsPair([2, 3, 4], 4));
+  });
+}
diff --git a/packages/collection/test/equality_set_test.dart b/packages/collection/test/equality_set_test.dart
new file mode 100644
index 0000000..a326b31
--- /dev/null
+++ b/packages/collection/test/equality_set_test.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2016, 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:collection/collection.dart';
+import 'package:test/test.dart';
+
+void main() {
+  test("uses the given equality", () {
+    var set = new EqualitySet(const IterableEquality());
+    expect(set, isEmpty);
+
+    var list1 = [1, 2, 3];
+    expect(set.add(list1), isTrue);
+    expect(set, contains([1, 2, 3]));
+    expect(set, contains(same(list1)));
+
+    var list2 = [1, 2, 3];
+    expect(set.add(list2), isFalse);
+    expect(set, contains([1, 2, 3]));
+    expect(set, contains(same(list1)));
+    expect(set, isNot(contains(same(list2))));
+
+    var list3 = [2, 3, 4];
+    expect(set.add(list3), isTrue);
+    expect(set, contains(same(list1)));
+    expect(set, contains(same(list3)));
+  });
+
+  test("EqualitySet.from() prefers the lattermost equivalent value", () {
+    var list1 = [1, 2, 3];
+    var list2 = [2, 3, 4];
+    var list3 = [1, 2, 3];
+    var list4 = [2, 3, 4];
+    var list5 = [1, 2, 3];
+    var list6 = [1, 2, 3];
+
+    var set = new EqualitySet.from(
+        const IterableEquality(), [list1, list2, list3, list4, list5, list6]);
+
+    expect(set, contains(same(list1)));
+    expect(set, contains(same(list2)));
+    expect(set, isNot(contains(same(list3))));
+    expect(set, isNot(contains(same(list4))));
+    expect(set, isNot(contains(same(list5))));
+    expect(set, isNot(contains(same(list6))));
+  });
+}
diff --git a/packages/collection/test/equality_test.dart b/packages/collection/test/equality_test.dart
new file mode 100644
index 0000000..ffc8655
--- /dev/null
+++ b/packages/collection/test/equality_test.dart
@@ -0,0 +1,243 @@
+// 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.
+
+// Tests equality utilities.
+
+import "dart:collection";
+import "package:collection/collection.dart";
+import "package:test/test.dart";
+
+main() {
+  o(Comparable id) => new Element(id);
+
+  // Lists that are point-wise equal, but not identical.
+  var list1 = [o(1), o(2), o(3), o(4), o(5)];
+  var list2 = [o(1), o(2), o(3), o(4), o(5)];
+  // Similar length list with equal elements in different order.
+  var list3 = [o(1), o(3), o(5), o(4), o(2)];
+
+  test("IterableEquality - List", () {
+    expect(const IterableEquality().equals(list1, list2), isTrue);
+    Equality iterId = const IterableEquality(const IdentityEquality());
+    expect(iterId.equals(list1, list2), isFalse);
+  });
+
+  test("IterableEquality - LinkedSet", () {
+    var l1 = new LinkedHashSet.from(list1);
+    var l2 = new LinkedHashSet.from(list2);
+    expect(const IterableEquality().equals(l1, l2), isTrue);
+    Equality iterId = const IterableEquality(const IdentityEquality());
+    expect(iterId.equals(l1, l2), isFalse);
+  });
+
+  test("ListEquality", () {
+    expect(const ListEquality().equals(list1, list2), isTrue);
+    Equality listId = const ListEquality(const IdentityEquality());
+    expect(listId.equals(list1, list2), isFalse);
+  });
+
+  test("ListInequality length", () {
+    var list4 = [o(1), o(2), o(3), o(4), o(5), o(6)];
+    expect(const ListEquality().equals(list1, list4), isFalse);
+    expect(const ListEquality(const IdentityEquality()).equals(list1, list4),
+        isFalse);
+  });
+
+  test("ListInequality value", () {
+    var list5 = [o(1), o(2), o(3), o(4), o(6)];
+    expect(const ListEquality().equals(list1, list5), isFalse);
+    expect(const ListEquality(const IdentityEquality()).equals(list1, list5),
+        isFalse);
+  });
+
+  test("UnorderedIterableEquality", () {
+    expect(const UnorderedIterableEquality().equals(list1, list3), isTrue);
+    Equality uniterId =
+        const UnorderedIterableEquality(const IdentityEquality());
+    expect(uniterId.equals(list1, list3), isFalse);
+  });
+
+  test("UnorderedIterableInequality length", () {
+    var list6 = [o(1), o(3), o(5), o(4), o(2), o(1)];
+    expect(const UnorderedIterableEquality().equals(list1, list6), isFalse);
+    expect(
+        const UnorderedIterableEquality(const IdentityEquality())
+            .equals(list1, list6),
+        isFalse);
+  });
+
+  test("UnorderedIterableInequality values", () {
+    var list7 = [o(1), o(3), o(5), o(4), o(6)];
+    expect(const UnorderedIterableEquality().equals(list1, list7), isFalse);
+    expect(
+        const UnorderedIterableEquality(const IdentityEquality())
+            .equals(list1, list7),
+        isFalse);
+  });
+
+  test("SetEquality", () {
+    var set1 = new HashSet.from(list1);
+    var set2 = new LinkedHashSet.from(list3);
+    expect(const SetEquality().equals(set1, set2), isTrue);
+    Equality setId = const SetEquality(const IdentityEquality());
+    expect(setId.equals(set1, set2), isFalse);
+  });
+
+  test("SetInequality length", () {
+    var list8 = [o(1), o(3), o(5), o(4), o(2), o(6)];
+    var set1 = new HashSet.from(list1);
+    var set2 = new LinkedHashSet.from(list8);
+    expect(const SetEquality().equals(set1, set2), isFalse);
+    expect(const SetEquality(const IdentityEquality()).equals(set1, set2),
+        isFalse);
+  });
+
+  test("SetInequality value", () {
+    var list7 = [o(1), o(3), o(5), o(4), o(6)];
+    var set1 = new HashSet.from(list1);
+    var set2 = new LinkedHashSet.from(list7);
+    expect(const SetEquality().equals(set1, set2), isFalse);
+    expect(const SetEquality(const IdentityEquality()).equals(set1, set2),
+        isFalse);
+  });
+
+  var map1a = {
+    "x": [o(1), o(2), o(3)],
+    "y": [true, false, null]
+  };
+  var map1b = {
+    "x": [o(4), o(5), o(6)],
+    "y": [false, true, null]
+  };
+  var map2a = {
+    "x": [o(3), o(2), o(1)],
+    "y": [false, true, null]
+  };
+  var map2b = {
+    "x": [o(6), o(5), o(4)],
+    "y": [null, false, true]
+  };
+  var l1 = [map1a, map1b];
+  var l2 = [map2a, map2b];
+  var s1 = new Set<Map>.from(l1);
+  var s2 = new Set<Map>.from([map2b, map2a]);
+
+  test("RecursiveEquality", () {
+    const unordered = const UnorderedIterableEquality();
+    expect(unordered.equals(map1a["x"], map2a["x"]), isTrue);
+    expect(unordered.equals(map1a["y"], map2a["y"]), isTrue);
+    expect(unordered.equals(map1b["x"], map2b["x"]), isTrue);
+    expect(unordered.equals(map1b["y"], map2b["y"]), isTrue);
+    const mapval = const MapEquality(values: unordered);
+    expect(mapval.equals(map1a, map2a), isTrue);
+    expect(mapval.equals(map1b, map2b), isTrue);
+    const listmapval = const ListEquality(mapval);
+    expect(listmapval.equals(l1, l2), isTrue);
+    const setmapval = const SetEquality<Map>(mapval);
+    expect(setmapval.equals(s1, s2), isTrue);
+  });
+
+  test("DeepEquality", () {
+    var colleq = const DeepCollectionEquality.unordered();
+    expect(colleq.equals(map1a["x"], map2a["x"]), isTrue);
+    expect(colleq.equals(map1a["y"], map2a["y"]), isTrue);
+    expect(colleq.equals(map1b["x"], map2b["x"]), isTrue);
+    expect(colleq.equals(map1b["y"], map2b["y"]), isTrue);
+    expect(colleq.equals(map1a, map2a), isTrue);
+    expect(colleq.equals(map1b, map2b), isTrue);
+    expect(colleq.equals(l1, l2), isTrue);
+    expect(colleq.equals(s1, s2), isTrue);
+  });
+
+  test("CaseInsensitiveEquality", () {
+    var equality = const CaseInsensitiveEquality();
+    expect(equality.equals("foo", "foo"), isTrue);
+    expect(equality.equals("fOo", "FoO"), isTrue);
+    expect(equality.equals("FoO", "fOo"), isTrue);
+    expect(equality.equals("foo", "bar"), isFalse);
+    expect(equality.equals("fÕÕ", "fõõ"), isFalse);
+
+    expect(equality.hash("foo"), equals(equality.hash("foo")));
+    expect(equality.hash("fOo"), equals(equality.hash("FoO")));
+    expect(equality.hash("FoO"), equals(equality.hash("fOo")));
+    expect(equality.hash("foo"), isNot(equals(equality.hash("bar"))));
+    expect(equality.hash("fÕÕ"), isNot(equals(equality.hash("fõõ"))));
+  });
+
+  group("EqualityBy should use a derived value for ", () {
+    var firstEquality = new EqualityBy<List<String>, String>((e) => e.first);
+    var firstInsensitiveEquality = new EqualityBy<List<String>, String>(
+        (e) => e.first, const CaseInsensitiveEquality());
+    var firstObjectEquality = new EqualityBy<List<Object>, Object>(
+        (e) => e.first, const IterableEquality());
+
+    test("equality", () {
+      expect(firstEquality.equals(["foo", "foo"], ["foo", "bar"]), isTrue);
+      expect(firstEquality.equals(["foo", "foo"], ["bar", "bar"]), isFalse);
+    });
+
+    test("equality with an inner equality", () {
+      expect(firstInsensitiveEquality.equals(["fOo"], ["FoO"]), isTrue);
+      expect(firstInsensitiveEquality.equals(["foo"], ["ffõõ"]), isFalse);
+    });
+
+    test("hash", () {
+      expect(firstEquality.hash(["foo", "bar"]), "foo".hashCode);
+    });
+
+    test("hash with an inner equality", () {
+      expect(firstInsensitiveEquality.hash(["fOo"]),
+          const CaseInsensitiveEquality().hash("foo"));
+    });
+
+    test("isValidKey", () {
+      expect(firstEquality.isValidKey(["foo"]), isTrue);
+      expect(firstEquality.isValidKey("foo"), isFalse);
+      expect(firstEquality.isValidKey([1]), isFalse);
+    });
+
+    test('isValidKey with an inner equality', () {
+      expect(firstObjectEquality.isValidKey([[]]), isTrue);
+      expect(firstObjectEquality.isValidKey([{}]), isFalse);
+    });
+  });
+
+  test("Equality accepts null", () {
+    var ie = new IterableEquality();
+    var le = new ListEquality();
+    var se = new SetEquality();
+    var me = new MapEquality();
+    expect(ie.equals(null, null), true);
+    expect(ie.equals([], null), false);
+    expect(ie.equals(null, []), false);
+    expect(ie.hash(null), null.hashCode);
+
+    expect(le.equals(null, null), true);
+    expect(le.equals([], null), false);
+    expect(le.equals(null, []), false);
+    expect(le.hash(null), null.hashCode);
+
+    expect(se.equals(null, null), true);
+    expect(se.equals(new Set(), null), false);
+    expect(se.equals(null, new Set()), false);
+    expect(se.hash(null), null.hashCode);
+
+    expect(me.equals(null, null), true);
+    expect(me.equals({}, null), false);
+    expect(me.equals(null, {}), false);
+    expect(me.hash(null), null.hashCode);
+  });
+}
+
+/// Wrapper objects for an `id` value.
+///
+/// Compares the `id` value by equality and for comparison.
+/// Allows creating simple objects that are equal without being identical.
+class Element implements Comparable<Element> {
+  final Comparable id;
+  const Element(this.id);
+  int get hashCode => id.hashCode;
+  bool operator ==(Object other) => other is Element && id == other.id;
+  int compareTo(other) => id.compareTo(other.id);
+}
diff --git a/packages/collection/test/functions_test.dart b/packages/collection/test/functions_test.dart
new file mode 100644
index 0000000..a1e8f08
--- /dev/null
+++ b/packages/collection/test/functions_test.dart
@@ -0,0 +1,332 @@
+// Copyright (c) 2016, 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:test/test.dart";
+
+import "package:collection/collection.dart";
+
+void main() {
+  group("mapMap()", () {
+    test("with an empty map returns an empty map", () {
+      expect(
+          mapMap({},
+              key: expectAsync2((_, __) {}, count: 0),
+              value: expectAsync2((_, __) {}, count: 0)),
+          isEmpty);
+    });
+
+    test("with no callbacks, returns a copy of the map", () {
+      var map = {"foo": 1, "bar": 2};
+      var result = mapMap(map);
+      expect(result, equals({"foo": 1, "bar": 2}));
+
+      // The resulting map should be a copy.
+      result["foo"] = 3;
+      expect(map, equals({"foo": 1, "bar": 2}));
+    });
+
+    test("maps the map's keys", () {
+      expect(mapMap({"foo": 1, "bar": 2}, key: (key, value) => key[value]),
+          equals({"o": 1, "r": 2}));
+    });
+
+    test("maps the map's values", () {
+      expect(mapMap({"foo": 1, "bar": 2}, value: (key, value) => key[value]),
+          equals({"foo": "o", "bar": "r"}));
+    });
+
+    test("maps both the map's keys and values", () {
+      expect(
+          mapMap({"foo": 1, "bar": 2},
+              key: (key, value) => "$key$value",
+              value: (key, value) => key[value]),
+          equals({"foo1": "o", "bar2": "r"}));
+    });
+  });
+
+  group("mergeMaps()", () {
+    test("with empty maps returns an empty map", () {
+      expect(mergeMaps({}, {}, value: expectAsync2((_, __) {}, count: 0)),
+          isEmpty);
+    });
+
+    test("returns a map with all values in both input maps", () {
+      expect(mergeMaps({"foo": 1, "bar": 2}, {"baz": 3, "qux": 4}),
+          equals({"foo": 1, "bar": 2, "baz": 3, "qux": 4}));
+    });
+
+    test("the second map's values win by default", () {
+      expect(mergeMaps({"foo": 1, "bar": 2}, {"bar": 3, "baz": 4}),
+          equals({"foo": 1, "bar": 3, "baz": 4}));
+    });
+
+    test("uses the callback to merge values", () {
+      expect(
+          mergeMaps({"foo": 1, "bar": 2}, {"bar": 3, "baz": 4},
+              value: (value1, value2) => value1 + value2),
+          equals({"foo": 1, "bar": 5, "baz": 4}));
+    });
+  });
+
+  group("groupBy()", () {
+    test("returns an empty map for an empty iterable", () {
+      expect(groupBy([], expectAsync1((_) {}, count: 0)), isEmpty);
+    });
+
+    test("groups elements by the function's return value", () {
+      expect(
+          groupBy(["foo", "bar", "baz", "bop", "qux"], (string) => string[1]),
+          equals({
+            "o": ["foo", "bop"],
+            "a": ["bar", "baz"],
+            "u": ["qux"]
+          }));
+    });
+  });
+
+  group("minBy()", () {
+    test("returns null for an empty iterable", () {
+      expect(
+          minBy([], expectAsync1((_) {}, count: 0),
+              compare: expectAsync2((_, __) {}, count: 0)),
+          isNull);
+    });
+
+    test(
+        "returns the element for which the ordering function returns the "
+        "smallest value", () {
+      expect(
+          minBy([
+            {"foo": 3},
+            {"foo": 5},
+            {"foo": 4},
+            {"foo": 1},
+            {"foo": 2}
+          ], (map) => map["foo"]),
+          equals({"foo": 1}));
+    });
+
+    test("uses a custom comparator if provided", () {
+      expect(
+          minBy<Map<String, int>, Map<String, int>>([
+            {"foo": 3},
+            {"foo": 5},
+            {"foo": 4},
+            {"foo": 1},
+            {"foo": 2}
+          ], (map) => map,
+              compare: (map1, map2) => map1["foo"].compareTo(map2["foo"])),
+          equals({"foo": 1}));
+    });
+  });
+
+  group("maxBy()", () {
+    test("returns null for an empty iterable", () {
+      expect(
+          maxBy([], expectAsync1((_) {}, count: 0),
+              compare: expectAsync2((_, __) {}, count: 0)),
+          isNull);
+    });
+
+    test(
+        "returns the element for which the ordering function returns the "
+        "largest value", () {
+      expect(
+          maxBy([
+            {"foo": 3},
+            {"foo": 5},
+            {"foo": 4},
+            {"foo": 1},
+            {"foo": 2}
+          ], (map) => map["foo"]),
+          equals({"foo": 5}));
+    });
+
+    test("uses a custom comparator if provided", () {
+      expect(
+          maxBy<Map<String, int>, Map<String, int>>([
+            {"foo": 3},
+            {"foo": 5},
+            {"foo": 4},
+            {"foo": 1},
+            {"foo": 2}
+          ], (map) => map,
+              compare: (map1, map2) => map1["foo"].compareTo(map2["foo"])),
+          equals({"foo": 5}));
+    });
+  });
+
+  group("transitiveClosure()", () {
+    test("returns an empty map for an empty graph", () {
+      expect(transitiveClosure({}), isEmpty);
+    });
+
+    test("returns the input when there are no transitive connections", () {
+      expect(
+          transitiveClosure({
+            "foo": ["bar"],
+            "bar": [],
+            "bang": ["qux", "zap"],
+            "qux": [],
+            "zap": []
+          }),
+          equals({
+            "foo": ["bar"],
+            "bar": [],
+            "bang": ["qux", "zap"],
+            "qux": [],
+            "zap": []
+          }));
+    });
+
+    test("flattens transitive connections", () {
+      expect(
+          transitiveClosure({
+            "qux": [],
+            "bar": ["baz"],
+            "baz": ["qux"],
+            "foo": ["bar"]
+          }),
+          equals({
+            "foo": ["bar", "baz", "qux"],
+            "bar": ["baz", "qux"],
+            "baz": ["qux"],
+            "qux": []
+          }));
+    });
+
+    test("handles loops", () {
+      expect(
+          transitiveClosure({
+            "foo": ["bar"],
+            "bar": ["baz"],
+            "baz": ["foo"]
+          }),
+          equals({
+            "foo": ["bar", "baz", "foo"],
+            "bar": ["baz", "foo", "bar"],
+            "baz": ["foo", "bar", "baz"]
+          }));
+    });
+  });
+
+  group("stronglyConnectedComponents()", () {
+    test("returns an empty list for an empty graph", () {
+      expect(stronglyConnectedComponents({}), isEmpty);
+    });
+
+    test("returns one set for a singleton graph", () {
+      expect(
+          stronglyConnectedComponents({"a": []}),
+          equals([
+            new Set.from(["a"])
+          ]));
+    });
+
+    test("returns two sets for a two-element tree", () {
+      expect(
+          stronglyConnectedComponents({
+            "a": ["b"],
+            "b": []
+          }),
+          equals([
+            new Set.from(["a"]),
+            new Set.from(["b"])
+          ]));
+    });
+
+    test("returns one set for a two-element loop", () {
+      expect(
+          stronglyConnectedComponents({
+            "a": ["b"],
+            "b": ["a"]
+          }),
+          equals([
+            new Set.from(["a", "b"])
+          ]));
+    });
+
+    test("returns individual vertices for a tree", () {
+      expect(
+          stronglyConnectedComponents({
+            "foo": ["bar"],
+            "bar": ["baz", "bang"],
+            "baz": ["qux"],
+            "bang": ["zap"],
+            "qux": [],
+            "zap": []
+          }),
+          equals([
+            // This is expected to return *a* topological ordering, but this isn't
+            // the only valid one. If the function implementation changes in the
+            // future, this test may need to be updated.
+            new Set.from(["foo"]),
+            new Set.from(["bar"]),
+            new Set.from(["bang"]),
+            new Set.from(["zap"]),
+            new Set.from(["baz"]),
+            new Set.from(["qux"])
+          ]));
+    });
+
+    test("returns a single set for a fully cyclic graph", () {
+      expect(
+          stronglyConnectedComponents({
+            "foo": ["bar"],
+            "bar": ["baz"],
+            "baz": ["bang"],
+            "bang": ["foo"]
+          }),
+          equals([
+            new Set.from(["foo", "bar", "baz", "bang"])
+          ]));
+    });
+
+    test("returns separate sets for each strongly connected component", () {
+      // https://en.wikipedia.org/wiki/Strongly_connected_component#/media/File:Scc.png
+      expect(
+          stronglyConnectedComponents({
+            "a": ["b"],
+            "b": ["c", "e", "f"],
+            "c": ["d", "g"],
+            "d": ["c", "h"],
+            "e": ["a", "f"],
+            "f": ["g"],
+            "g": ["f"],
+            "h": ["g", "d"]
+          }),
+          equals([
+            // This is expected to return *a* topological ordering, but this isn't
+            // the only valid one. If the function implementation changes in the
+            // future, this test may need to be updated.
+            new Set.from(["a", "b", "e"]),
+            new Set.from(["c", "d", "h"]),
+            new Set.from(["f", "g"]),
+          ]));
+    });
+
+    test("always returns components in topological order", () {
+      expect(
+          stronglyConnectedComponents({
+            "bar": ["baz", "bang"],
+            "zap": [],
+            "baz": ["qux"],
+            "qux": [],
+            "foo": ["bar"],
+            "bang": ["zap"]
+          }),
+          equals([
+            // This is expected to return *a* topological ordering, but this isn't
+            // the only valid one. If the function implementation changes in the
+            // future, this test may need to be updated.
+            new Set.from(["foo"]),
+            new Set.from(["bar"]),
+            new Set.from(["bang"]),
+            new Set.from(["zap"]),
+            new Set.from(["baz"]),
+            new Set.from(["qux"])
+          ]));
+    });
+  });
+}
diff --git a/packages/collection/test/ignore_ascii_case_test.dart b/packages/collection/test/ignore_ascii_case_test.dart
new file mode 100644
index 0000000..5e3ee4f
--- /dev/null
+++ b/packages/collection/test/ignore_ascii_case_test.dart
@@ -0,0 +1,59 @@
+// Copyright (c) 2016, 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.
+
+/// Tests case-ignoring compare and equality.
+
+import "package:collection/collection.dart";
+import "package:test/test.dart";
+
+main() {
+  test("equality ignore ASCII case", () {
+    var strings = [
+      "0@`aopz[{",
+      "0@`aopz[{",
+      "0@`Aopz[{",
+      "0@`aOpz[{",
+      "0@`AOpz[{",
+      "0@`aoPz[{",
+      "0@`AoPz[{",
+      "0@`aOPz[{",
+      "0@`AOPz[{",
+      "0@`aopZ[{",
+      "0@`AopZ[{",
+      "0@`aOpZ[{",
+      "0@`AOpZ[{",
+      "0@`aoPZ[{",
+      "0@`AoPZ[{",
+      "0@`aOPZ[{",
+      "0@`AOPZ[{",
+    ];
+
+    for (var s1 in strings) {
+      for (var s2 in strings) {
+        var reason = "$s1 =?= $s2";
+        expect(equalsIgnoreAsciiCase(s1, s2), true, reason: reason);
+        expect(hashIgnoreAsciiCase(s1), hashIgnoreAsciiCase(s2),
+            reason: reason);
+      }
+    }
+
+    var upperCaseLetters = "@`abcdefghijklmnopqrstuvwxyz[{åÅ";
+    var lowerCaseLetters = "@`ABCDEFGHIJKLMNOPQRSTUVWXYZ[{åÅ";
+    expect(equalsIgnoreAsciiCase(upperCaseLetters, lowerCaseLetters), true);
+
+    testChars(String char1, String char2, bool areEqual) {
+      expect(equalsIgnoreAsciiCase(char1, char2), areEqual,
+          reason: "$char1 ${areEqual ? "=" : "!"}= $char2");
+    }
+
+    for (int i = 0; i < upperCaseLetters.length; i++) {
+      for (int j = 0; i < upperCaseLetters.length; i++) {
+        testChars(upperCaseLetters[i], upperCaseLetters[j], i == j);
+        testChars(lowerCaseLetters[i], upperCaseLetters[j], i == j);
+        testChars(upperCaseLetters[i], lowerCaseLetters[j], i == j);
+        testChars(lowerCaseLetters[i], lowerCaseLetters[j], i == j);
+      }
+    }
+  });
+}
diff --git a/packages/collection/test/iterable_zip_test.dart b/packages/collection/test/iterable_zip_test.dart
new file mode 100644
index 0000000..7b6db73
--- /dev/null
+++ b/packages/collection/test/iterable_zip_test.dart
@@ -0,0 +1,217 @@
+// 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.
+
+import "dart:collection";
+
+import "package:test/test.dart";
+
+import "package:collection/collection.dart";
+
+/// Iterable like [base] except that it throws when value equals [errorValue].
+Iterable iterError(Iterable base, int errorValue) {
+  // ignore: only_throw_errors
+  return base.map((x) => x == errorValue ? throw "BAD" : x);
+}
+
+main() {
+  test("Basic", () {
+    expect(
+        new IterableZip([
+          [1, 2, 3],
+          [4, 5, 6],
+          [7, 8, 9]
+        ]),
+        equals([
+          [1, 4, 7],
+          [2, 5, 8],
+          [3, 6, 9]
+        ]));
+  });
+
+  test("Uneven length 1", () {
+    expect(
+        new IterableZip([
+          [1, 2, 3, 99, 100],
+          [4, 5, 6],
+          [7, 8, 9]
+        ]),
+        equals([
+          [1, 4, 7],
+          [2, 5, 8],
+          [3, 6, 9]
+        ]));
+  });
+
+  test("Uneven length 2", () {
+    expect(
+        new IterableZip([
+          [1, 2, 3],
+          [4, 5, 6, 99, 100],
+          [7, 8, 9]
+        ]),
+        equals([
+          [1, 4, 7],
+          [2, 5, 8],
+          [3, 6, 9]
+        ]));
+  });
+
+  test("Uneven length 3", () {
+    expect(
+        new IterableZip([
+          [1, 2, 3],
+          [4, 5, 6],
+          [7, 8, 9, 99, 100]
+        ]),
+        equals([
+          [1, 4, 7],
+          [2, 5, 8],
+          [3, 6, 9]
+        ]));
+  });
+
+  test("Uneven length 3", () {
+    expect(
+        new IterableZip([
+          [1, 2, 3, 98],
+          [4, 5, 6],
+          [7, 8, 9, 99, 100]
+        ]),
+        equals([
+          [1, 4, 7],
+          [2, 5, 8],
+          [3, 6, 9]
+        ]));
+  });
+
+  test("Empty 1", () {
+    expect(
+        new IterableZip([
+          [],
+          [4, 5, 6],
+          [7, 8, 9]
+        ]),
+        equals([]));
+  });
+
+  test("Empty 2", () {
+    expect(
+        new IterableZip([
+          [1, 2, 3],
+          [],
+          [7, 8, 9]
+        ]),
+        equals([]));
+  });
+
+  test("Empty 3", () {
+    expect(
+        new IterableZip([
+          [1, 2, 3],
+          [4, 5, 6],
+          []
+        ]),
+        equals([]));
+  });
+
+  test("Empty source", () {
+    expect(new IterableZip([]), equals([]));
+  });
+
+  test("Single Source", () {
+    expect(
+        new IterableZip([
+          [1, 2, 3]
+        ]),
+        equals([
+          [1],
+          [2],
+          [3]
+        ]));
+  });
+
+  test("Not-lists", () {
+    // Use other iterables than list literals.
+    Iterable it1 = [1, 2, 3, 4, 5, 6].where((x) => x < 4);
+    Set it2 = new LinkedHashSet()..add(4)..add(5)..add(6);
+    Iterable it3 = (new LinkedHashMap()
+          ..[7] = 0
+          ..[8] = 0
+          ..[9] = 0)
+        .keys;
+    Iterable<Iterable> allIts =
+        new Iterable.generate(3, (i) => [it1, it2, it3][i]);
+    expect(
+        new IterableZip(allIts),
+        equals([
+          [1, 4, 7],
+          [2, 5, 8],
+          [3, 6, 9]
+        ]));
+  });
+
+  test("Error 1", () {
+    expect(
+        () => new IterableZip([
+              iterError([1, 2, 3], 2),
+              [4, 5, 6],
+              [7, 8, 9]
+            ]).toList(),
+        throwsA(equals("BAD")));
+  });
+
+  test("Error 2", () {
+    expect(
+        () => new IterableZip([
+              [1, 2, 3],
+              iterError([4, 5, 6], 5),
+              [7, 8, 9]
+            ]).toList(),
+        throwsA(equals("BAD")));
+  });
+
+  test("Error 3", () {
+    expect(
+        () => new IterableZip([
+              [1, 2, 3],
+              [4, 5, 6],
+              iterError([7, 8, 9], 8)
+            ]).toList(),
+        throwsA(equals("BAD")));
+  });
+
+  test("Error at end", () {
+    expect(
+        () => new IterableZip([
+              [1, 2, 3],
+              iterError([4, 5, 6], 6),
+              [7, 8, 9]
+            ]).toList(),
+        throwsA(equals("BAD")));
+  });
+
+  test("Error before first end", () {
+    expect(
+        () => new IterableZip([
+              iterError([1, 2, 3, 4], 4),
+              [4, 5, 6],
+              [7, 8, 9]
+            ]).toList(),
+        throwsA(equals("BAD")));
+  });
+
+  test("Error after first end", () {
+    expect(
+        new IterableZip([
+          [1, 2, 3],
+          [4, 5, 6],
+          iterError([7, 8, 9, 10], 10)
+        ]),
+        equals([
+          [1, 4, 7],
+          [2, 5, 8],
+          [3, 6, 9]
+        ]));
+  });
+}
diff --git a/packages/collection/test/priority_queue_test.dart b/packages/collection/test/priority_queue_test.dart
new file mode 100644
index 0000000..bcb892f
--- /dev/null
+++ b/packages/collection/test/priority_queue_test.dart
@@ -0,0 +1,171 @@
+// 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.
+
+/// Tests priority queue implementations utilities.
+
+import "package:test/test.dart";
+
+import "package:collection/src/priority_queue.dart";
+
+void main() {
+  testDefault();
+  testInt(() => new HeapPriorityQueue<int>());
+  testCustom((comparator) => new HeapPriorityQueue<C>(comparator));
+}
+
+void testDefault() {
+  test('new PriorityQueue() returns a HeapPriorityQueue', () {
+    expect(
+        new PriorityQueue<int>(), new isInstanceOf<HeapPriorityQueue<int>>());
+  });
+  testInt(() => new PriorityQueue<int>());
+  testCustom((comparator) => new PriorityQueue<C>(comparator));
+}
+
+void testInt(PriorityQueue<int> create()) {
+  for (int count in [1, 5, 127, 128]) {
+    testQueue(
+        "int:$count", create, new List<int>.generate(count, (x) => x), count);
+  }
+}
+
+void testCustom(PriorityQueue<C> create(int comparator(C a, C b))) {
+  for (int count in [1, 5, 127, 128]) {
+    testQueue("Custom:$count/null", () => create(null),
+        new List<C>.generate(count, (x) => new C(x)), new C(count));
+    testQueue("Custom:$count/compare", () => create(compare),
+        new List<C>.generate(count, (x) => new C(x)), new C(count));
+    testQueue("Custom:$count/compareNeg", () => create(compareNeg),
+        new List<C>.generate(count, (x) => new C(count - x)), new C(0));
+  }
+}
+
+/// Test that a queue behaves correctly.
+///
+/// The elements must be in priority order, from highest to lowest.
+void testQueue(String name, PriorityQueue create(), List elements, notElement) {
+  test(name, () => testQueueBody(create, elements, notElement));
+}
+
+void testQueueBody(PriorityQueue create(), List elements, notElement) {
+  PriorityQueue q = create();
+  expect(q.isEmpty, isTrue);
+  expect(q, hasLength(0));
+  expect(() {
+    q.first;
+  }, throwsStateError);
+  expect(() {
+    q.removeFirst();
+  }, throwsStateError);
+
+  // Tests removeFirst, first, contains, toList and toSet.
+  void testElements() {
+    expect(q.isNotEmpty, isTrue);
+    expect(q, hasLength(elements.length));
+
+    expect(q.toList(), equals(elements));
+    expect(q.toSet().toList(), equals(elements));
+
+    for (int i = 0; i < elements.length; i++) {
+      expect(q.contains(elements[i]), isTrue);
+    }
+    expect(q.contains(notElement), isFalse);
+
+    List all = [];
+    while (q.isNotEmpty) {
+      var expected = q.first;
+      var actual = q.removeFirst();
+      expect(actual, same(expected));
+      all.add(actual);
+    }
+
+    expect(all.length, elements.length);
+    for (int i = 0; i < all.length; i++) {
+      expect(all[i], same(elements[i]));
+    }
+
+    expect(q.isEmpty, isTrue);
+  }
+
+  q.addAll(elements);
+  testElements();
+
+  q.addAll(elements.reversed);
+  testElements();
+
+  // Add elements in a non-linear order (gray order).
+  for (int i = 0, j = 0; i < elements.length; i++) {
+    int gray;
+    do {
+      gray = j ^ (j >> 1);
+      j++;
+    } while (gray >= elements.length);
+    q.add(elements[gray]);
+  }
+  testElements();
+
+  // Add elements by picking the middle element first, and then recursing
+  // on each side.
+  void addRec(int min, int max) {
+    int mid = min + ((max - min) >> 1);
+    q.add(elements[mid]);
+    if (mid + 1 < max) addRec(mid + 1, max);
+    if (mid > min) addRec(min, mid);
+  }
+
+  addRec(0, elements.length);
+  testElements();
+
+  // Test removeAll.
+  q.addAll(elements);
+  expect(q, hasLength(elements.length));
+  Iterable all = q.removeAll();
+  expect(q.isEmpty, isTrue);
+  expect(all, hasLength(elements.length));
+  for (int i = 0; i < elements.length; i++) {
+    expect(all, contains(elements[i]));
+  }
+
+  // Test the same element more than once in queue.
+  q.addAll(elements);
+  q.addAll(elements.reversed);
+  expect(q, hasLength(elements.length * 2));
+  for (int i = 0; i < elements.length; i++) {
+    var element = elements[i];
+    expect(q.contains(element), isTrue);
+    expect(q.removeFirst(), element);
+    expect(q.removeFirst(), element);
+  }
+
+  // Test queue with all same element.
+  var a = elements[0];
+  for (int i = 0; i < elements.length; i++) {
+    q.add(a);
+  }
+  expect(q, hasLength(elements.length));
+  expect(q.contains(a), isTrue);
+  expect(q.contains(notElement), isFalse);
+  q.removeAll().forEach((x) => expect(x, same(a)));
+
+  // Test remove.
+  q.addAll(elements);
+  for (var element in elements.reversed) {
+    expect(q.remove(element), isTrue);
+  }
+  expect(q.isEmpty, isTrue);
+}
+
+// Custom class.
+// Class is comparable, comparators match normal and inverse order.
+int compare(C c1, C c2) => c1.value - c2.value;
+int compareNeg(C c1, C c2) => c2.value - c1.value;
+
+class C implements Comparable<C> {
+  final int value;
+  const C(this.value);
+  int get hashCode => value;
+  bool operator ==(Object other) => other is C && value == other.value;
+  int compareTo(C other) => value - other.value;
+  String toString() => "C($value)";
+}
diff --git a/packages/collection/test/queue_list_test.dart b/packages/collection/test/queue_list_test.dart
new file mode 100644
index 0000000..9e6d199
--- /dev/null
+++ b/packages/collection/test/queue_list_test.dart
@@ -0,0 +1,278 @@
+// 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.
+
+import "package:collection/collection.dart";
+import "package:test/test.dart";
+
+void main() {
+  group("new QueueList()", () {
+    test("creates an empty QueueList", () {
+      expect(new QueueList(), isEmpty);
+    });
+
+    test("takes an initial capacity", () {
+      expect(new QueueList(100), isEmpty);
+    });
+  });
+
+  test("new QueueList.from() copies the contents of an iterable", () {
+    expect(new QueueList.from([1, 2, 3].skip(1)), equals([2, 3]));
+  });
+
+  group("add()", () {
+    test("adds an element to the end of the queue", () {
+      var queue = new QueueList.from([1, 2, 3]);
+      queue.add(4);
+      expect(queue, equals([1, 2, 3, 4]));
+    });
+
+    test("expands a full queue", () {
+      var queue = atCapacity();
+      queue.add(8);
+      expect(queue, equals([1, 2, 3, 4, 5, 6, 7, 8]));
+    });
+  });
+
+  group("addAll()", () {
+    test("adds elements to the end of the queue", () {
+      var queue = new QueueList.from([1, 2, 3]);
+      queue.addAll([4, 5, 6]);
+      expect(queue, equals([1, 2, 3, 4, 5, 6]));
+    });
+
+    test("expands a full queue", () {
+      var queue = atCapacity();
+      queue.addAll([8, 9]);
+      expect(queue, equals([1, 2, 3, 4, 5, 6, 7, 8, 9]));
+    });
+  });
+
+  group("addFirst()", () {
+    test("adds an element to the beginning of the queue", () {
+      var queue = new QueueList.from([1, 2, 3]);
+      queue.addFirst(0);
+      expect(queue, equals([0, 1, 2, 3]));
+    });
+
+    test("expands a full queue", () {
+      var queue = atCapacity();
+      queue.addFirst(0);
+      expect(queue, equals([0, 1, 2, 3, 4, 5, 6, 7]));
+    });
+  });
+
+  group("removeFirst()", () {
+    test("removes an element from the beginning of the queue", () {
+      var queue = new QueueList.from([1, 2, 3]);
+      expect(queue.removeFirst(), equals(1));
+      expect(queue, equals([2, 3]));
+    });
+
+    test(
+        "removes an element from the beginning of a queue with an internal "
+        "gap", () {
+      var queue = withInternalGap();
+      expect(queue.removeFirst(), equals(1));
+      expect(queue, equals([2, 3, 4, 5, 6, 7]));
+    });
+
+    test("removes an element from the beginning of a queue at capacity", () {
+      var queue = atCapacity();
+      expect(queue.removeFirst(), equals(1));
+      expect(queue, equals([2, 3, 4, 5, 6, 7]));
+    });
+
+    test("throws a StateError for an empty queue", () {
+      expect(new QueueList().removeFirst, throwsStateError);
+    });
+  });
+
+  group("removeLast()", () {
+    test("removes an element from the end of the queue", () {
+      var queue = new QueueList.from([1, 2, 3]);
+      expect(queue.removeLast(), equals(3));
+      expect(queue, equals([1, 2]));
+    });
+
+    test("removes an element from the end of a queue with an internal gap", () {
+      var queue = withInternalGap();
+      expect(queue.removeLast(), equals(7));
+      expect(queue, equals([1, 2, 3, 4, 5, 6]));
+    });
+
+    test("removes an element from the end of a queue at capacity", () {
+      var queue = atCapacity();
+      expect(queue.removeLast(), equals(7));
+      expect(queue, equals([1, 2, 3, 4, 5, 6]));
+    });
+
+    test("throws a StateError for an empty queue", () {
+      expect(new QueueList().removeLast, throwsStateError);
+    });
+  });
+
+  group("length", () {
+    test("returns the length of a queue", () {
+      expect(new QueueList.from([1, 2, 3]).length, equals(3));
+    });
+
+    test("returns the length of a queue with an internal gap", () {
+      expect(withInternalGap().length, equals(7));
+    });
+
+    test("returns the length of a queue at capacity", () {
+      expect(atCapacity().length, equals(7));
+    });
+  });
+
+  group("length=", () {
+    test("shrinks a larger queue", () {
+      var queue = new QueueList.from([1, 2, 3]);
+      queue.length = 1;
+      expect(queue, equals([1]));
+    });
+
+    test("grows a smaller queue", () {
+      var queue = new QueueList.from([1, 2, 3]);
+      queue.length = 5;
+      expect(queue, equals([1, 2, 3, null, null]));
+    });
+
+    test("throws a RangeError if length is less than 0", () {
+      expect(() => new QueueList().length = -1, throwsRangeError);
+    });
+  });
+
+  group("[]", () {
+    test("returns individual entries in the queue", () {
+      var queue = new QueueList.from([1, 2, 3]);
+      expect(queue[0], equals(1));
+      expect(queue[1], equals(2));
+      expect(queue[2], equals(3));
+    });
+
+    test("returns individual entries in a queue with an internal gap", () {
+      var queue = withInternalGap();
+      expect(queue[0], equals(1));
+      expect(queue[1], equals(2));
+      expect(queue[2], equals(3));
+      expect(queue[3], equals(4));
+      expect(queue[4], equals(5));
+      expect(queue[5], equals(6));
+      expect(queue[6], equals(7));
+    });
+
+    test("throws a RangeError if the index is less than 0", () {
+      var queue = new QueueList.from([1, 2, 3]);
+      expect(() => queue[-1], throwsRangeError);
+    });
+
+    test(
+        "throws a RangeError if the index is greater than or equal to the "
+        "length", () {
+      var queue = new QueueList.from([1, 2, 3]);
+      expect(() => queue[3], throwsRangeError);
+    });
+  });
+
+  group("[]=", () {
+    test("sets individual entries in the queue", () {
+      var queue = new QueueList<dynamic>.from([1, 2, 3]);
+      queue[0] = "a";
+      queue[1] = "b";
+      queue[2] = "c";
+      expect(queue, equals(["a", "b", "c"]));
+    });
+
+    test("sets individual entries in a queue with an internal gap", () {
+      var queue = withInternalGap();
+      queue[0] = "a";
+      queue[1] = "b";
+      queue[2] = "c";
+      queue[3] = "d";
+      queue[4] = "e";
+      queue[5] = "f";
+      queue[6] = "g";
+      expect(queue, equals(["a", "b", "c", "d", "e", "f", "g"]));
+    });
+
+    test("throws a RangeError if the index is less than 0", () {
+      var queue = new QueueList.from([1, 2, 3]);
+      expect(() {
+        queue[-1] = 0;
+      }, throwsRangeError);
+    });
+
+    test(
+        "throws a RangeError if the index is greater than or equal to the "
+        "length", () {
+      var queue = new QueueList.from([1, 2, 3]);
+      expect(() {
+        queue[3] = 4;
+      }, throwsRangeError);
+    });
+  });
+
+  group("throws a modification error for", () {
+    var queue;
+    setUp(() {
+      queue = new QueueList.from([1, 2, 3]);
+    });
+
+    test("add", () {
+      expect(() => queue.forEach((_) => queue.add(4)),
+          throwsConcurrentModificationError);
+    });
+
+    test("addAll", () {
+      expect(() => queue.forEach((_) => queue.addAll([4, 5, 6])),
+          throwsConcurrentModificationError);
+    });
+
+    test("addFirst", () {
+      expect(() => queue.forEach((_) => queue.addFirst(0)),
+          throwsConcurrentModificationError);
+    });
+
+    test("removeFirst", () {
+      expect(() => queue.forEach((_) => queue.removeFirst()),
+          throwsConcurrentModificationError);
+    });
+
+    test("removeLast", () {
+      expect(() => queue.forEach((_) => queue.removeLast()),
+          throwsConcurrentModificationError);
+    });
+
+    test("length=", () {
+      expect(() => queue.forEach((_) => queue.length = 1),
+          throwsConcurrentModificationError);
+    });
+  });
+}
+
+/// Returns a queue whose internal ring buffer is full enough that adding a new
+/// element will expand it.
+QueueList atCapacity() {
+  // Use addAll because [new QueueList.from(List)] won't use the default initial
+  // capacity of 8.
+  return new QueueList()..addAll([1, 2, 3, 4, 5, 6, 7]);
+}
+
+/// Returns a queue whose internal tail has a lower index than its head.
+QueueList withInternalGap() {
+  var queue = new QueueList.from([null, null, null, null, 1, 2, 3, 4]);
+  for (var i = 0; i < 4; i++) {
+    queue.removeFirst();
+  }
+  for (var i = 5; i < 8; i++) {
+    queue.addLast(i);
+  }
+  return queue;
+}
+
+/// Returns a matcher that expects that a closure throws a
+/// [ConcurrentModificationError].
+final throwsConcurrentModificationError =
+    throwsA(new isInstanceOf<ConcurrentModificationError>());
diff --git a/packages/collection/test/typed_wrapper/iterable_test.dart b/packages/collection/test/typed_wrapper/iterable_test.dart
new file mode 100644
index 0000000..5de9651
--- /dev/null
+++ b/packages/collection/test/typed_wrapper/iterable_test.dart
@@ -0,0 +1,354 @@
+// Copyright (c) 2016, 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:collection/collection.dart";
+import "package:test/test.dart";
+
+import '../utils.dart';
+
+void main() {
+  group("with valid types, forwards", () {
+    var wrapper;
+    var emptyWrapper;
+    var singleWrapper;
+    setUp(() {
+      wrapper =
+          DelegatingIterable.typed<int>(<Object>[1, 2, 3, 4, 5].map((i) => i));
+      emptyWrapper = DelegatingIterable.typed<int>(<Object>[].map((i) => i));
+      singleWrapper = DelegatingIterable.typed<int>(<Object>[1].map((i) => i));
+    });
+
+    test("any()", () {
+      expect(wrapper.any((i) => i > 3), isTrue);
+      expect(wrapper.any((i) => i > 5), isFalse);
+    });
+
+    test("contains()", () {
+      expect(wrapper.contains(2), isTrue);
+      expect(wrapper.contains(6), isFalse);
+      expect(wrapper.contains("foo"), isFalse);
+    });
+
+    test("elementAt()", () {
+      expect(wrapper.elementAt(1), equals(2));
+      expect(wrapper.elementAt(4), equals(5));
+      expect(() => wrapper.elementAt(5), throwsRangeError);
+      expect(() => wrapper.elementAt(-1), throwsRangeError);
+    });
+
+    test("every()", () {
+      expect(wrapper.every((i) => i < 6), isTrue);
+      expect(wrapper.every((i) => i > 3), isFalse);
+    });
+
+    test("expand()", () {
+      expect(wrapper.expand((i) => [i]), equals([1, 2, 3, 4, 5]));
+      expect(wrapper.expand((i) => [i, i]),
+          equals([1, 1, 2, 2, 3, 3, 4, 4, 5, 5]));
+    });
+
+    test("first", () {
+      expect(wrapper.first, equals(1));
+      expect(() => emptyWrapper.first, throwsStateError);
+    });
+
+    test("firstWhere()", () {
+      expect(wrapper.firstWhere((i) => i > 3), equals(4));
+      expect(() => wrapper.firstWhere((i) => i > 5), throwsStateError);
+      expect(wrapper.firstWhere((i) => i > 5, orElse: () => -1), equals(-1));
+    });
+
+    test("fold()", () {
+      expect(wrapper.fold("", (previous, i) => previous + i.toString()),
+          equals("12345"));
+      expect(emptyWrapper.fold(null, (previous, i) => previous + i), isNull);
+    });
+
+    test("forEach()", () {
+      var results = [];
+      wrapper.forEach(results.add);
+      expect(results, equals([1, 2, 3, 4, 5]));
+
+      emptyWrapper.forEach(expectAsync1((_) {}, count: 0));
+    });
+
+    test("isEmpty", () {
+      expect(wrapper.isEmpty, isFalse);
+      expect(emptyWrapper.isEmpty, isTrue);
+    });
+
+    test("isNotEmpty", () {
+      expect(wrapper.isNotEmpty, isTrue);
+      expect(emptyWrapper.isNotEmpty, isFalse);
+    });
+
+    test("iterator", () {
+      var iterator = wrapper.iterator;
+      expect(iterator.current, isNull);
+      expect(iterator.moveNext(), isTrue);
+      expect(iterator.current, equals(1));
+      expect(iterator.moveNext(), isTrue);
+      expect(iterator.current, equals(2));
+      expect(iterator.moveNext(), isTrue);
+      expect(iterator.current, equals(3));
+      expect(iterator.moveNext(), isTrue);
+      expect(iterator.current, equals(4));
+      expect(iterator.moveNext(), isTrue);
+      expect(iterator.current, equals(5));
+      expect(iterator.moveNext(), isFalse);
+      expect(iterator.current, isNull);
+    });
+
+    test("join()", () {
+      expect(wrapper.join(), "12345");
+      expect(wrapper.join("-"), "1-2-3-4-5");
+    });
+
+    test("last", () {
+      expect(wrapper.last, equals(5));
+      expect(() => emptyWrapper.last, throwsStateError);
+    });
+
+    test("lastWhere()", () {
+      expect(wrapper.lastWhere((i) => i > 3), equals(5));
+      expect(() => wrapper.lastWhere((i) => i > 5), throwsStateError);
+      expect(wrapper.lastWhere((i) => i > 5, orElse: () => -1), equals(-1));
+    });
+
+    test("length", () {
+      expect(wrapper.length, equals(5));
+      expect(emptyWrapper.length, equals(0));
+    });
+
+    test("map()", () {
+      expect(wrapper.map((i) => i + 1), equals([2, 3, 4, 5, 6]));
+      expect(wrapper.map((i) => i / 2), equals([0.5, 1.0, 1.5, 2.0, 2.5]));
+    });
+
+    test("reduce()", () {
+      expect(wrapper.reduce((value, i) => value + i), equals(15));
+      expect(
+          () => emptyWrapper.reduce((value, i) => value + i), throwsStateError);
+    });
+
+    test("single", () {
+      expect(() => wrapper.single, throwsStateError);
+      expect(singleWrapper.single, equals(1));
+    });
+
+    test("singleWhere()", () {
+      expect(() => wrapper.singleWhere((i) => i.isOdd), throwsStateError);
+      expect(singleWrapper.singleWhere((i) => i.isOdd), equals(1));
+      expect(
+          () => singleWrapper.singleWhere((i) => i.isEven), throwsStateError);
+    });
+
+    test("skip()", () {
+      expect(wrapper.skip(3), equals([4, 5]));
+      expect(wrapper.skip(10), isEmpty);
+      expect(() => wrapper.skip(-1), throwsRangeError);
+    });
+
+    test("skipWhile()", () {
+      expect(wrapper.skipWhile((i) => i < 3), equals([3, 4, 5]));
+      expect(wrapper.skipWhile((i) => i < 10), isEmpty);
+    });
+
+    test("take()", () {
+      expect(wrapper.take(3), equals([1, 2, 3]));
+      expect(wrapper.take(10), equals([1, 2, 3, 4, 5]));
+      expect(() => wrapper.take(-1), throwsRangeError);
+    });
+
+    test("takeWhile()", () {
+      expect(wrapper.takeWhile((i) => i < 3), equals([1, 2]));
+      expect(wrapper.takeWhile((i) => i < 10), equals([1, 2, 3, 4, 5]));
+    });
+
+    test("toList()", () {
+      expect(wrapper.toList(), equals([1, 2, 3, 4, 5]));
+      expect(wrapper.toList(growable: false), equals([1, 2, 3, 4, 5]));
+      expect(
+          () => wrapper.toList(growable: false).add(6), throwsUnsupportedError);
+    });
+
+    test("toSet()", () {
+      expect(wrapper.toSet(), unorderedEquals([1, 2, 3, 4, 5]));
+      expect(DelegatingIterable.typed<int>(<Object>[1, 1, 2, 2]).toSet(),
+          unorderedEquals([1, 2]));
+    });
+
+    test("where()", () {
+      expect(wrapper.where((i) => i.isOdd), equals([1, 3, 5]));
+      expect(wrapper.where((i) => i.isEven), equals([2, 4]));
+    });
+
+    test("toString()", () {
+      expect(wrapper.toString(), equals("(1, 2, 3, 4, 5)"));
+      expect(emptyWrapper.toString(), equals("()"));
+    });
+  });
+
+  group("with invalid types", () {
+    var wrapper;
+    var singleWrapper;
+    setUp(() {
+      wrapper = DelegatingIterable
+          .typed<int>(<Object>["foo", "bar", "baz"].map((element) => element));
+      singleWrapper = DelegatingIterable
+          .typed<int>(<Object>["foo"].map((element) => element));
+    });
+
+    group("throws a CastError for", () {
+      test("any()", () {
+        expect(() => wrapper.any(expectAsync1((_) => false, count: 0)),
+            throwsCastError);
+      });
+
+      test("elementAt()", () {
+        expect(() => wrapper.elementAt(1), throwsCastError);
+      });
+
+      test("every()", () {
+        expect(() => wrapper.every(expectAsync1((_) => false, count: 0)),
+            throwsCastError);
+      });
+
+      test("expand()", () {
+        var lazy = wrapper.expand(expectAsync1((_) => [], count: 0));
+        expect(() => lazy.first, throwsCastError);
+      });
+
+      test("first", () {
+        expect(() => wrapper.first, throwsCastError);
+      });
+
+      test("firstWhere()", () {
+        expect(() => wrapper.firstWhere(expectAsync1((_) => false, count: 0)),
+            throwsCastError);
+      });
+
+      test("fold()", () {
+        expect(
+            () => wrapper.fold(null, expectAsync2((_, __) => null, count: 0)),
+            throwsCastError);
+      });
+
+      test("forEach()", () {
+        expect(() => wrapper.forEach(expectAsync1((_) {}, count: 0)),
+            throwsCastError);
+      });
+
+      test("iterator", () {
+        var iterator = wrapper.iterator;
+        expect(iterator.current, isNull);
+        expect(() => iterator.moveNext(), throwsCastError);
+      });
+
+      test("last", () {
+        expect(() => wrapper.last, throwsCastError);
+      });
+
+      test("lastWhere()", () {
+        expect(() => wrapper.lastWhere(expectAsync1((_) => false, count: 0)),
+            throwsCastError);
+      });
+
+      test("map()", () {
+        var lazy = wrapper.map(expectAsync1((_) => null, count: 0));
+        expect(() => lazy.first, throwsCastError);
+      });
+
+      test("reduce()", () {
+        expect(() => wrapper.reduce(expectAsync2((_, __) => null, count: 0)),
+            throwsCastError);
+      });
+
+      test("single", () {
+        expect(() => singleWrapper.single, throwsCastError);
+      });
+
+      test("singleWhere()", () {
+        expect(() {
+          singleWrapper.singleWhere(expectAsync1((_) => false, count: 0));
+        }, throwsCastError);
+      });
+
+      test("skip()", () {
+        var lazy = wrapper.skip(1);
+        expect(() => lazy.first, throwsCastError);
+      });
+
+      test("skipWhile()", () {
+        var lazy = wrapper.skipWhile(expectAsync1((_) => false, count: 0));
+        expect(() => lazy.first, throwsCastError);
+      });
+
+      test("take()", () {
+        var lazy = wrapper.take(1);
+        expect(() => lazy.first, throwsCastError);
+      });
+
+      test("takeWhile()", () {
+        var lazy = wrapper.takeWhile(expectAsync1((_) => false, count: 0));
+        expect(() => lazy.first, throwsCastError);
+      });
+
+      test("toList()", () {
+        var list = wrapper.toList();
+        expect(() => list.first, throwsCastError);
+      });
+
+      test("toSet()", () {
+        var asSet = wrapper.toSet();
+        expect(() => asSet.first, throwsCastError);
+      });
+
+      test("where()", () {
+        var lazy = wrapper.where(expectAsync1((_) => false, count: 0));
+        expect(() => lazy.first, throwsCastError);
+      });
+    });
+
+    group("doesn't throw a CastError for", () {
+      test("contains()", () {
+        expect(wrapper.contains(1), isFalse);
+        expect(wrapper.contains("foo"), isTrue);
+      });
+
+      test("elementAt()", () {
+        expect(() => wrapper.elementAt(-1), throwsRangeError);
+        expect(() => wrapper.elementAt(10), throwsRangeError);
+      });
+
+      test("isEmpty", () {
+        expect(wrapper.isEmpty, isFalse);
+      });
+
+      test("isNotEmpty", () {
+        expect(wrapper.isNotEmpty, isTrue);
+      });
+
+      test("join()", () {
+        expect(wrapper.join(), "foobarbaz");
+      });
+
+      test("length", () {
+        expect(wrapper.length, equals(3));
+      });
+
+      test("single", () {
+        expect(() => wrapper.single, throwsStateError);
+      });
+
+      test("skip()", () {
+        expect(() => wrapper.skip(-1), throwsRangeError);
+      });
+
+      test("toString()", () {
+        expect(wrapper.toString(), equals("(foo, bar, baz)"));
+      });
+    });
+  }, skip: "Re-enable this when test can run DDC (test#414).");
+}
diff --git a/packages/collection/test/typed_wrapper/list_test.dart b/packages/collection/test/typed_wrapper/list_test.dart
new file mode 100644
index 0000000..7876dbe
--- /dev/null
+++ b/packages/collection/test/typed_wrapper/list_test.dart
@@ -0,0 +1,421 @@
+// Copyright (c) 2016, 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:math' as math;
+
+import "package:collection/collection.dart";
+import "package:test/test.dart";
+
+import '../utils.dart';
+
+void main() {
+  group("with valid types, forwards", () {
+    var wrapper;
+    var emptyWrapper;
+    setUp(() {
+      wrapper = DelegatingList.typed<int>(<Object>[1, 2, 3, 4, 5]);
+      emptyWrapper = DelegatingList.typed<int>(<Object>[]);
+    });
+
+    test("[]", () {
+      expect(wrapper[0], equals(1));
+      expect(wrapper[1], equals(2));
+      expect(() => wrapper[-1], throwsRangeError);
+      expect(() => wrapper[10], throwsRangeError);
+    });
+
+    test("[]=", () {
+      wrapper[1] = 10;
+      wrapper[3] = 15;
+      expect(wrapper, equals([1, 10, 3, 15, 5]));
+      expect(() => wrapper[-1] = 10, throwsRangeError);
+      expect(() => wrapper[10] = 10, throwsRangeError);
+    });
+
+    test("add()", () {
+      wrapper.add(6);
+      wrapper.add(7);
+      expect(wrapper, equals([1, 2, 3, 4, 5, 6, 7]));
+    });
+
+    test("addAll()", () {
+      wrapper.addAll([6, 7, 8]);
+      expect(wrapper, equals([1, 2, 3, 4, 5, 6, 7, 8]));
+    });
+
+    test("asMap()", () {
+      expect(wrapper.asMap(), equals({0: 1, 1: 2, 2: 3, 3: 4, 4: 5}));
+    });
+
+    test("clear()", () {
+      wrapper.clear();
+      expect(wrapper, isEmpty);
+    });
+
+    test("fillRange()", () {
+      wrapper.fillRange(2, 4);
+      expect(wrapper, equals([1, 2, null, null, 5]));
+
+      wrapper.fillRange(1, 2, 7);
+      expect(wrapper, equals([1, 7, null, null, 5]));
+
+      expect(() => wrapper.fillRange(-1, 2), throwsRangeError);
+      expect(() => wrapper.fillRange(1, 10), throwsRangeError);
+      expect(() => wrapper.fillRange(4, 2), throwsRangeError);
+      expect(() => wrapper.fillRange(10, 12), throwsRangeError);
+    });
+
+    test("getRange()", () {
+      expect(wrapper.getRange(2, 4), equals([3, 4]));
+      expect(wrapper.getRange(1, 2), equals([2]));
+
+      expect(() => wrapper.getRange(-1, 2), throwsRangeError);
+      expect(() => wrapper.getRange(1, 10), throwsRangeError);
+      expect(() => wrapper.getRange(4, 2), throwsRangeError);
+      expect(() => wrapper.getRange(10, 12), throwsRangeError);
+    });
+
+    test("indexOf()", () {
+      expect(wrapper.indexOf(4), equals(3));
+      expect(wrapper.indexOf(10), equals(-1));
+      expect(wrapper.indexOf(4, 2), equals(3));
+      expect(wrapper.indexOf(4, 4), equals(-1));
+    });
+
+    test("insert()", () {
+      wrapper.insert(3, 10);
+      expect(wrapper, equals([1, 2, 3, 10, 4, 5]));
+
+      wrapper.insert(0, 15);
+      expect(wrapper, equals([15, 1, 2, 3, 10, 4, 5]));
+
+      expect(() => wrapper.insert(-1, 0), throwsRangeError);
+      expect(() => wrapper.insert(10, 0), throwsRangeError);
+    });
+
+    test("insertAll()", () {
+      wrapper.insertAll(3, [10, 11, 12]);
+      expect(wrapper, equals([1, 2, 3, 10, 11, 12, 4, 5]));
+
+      wrapper.insertAll(0, [15, 16, 17]);
+      expect(wrapper, equals([15, 16, 17, 1, 2, 3, 10, 11, 12, 4, 5]));
+
+      expect(() => wrapper.insertAll(-1, []), throwsRangeError);
+      expect(() => wrapper.insertAll(100, []), throwsRangeError);
+    });
+
+    test("lastIndexOf()", () {
+      expect(wrapper.lastIndexOf(4), equals(3));
+      expect(wrapper.lastIndexOf(10), equals(-1));
+      expect(wrapper.lastIndexOf(4, 4), equals(3));
+      expect(wrapper.lastIndexOf(4, 2), equals(-1));
+    });
+
+    test("length=", () {
+      wrapper.length = 10;
+      expect(wrapper, equals([1, 2, 3, 4, 5, null, null, null, null, null]));
+
+      wrapper.length = 3;
+      expect(wrapper, equals([1, 2, 3]));
+    });
+
+    test("remove()", () {
+      expect(wrapper.remove(3), isTrue);
+      expect(wrapper, equals([1, 2, 4, 5]));
+
+      expect(wrapper.remove(3), isFalse);
+      expect(wrapper.remove("foo"), isFalse);
+    });
+
+    test("removeAt()", () {
+      expect(wrapper.removeAt(3), equals(4));
+      expect(wrapper, equals([1, 2, 3, 5]));
+
+      expect(() => wrapper.removeAt(-1), throwsRangeError);
+      expect(() => wrapper.removeAt(10), throwsRangeError);
+    });
+
+    test("removeLast()", () {
+      expect(wrapper.removeLast(), equals(5));
+      expect(wrapper, equals([1, 2, 3, 4]));
+
+      // See sdk#26087. We want this to pass with the current implementation and
+      // with the fix.
+      expect(() => emptyWrapper.removeLast(),
+          anyOf(throwsStateError, throwsRangeError));
+    });
+
+    test("removeRange()", () {
+      wrapper.removeRange(2, 4);
+      expect(wrapper, equals([1, 2, 5]));
+
+      expect(() => wrapper.removeRange(-1, 2), throwsRangeError);
+      expect(() => wrapper.removeRange(1, 10), throwsRangeError);
+      expect(() => wrapper.removeRange(4, 2), throwsRangeError);
+      expect(() => wrapper.removeRange(10, 12), throwsRangeError);
+    });
+
+    test("removeWhere()", () {
+      wrapper.removeWhere((i) => i.isOdd);
+      expect(wrapper, equals([2, 4]));
+    });
+
+    test("replaceRange()", () {
+      wrapper.replaceRange(2, 4, [10, 11, 12]);
+      expect(wrapper, equals([1, 2, 10, 11, 12, 5]));
+
+      expect(() => wrapper.replaceRange(-1, 2, []), throwsRangeError);
+      expect(() => wrapper.replaceRange(1, 10, []), throwsRangeError);
+      expect(() => wrapper.replaceRange(4, 2, []), throwsRangeError);
+      expect(() => wrapper.replaceRange(10, 12, []), throwsRangeError);
+    });
+
+    test("retainWhere()", () {
+      wrapper.retainWhere((i) => i.isOdd);
+      expect(wrapper, equals([1, 3, 5]));
+    });
+
+    test("reversed", () {
+      expect(wrapper.reversed, equals([5, 4, 3, 2, 1]));
+    });
+
+    test("setAll()", () {
+      wrapper.setAll(2, [10, 11]);
+      expect(wrapper, equals([1, 2, 10, 11, 5]));
+
+      expect(() => wrapper.setAll(-1, []), throwsRangeError);
+      expect(() => wrapper.setAll(10, []), throwsRangeError);
+    });
+
+    test("setRange()", () {
+      wrapper.setRange(2, 4, [10, 11, 12]);
+      expect(wrapper, equals([1, 2, 10, 11, 5]));
+
+      wrapper.setRange(2, 4, [10, 11, 12], 1);
+      expect(wrapper, equals([1, 2, 11, 12, 5]));
+
+      expect(() => wrapper.setRange(-1, 2, []), throwsRangeError);
+      expect(() => wrapper.setRange(1, 10, []), throwsRangeError);
+      expect(() => wrapper.setRange(4, 2, []), throwsRangeError);
+      expect(() => wrapper.setRange(10, 12, []), throwsRangeError);
+      expect(() => wrapper.setRange(2, 4, []), throwsStateError);
+    });
+
+    test("shuffle()", () {
+      wrapper.shuffle(new math.Random(1234));
+      expect(wrapper, equals([1, 2, 3, 4, 5]..shuffle(new math.Random(1234))));
+    });
+
+    test("sort()", () {
+      wrapper.sort((a, b) => b.compareTo(a));
+      expect(wrapper, equals([5, 4, 3, 2, 1]));
+
+      wrapper.sort();
+      expect(wrapper, equals([1, 2, 3, 4, 5]));
+    });
+
+    test("sublist()", () {
+      expect(wrapper.sublist(2), equals([3, 4, 5]));
+      expect(wrapper.sublist(2, 4), equals([3, 4]));
+    });
+  });
+
+  group("with invalid types", () {
+    List inner;
+    var wrapper;
+    setUp(() {
+      inner = <Object>["foo", "bar", "baz"];
+      wrapper = DelegatingList.typed<int>(inner);
+    });
+
+    group("throws a CastError for", () {
+      test("[]", () {
+        expect(() => wrapper[0], throwsCastError);
+      });
+
+      test("asMap()", () {
+        var map = wrapper.asMap();
+        expect(() => map[1], throwsCastError);
+      });
+
+      test("getRange()", () {
+        var lazy = wrapper.getRange(1, 2);
+        expect(() => lazy.first, throwsCastError);
+      });
+
+      test("removeAt()", () {
+        expect(() => wrapper.removeAt(2), throwsCastError);
+      });
+
+      test("removeLast()", () {
+        expect(() => wrapper.removeLast(), throwsCastError);
+      });
+
+      test("removeWhere()", () {
+        expect(() => wrapper.removeWhere(expectAsync1((_) => false, count: 0)),
+            throwsCastError);
+      });
+
+      test("retainWhere()", () {
+        expect(() => wrapper.retainWhere(expectAsync1((_) => false, count: 0)),
+            throwsCastError);
+      });
+
+      test("reversed", () {
+        var lazy = wrapper.reversed;
+        expect(() => lazy.first, throwsCastError);
+      });
+
+      test("sort()", () {
+        expect(() => wrapper.sort(expectAsync2((_, __) => 0, count: 0)),
+            throwsCastError);
+      });
+
+      test("sublist()", () {
+        var list = wrapper.sublist(1);
+        expect(() => list[0], throwsCastError);
+      });
+    });
+
+    group("doesn't throw a CastError for", () {
+      test("[]", () {
+        expect(() => wrapper[-1], throwsRangeError);
+        expect(() => wrapper[10], throwsRangeError);
+      });
+
+      test("[]=", () {
+        wrapper[1] = 10;
+        expect(inner, equals(["foo", 10, "baz"]));
+        expect(() => wrapper[-1] = 10, throwsRangeError);
+        expect(() => wrapper[10] = 10, throwsRangeError);
+      });
+
+      test("add()", () {
+        wrapper.add(6);
+        wrapper.add(7);
+        expect(inner, equals(["foo", "bar", "baz", 6, 7]));
+      });
+
+      test("addAll()", () {
+        wrapper.addAll([6, 7, 8]);
+        expect(inner, equals(["foo", "bar", "baz", 6, 7, 8]));
+      });
+
+      test("clear()", () {
+        wrapper.clear();
+        expect(wrapper, isEmpty);
+      });
+
+      test("fillRange()", () {
+        wrapper.fillRange(1, 3, 7);
+        expect(inner, equals(["foo", 7, 7]));
+
+        expect(() => wrapper.fillRange(-1, 2), throwsRangeError);
+        expect(() => wrapper.fillRange(1, 10), throwsRangeError);
+        expect(() => wrapper.fillRange(4, 2), throwsRangeError);
+        expect(() => wrapper.fillRange(10, 12), throwsRangeError);
+      });
+
+      test("getRange()", () {
+        expect(() => wrapper.getRange(-1, 2), throwsRangeError);
+        expect(() => wrapper.getRange(1, 10), throwsRangeError);
+        expect(() => wrapper.getRange(4, 2), throwsRangeError);
+        expect(() => wrapper.getRange(10, 12), throwsRangeError);
+      });
+
+      test("indexOf()", () {
+        expect(wrapper.indexOf(4), equals(-1));
+      });
+
+      test("insert()", () {
+        wrapper.insert(0, 15);
+        expect(inner, equals([15, "foo", "bar", "baz"]));
+
+        expect(() => wrapper.insert(-1, 0), throwsRangeError);
+        expect(() => wrapper.insert(10, 0), throwsRangeError);
+      });
+
+      test("insertAll()", () {
+        wrapper.insertAll(0, [15, 16, 17]);
+        expect(inner, equals([15, 16, 17, "foo", "bar", "baz"]));
+
+        expect(() => wrapper.insertAll(-1, []), throwsRangeError);
+        expect(() => wrapper.insertAll(100, []), throwsRangeError);
+      });
+
+      test("lastIndexOf()", () {
+        expect(wrapper.lastIndexOf(4), equals(-1));
+      });
+
+      test("length=", () {
+        wrapper.length = 5;
+        expect(inner, equals(["foo", "bar", "baz", null, null]));
+
+        wrapper.length = 1;
+        expect(inner, equals(["foo"]));
+      });
+
+      test("remove()", () {
+        expect(wrapper.remove(3), isFalse);
+        expect(wrapper.remove("foo"), isTrue);
+        expect(inner, equals(["bar", "baz"]));
+      });
+
+      test("removeAt()", () {
+        expect(() => wrapper.removeAt(-1), throwsRangeError);
+        expect(() => wrapper.removeAt(10), throwsRangeError);
+      });
+
+      test("removeRange()", () {
+        wrapper.removeRange(1, 3);
+        expect(inner, equals(["foo"]));
+
+        expect(() => wrapper.removeRange(-1, 2), throwsRangeError);
+        expect(() => wrapper.removeRange(1, 10), throwsRangeError);
+        expect(() => wrapper.removeRange(4, 2), throwsRangeError);
+        expect(() => wrapper.removeRange(10, 12), throwsRangeError);
+      });
+
+      test("replaceRange()", () {
+        wrapper.replaceRange(1, 2, [10, 11, 12]);
+        expect(inner, equals(["foo", 10, 11, 12, "baz"]));
+
+        expect(() => wrapper.replaceRange(-1, 2, []), throwsRangeError);
+        expect(() => wrapper.replaceRange(1, 10, []), throwsRangeError);
+        expect(() => wrapper.replaceRange(4, 2, []), throwsRangeError);
+        expect(() => wrapper.replaceRange(10, 12, []), throwsRangeError);
+      });
+
+      test("setAll()", () {
+        wrapper.setAll(1, [10, 11]);
+        expect(inner, equals(["foo", 10, 11]));
+
+        expect(() => wrapper.setAll(-1, []), throwsRangeError);
+        expect(() => wrapper.setAll(10, []), throwsRangeError);
+      });
+
+      test("setRange()", () {
+        wrapper.setRange(1, 2, [10, 11, 12]);
+        expect(inner, equals(["foo", 10, "baz"]));
+
+        expect(() => wrapper.setRange(-1, 2, []), throwsRangeError);
+        expect(() => wrapper.setRange(1, 10, []), throwsRangeError);
+        expect(() => wrapper.setRange(4, 2, []), throwsRangeError);
+        expect(() => wrapper.setRange(10, 12, []), throwsRangeError);
+        expect(() => wrapper.setRange(1, 2, []), throwsStateError);
+      });
+
+      test("shuffle()", () {
+        wrapper.shuffle(new math.Random(1234));
+        expect(inner,
+            equals(["foo", "bar", "baz"]..shuffle(new math.Random(1234))));
+      });
+
+      test("sort()", () {
+        wrapper.sort();
+        expect(inner, equals(["bar", "baz", "foo"]));
+      });
+    });
+  }, skip: "Re-enable this when test can run DDC (test#414).");
+}
diff --git a/packages/collection/test/typed_wrapper/map_test.dart b/packages/collection/test/typed_wrapper/map_test.dart
new file mode 100644
index 0000000..75078bd
--- /dev/null
+++ b/packages/collection/test/typed_wrapper/map_test.dart
@@ -0,0 +1,327 @@
+// Copyright (c) 2016, 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:collection/collection.dart";
+import "package:test/test.dart";
+
+import '../utils.dart';
+
+void main() {
+  group("with valid types, forwards", () {
+    var wrapper;
+    var emptyWrapper;
+    setUp(() {
+      wrapper = DelegatingMap.typed<String, int>(
+          <Object, Object>{"foo": 1, "bar": 2, "baz": 3, "bang": 4});
+      emptyWrapper = DelegatingMap.typed<String, int>(<Object, Object>{});
+    });
+
+    test("[]", () {
+      expect(wrapper["foo"], equals(1));
+      expect(wrapper["bar"], equals(2));
+      expect(wrapper["qux"], isNull);
+      expect(wrapper[1], isNull);
+    });
+
+    test("[]=", () {
+      wrapper["foo"] = 5;
+      expect(wrapper, equals({"foo": 5, "bar": 2, "baz": 3, "bang": 4}));
+
+      wrapper["qux"] = 6;
+      expect(
+          wrapper, equals({"foo": 5, "bar": 2, "baz": 3, "bang": 4, "qux": 6}));
+    });
+
+    test("addAll()", () {
+      wrapper.addAll({"bar": 5, "qux": 6});
+      expect(
+          wrapper, equals({"foo": 1, "bar": 5, "baz": 3, "bang": 4, "qux": 6}));
+    });
+
+    test("clear()", () {
+      wrapper.clear();
+      expect(wrapper, isEmpty);
+    });
+
+    test("containsKey()", () {
+      expect(wrapper.containsKey("foo"), isTrue);
+      expect(wrapper.containsKey("qux"), isFalse);
+      expect(wrapper.containsKey(1), isFalse);
+    });
+
+    test("containsValue()", () {
+      expect(wrapper.containsValue(1), isTrue);
+      expect(wrapper.containsValue(7), isFalse);
+      expect(wrapper.containsValue("foo"), isFalse);
+    });
+
+    test("forEach()", () {
+      var results = [];
+      wrapper.forEach((key, value) => results.add([key, value]));
+      expect(
+          results,
+          unorderedEquals([
+            ["foo", 1],
+            ["bar", 2],
+            ["baz", 3],
+            ["bang", 4]
+          ]));
+
+      emptyWrapper.forEach(expectAsync2((_, __) {}, count: 0));
+    });
+
+    test("isEmpty", () {
+      expect(wrapper.isEmpty, isFalse);
+      expect(emptyWrapper.isEmpty, isTrue);
+    });
+
+    test("isNotEmpty", () {
+      expect(wrapper.isNotEmpty, isTrue);
+      expect(emptyWrapper.isNotEmpty, isFalse);
+    });
+
+    test("keys", () {
+      expect(wrapper.keys, unorderedEquals(["foo", "bar", "baz", "bang"]));
+      expect(emptyWrapper.keys, isEmpty);
+    });
+
+    test("length", () {
+      expect(wrapper.length, equals(4));
+      expect(emptyWrapper.length, equals(0));
+    });
+
+    test("putIfAbsent()", () {
+      expect(wrapper.putIfAbsent("foo", expectAsync1((_) => null, count: 0)),
+          equals(1));
+
+      expect(wrapper.putIfAbsent("qux", () => 6), equals(6));
+      expect(
+          wrapper, equals({"foo": 1, "bar": 2, "baz": 3, "bang": 4, "qux": 6}));
+    });
+
+    test("remove()", () {
+      expect(wrapper.remove("foo"), equals(1));
+      expect(wrapper, equals({"bar": 2, "baz": 3, "bang": 4}));
+
+      expect(wrapper.remove("foo"), isNull);
+      expect(wrapper.remove(3), isNull);
+    });
+
+    test("values", () {
+      expect(wrapper.values, unorderedEquals([1, 2, 3, 4]));
+      expect(emptyWrapper.values, isEmpty);
+    });
+
+    test("toString()", () {
+      expect(
+          wrapper.toString(),
+          allOf([
+            startsWith("{"),
+            contains("foo: 1"),
+            contains("bar: 2"),
+            contains("baz: 3"),
+            contains("bang: 4"),
+            endsWith("}")
+          ]));
+    });
+  });
+
+  group("with invalid key types", () {
+    Map inner;
+    var wrapper;
+    setUp(() {
+      inner = <Object, Object>{1: 1, 2: 2, 3: 3, 4: 4};
+      wrapper = DelegatingMap.typed<String, int>(inner);
+    });
+
+    group("throws a CastError for", () {
+      test("forEach()", () {
+        expect(() => wrapper.forEach(expectAsync2((_, __) {}, count: 0)),
+            throwsCastError);
+      });
+
+      test("keys", () {
+        var lazy = wrapper.keys;
+        expect(() => lazy.first, throwsCastError);
+      });
+    });
+
+    group("doesn't throw a CastError for", () {
+      test("[]", () {
+        expect(wrapper["foo"], isNull);
+        expect(wrapper[1], equals(1));
+        expect(wrapper[7], isNull);
+      });
+
+      test("[]=", () {
+        wrapper["foo"] = 5;
+        expect(inner, equals({"foo": 5, 1: 1, 2: 2, 3: 3, 4: 4}));
+      });
+
+      test("addAll()", () {
+        wrapper.addAll({"foo": 1, "bar": 2});
+        expect(inner, equals({"foo": 1, "bar": 2, 1: 1, 2: 2, 3: 3, 4: 4}));
+      });
+
+      test("clear()", () {
+        wrapper.clear();
+        expect(wrapper, isEmpty);
+      });
+
+      test("containsKey()", () {
+        expect(wrapper.containsKey(1), isTrue);
+        expect(wrapper.containsKey(7), isFalse);
+        expect(wrapper.containsKey("foo"), isFalse);
+      });
+
+      test("containsValue()", () {
+        expect(wrapper.containsValue(1), isTrue);
+        expect(wrapper.containsValue(7), isFalse);
+        expect(wrapper.containsValue("foo"), isFalse);
+      });
+
+      test("isEmpty", () {
+        expect(wrapper.isEmpty, isFalse);
+      });
+
+      test("isNotEmpty", () {
+        expect(wrapper.isNotEmpty, isTrue);
+      });
+
+      test("length", () {
+        expect(wrapper.length, equals(4));
+      });
+
+      test("putIfAbsent()", () {
+        expect(wrapper.putIfAbsent("foo", () => 1), equals(1));
+        expect(inner, equals({"foo": 1, 1: 1, 2: 2, 3: 3, 4: 4}));
+      });
+
+      test("remove()", () {
+        expect(wrapper.remove(1), equals(1));
+        expect(inner, equals({2: 2, 3: 3, 4: 4}));
+
+        expect(wrapper.remove("foo"), isNull);
+        expect(wrapper.remove(7), isNull);
+      });
+
+      test("values", () {
+        expect(wrapper.values, unorderedEquals([1, 2, 3, 4]));
+      });
+
+      test("toString()", () {
+        expect(
+            wrapper.toString(),
+            allOf([
+              startsWith("{"),
+              contains("1: 1"),
+              contains("2: 2"),
+              contains("3: 3"),
+              contains("4: 4"),
+              endsWith("}")
+            ]));
+      });
+    });
+  }, skip: "Re-enable this when test can run DDC (test#414).");
+
+  group("with invalid value types", () {
+    Map inner;
+    var wrapper;
+    setUp(() {
+      inner = <Object, Object>{"foo": "bar", "baz": "bang"};
+      wrapper = DelegatingMap.typed<String, int>(inner);
+    });
+
+    group("throws a CastError for", () {
+      test("forEach()", () {
+        expect(() => wrapper.forEach(expectAsync2((_, __) {}, count: 0)),
+            throwsCastError);
+      });
+
+      test("[]", () {
+        expect(() => wrapper["foo"], throwsCastError);
+        expect(wrapper["qux"], isNull);
+      });
+
+      test("putIfAbsent()", () {
+        expect(() => wrapper.putIfAbsent("foo", () => 1), throwsCastError);
+      });
+
+      test("remove()", () {
+        expect(() => wrapper.remove("foo"), throwsCastError);
+      });
+
+      test("values", () {
+        var lazy = wrapper.values;
+        expect(() => lazy.first, throwsCastError);
+      });
+    });
+
+    group("doesn't throw a CastError for", () {
+      test("[]=", () {
+        wrapper["foo"] = 5;
+        expect(inner, equals({"foo": 5, "baz": "bang"}));
+      });
+
+      test("addAll()", () {
+        wrapper.addAll({"foo": 1, "qux": 2});
+        expect(inner, equals({"foo": 1, "baz": "bang", "qux": 2}));
+      });
+
+      test("clear()", () {
+        wrapper.clear();
+        expect(wrapper, isEmpty);
+      });
+
+      test("containsKey()", () {
+        expect(wrapper.containsKey("foo"), isTrue);
+        expect(wrapper.containsKey(1), isFalse);
+        expect(wrapper.containsKey("qux"), isFalse);
+      });
+
+      test("containsValue()", () {
+        expect(wrapper.containsValue("bar"), isTrue);
+        expect(wrapper.containsValue(1), isFalse);
+        expect(wrapper.containsValue("foo"), isFalse);
+      });
+
+      test("isEmpty", () {
+        expect(wrapper.isEmpty, isFalse);
+      });
+
+      test("isNotEmpty", () {
+        expect(wrapper.isNotEmpty, isTrue);
+      });
+
+      test("keys", () {
+        expect(wrapper.keys, unorderedEquals(["foo", "baz"]));
+      });
+
+      test("length", () {
+        expect(wrapper.length, equals(2));
+      });
+
+      test("putIfAbsent()", () {
+        expect(wrapper.putIfAbsent("qux", () => 1), equals(1));
+        expect(inner, equals({"foo": "bar", "baz": "bang", "qux": 1}));
+      });
+
+      test("remove()", () {
+        expect(wrapper.remove("qux"), isNull);
+        expect(wrapper.remove(7), isNull);
+      });
+
+      test("toString()", () {
+        expect(
+            wrapper.toString(),
+            allOf([
+              startsWith("{"),
+              contains("foo: bar"),
+              contains("baz: bang"),
+              endsWith("}")
+            ]));
+      });
+    });
+  }, skip: "Re-enable this when test can run DDC (test#414).");
+}
diff --git a/packages/collection/test/typed_wrapper/queue_test.dart b/packages/collection/test/typed_wrapper/queue_test.dart
new file mode 100644
index 0000000..e9ffb3a
--- /dev/null
+++ b/packages/collection/test/typed_wrapper/queue_test.dart
@@ -0,0 +1,141 @@
+// Copyright (c) 2016, 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:collection";
+
+import "package:collection/collection.dart";
+import "package:test/test.dart";
+
+import '../utils.dart';
+
+void main() {
+  group("with valid types, forwards", () {
+    var wrapper;
+    var emptyWrapper;
+    setUp(() {
+      wrapper =
+          DelegatingQueue.typed<int>(new Queue<Object>.from([1, 2, 3, 4, 5]));
+      emptyWrapper = DelegatingQueue.typed<int>(new Queue<Object>());
+    });
+
+    test("add()", () {
+      wrapper.add(6);
+      wrapper.add(7);
+      expect(wrapper, equals([1, 2, 3, 4, 5, 6, 7]));
+    });
+
+    test("addAll()", () {
+      wrapper.addAll([6, 7, 8]);
+      expect(wrapper, equals([1, 2, 3, 4, 5, 6, 7, 8]));
+    });
+
+    test("addFirst()", () {
+      wrapper.addFirst(6);
+      wrapper.addFirst(7);
+      expect(wrapper, equals([7, 6, 1, 2, 3, 4, 5]));
+    });
+
+    test("addLast()", () {
+      wrapper.addLast(6);
+      wrapper.addLast(7);
+      expect(wrapper, equals([1, 2, 3, 4, 5, 6, 7]));
+    });
+
+    test("clear()", () {
+      wrapper.clear();
+      expect(wrapper, isEmpty);
+    });
+
+    test("remove()", () {
+      expect(wrapper.remove(3), isTrue);
+      expect(wrapper, equals([1, 2, 4, 5]));
+
+      expect(wrapper.remove(3), isFalse);
+      expect(wrapper.remove("foo"), isFalse);
+    });
+
+    test("removeWhere()", () {
+      wrapper.removeWhere((i) => i.isOdd);
+      expect(wrapper, equals([2, 4]));
+    });
+
+    test("retainWhere()", () {
+      wrapper.retainWhere((i) => i.isOdd);
+      expect(wrapper, equals([1, 3, 5]));
+    });
+
+    test("removeLast()", () {
+      expect(wrapper.removeLast(), equals(5));
+      expect(wrapper, equals([1, 2, 3, 4]));
+
+      expect(() => emptyWrapper.removeLast(), throwsStateError);
+    });
+
+    test("removeFirst()", () {
+      expect(wrapper.removeFirst(), equals(1));
+      expect(wrapper, equals([2, 3, 4, 5]));
+
+      expect(() => emptyWrapper.removeFirst(), throwsStateError);
+    });
+  });
+
+  group("with invalid types", () {
+    Queue inner;
+    var wrapper;
+    setUp(() {
+      inner = new Queue<Object>.from(["foo", "bar", "baz"]);
+      wrapper = DelegatingQueue.typed<int>(inner);
+    });
+
+    group("throws a CastError for", () {
+      test("removeLast()", () {
+        expect(() => wrapper.removeLast(), throwsCastError);
+      });
+
+      test("removeFirst()", () {
+        expect(() => wrapper.removeFirst(), throwsCastError);
+      });
+
+      test("removeWhere()", () {
+        expect(() => wrapper.removeWhere(expectAsync1((_) => false, count: 0)),
+            throwsCastError);
+      });
+
+      test("retainWhere()", () {
+        expect(() => wrapper.retainWhere(expectAsync1((_) => false, count: 0)),
+            throwsCastError);
+      });
+    });
+
+    group("doesn't throw a CastError for", () {
+      test("add()", () {
+        wrapper.add(6);
+        wrapper.add(7);
+        expect(inner, equals(["foo", "bar", "baz", 6, 7]));
+      });
+
+      test("addAll()", () {
+        wrapper.addAll([6, 7, 8]);
+        expect(inner, equals(["foo", "bar", "baz", 6, 7, 8]));
+      });
+
+      test("addFirst()", () {
+        wrapper.addFirst(6);
+        wrapper.addFirst(7);
+        expect(inner, equals([7, 6, "foo", "bar", "baz"]));
+      });
+
+      test("addLast()", () {
+        wrapper.addLast(6);
+        wrapper.addLast(7);
+        expect(inner, equals(["foo", "bar", "baz", 6, 7]));
+      });
+
+      test("clear()", () {
+        wrapper.clear();
+        expect(wrapper, isEmpty);
+      });
+    });
+  }, skip: "Re-enable this when test can run DDC (test#414).");
+}
diff --git a/packages/collection/test/typed_wrapper/set_test.dart b/packages/collection/test/typed_wrapper/set_test.dart
new file mode 100644
index 0000000..d7eed5f
--- /dev/null
+++ b/packages/collection/test/typed_wrapper/set_test.dart
@@ -0,0 +1,173 @@
+// Copyright (c) 2016, 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:collection/collection.dart";
+import "package:test/test.dart";
+
+import '../utils.dart';
+
+void main() {
+  group("with valid types, forwards", () {
+    var wrapper;
+    setUp(() {
+      wrapper = DelegatingSet.typed<int>(new Set<Object>.from([1, 2, 3, 4, 5]));
+    });
+
+    test("add()", () {
+      wrapper.add(1);
+      wrapper.add(6);
+      expect(wrapper, unorderedEquals([1, 2, 3, 4, 5, 6]));
+    });
+
+    test("addAll()", () {
+      wrapper.addAll([1, 6, 7]);
+      expect(wrapper, unorderedEquals([1, 2, 3, 4, 5, 6, 7]));
+    });
+
+    test("clear()", () {
+      wrapper.clear();
+      expect(wrapper, isEmpty);
+    });
+
+    test("containsAll()", () {
+      expect(wrapper.containsAll([1, 3, 5]), isTrue);
+      expect(wrapper.containsAll([1, 3, 6]), isFalse);
+      expect(wrapper.containsAll([1, 3, "foo"]), isFalse);
+    });
+
+    test("difference()", () {
+      expect(wrapper.difference(new Set.from([1, 3, 6])),
+          unorderedEquals([2, 4, 5]));
+    });
+
+    test("intersection()", () {
+      expect(wrapper.intersection(new Set.from([1, 3, 6, "foo"])),
+          unorderedEquals([1, 3]));
+    });
+
+    test("lookup()", () {
+      expect(wrapper.lookup(1), equals(1));
+      expect(wrapper.lookup(7), isNull);
+      expect(wrapper.lookup("foo"), isNull);
+    });
+
+    test("remove()", () {
+      expect(wrapper.remove(3), isTrue);
+      expect(wrapper, unorderedEquals([1, 2, 4, 5]));
+
+      expect(wrapper.remove(3), isFalse);
+      expect(wrapper.remove("foo"), isFalse);
+    });
+
+    test("removeAll()", () {
+      wrapper.removeAll([1, 3, 6, "foo"]);
+      expect(wrapper, unorderedEquals([2, 4, 5]));
+    });
+
+    test("removeWhere()", () {
+      wrapper.removeWhere((i) => i.isOdd);
+      expect(wrapper, unorderedEquals([2, 4]));
+    });
+
+    test("retainAll()", () {
+      wrapper.retainAll([1, 3, 6, "foo"]);
+      expect(wrapper, unorderedEquals([1, 3]));
+    });
+
+    test("retainWhere()", () {
+      wrapper.retainWhere((i) => i.isOdd);
+      expect(wrapper, unorderedEquals([1, 3, 5]));
+    });
+
+    test("union()", () {
+      expect(wrapper.union(new Set.from([5, 6, 7])),
+          unorderedEquals([1, 2, 3, 4, 5, 6, 7]));
+    });
+  });
+
+  group("with invalid types", () {
+    Set inner;
+    var wrapper;
+    setUp(() {
+      inner = new Set<Object>.from(["foo", "bar", "baz"]);
+      wrapper = DelegatingSet.typed<int>(inner);
+    });
+
+    group("throws a CastError for", () {
+      test("difference()", () {
+        var result = wrapper.difference(new Set.from([1, 3, 6]));
+        expect(() => result.first, throwsCastError);
+      });
+
+      test("intersection()", () {
+        var result = wrapper.intersection(new Set.from([1, 3, 6, "foo"]));
+        expect(() => result.first, throwsCastError);
+      });
+
+      test("lookup()", () {
+        expect(() => wrapper.lookup("foo"), throwsCastError);
+      });
+
+      test("removeWhere()", () {
+        expect(() => wrapper.removeWhere(expectAsync1((_) => false, count: 0)),
+            throwsCastError);
+      });
+
+      test("retainWhere()", () {
+        expect(() => wrapper.retainWhere(expectAsync1((_) => false, count: 0)),
+            throwsCastError);
+      });
+
+      test("union()", () {
+        var result = wrapper.union(new Set.from([5, 6, 7]));
+        expect(() => result.first, throwsCastError);
+      });
+    });
+
+    group("doesn't throw a CastError for", () {
+      test("add()", () {
+        wrapper.add(6);
+        expect(inner, unorderedEquals(["foo", "bar", "baz", 6]));
+      });
+
+      test("addAll()", () {
+        wrapper.addAll([6, 7]);
+        expect(inner, unorderedEquals(["foo", "bar", "baz", 6, 7]));
+      });
+
+      test("clear()", () {
+        wrapper.clear();
+        expect(wrapper, isEmpty);
+      });
+
+      test("containsAll()", () {
+        expect(wrapper.containsAll(["foo", "bar"]), isTrue);
+        expect(wrapper.containsAll(["foo", "bar", 1]), isFalse);
+      });
+
+      test("lookup()", () {
+        expect(wrapper.lookup(1), isNull);
+        expect(wrapper.lookup("zap"), isNull);
+      });
+
+      test("remove()", () {
+        expect(wrapper.remove("foo"), isTrue);
+        expect(inner, unorderedEquals(["bar", "baz"]));
+
+        expect(wrapper.remove(3), isFalse);
+        expect(wrapper.remove("foo"), isFalse);
+      });
+
+      test("removeAll()", () {
+        wrapper.removeAll([1, "foo", "baz"]);
+        expect(inner, unorderedEquals(["bar"]));
+      });
+
+      test("retainAll()", () {
+        wrapper.retainAll([1, "foo", "baz"]);
+        expect(inner, unorderedEquals(["foo", "baz"]));
+      });
+    });
+  }, skip: "Re-enable this when test can run DDC (test#414).");
+}
diff --git a/packages/collection/test/union_set_controller_test.dart b/packages/collection/test/union_set_controller_test.dart
new file mode 100644
index 0000000..366b781
--- /dev/null
+++ b/packages/collection/test/union_set_controller_test.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2016, 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:test/test.dart";
+
+import "package:collection/collection.dart";
+
+void main() {
+  var controller;
+  Set<int> innerSet;
+  setUp(() {
+    innerSet = new Set.from([1, 2, 3]);
+    controller = new UnionSetController<int>()..add(innerSet);
+  });
+
+  test("exposes a union set", () {
+    expect(controller.set, unorderedEquals([1, 2, 3]));
+
+    controller.add(new Set.from([3, 4, 5]));
+    expect(controller.set, unorderedEquals([1, 2, 3, 4, 5]));
+
+    controller.remove(innerSet);
+    expect(controller.set, unorderedEquals([3, 4, 5]));
+  });
+
+  test("exposes a disjoint union set", () {
+    expect(controller.set, unorderedEquals([1, 2, 3]));
+
+    controller.add(new Set.from([4, 5, 6]));
+    expect(controller.set, unorderedEquals([1, 2, 3, 4, 5, 6]));
+
+    controller.remove(innerSet);
+    expect(controller.set, unorderedEquals([4, 5, 6]));
+  });
+}
diff --git a/packages/collection/test/union_set_test.dart b/packages/collection/test/union_set_test.dart
new file mode 100644
index 0000000..f2a792a
--- /dev/null
+++ b/packages/collection/test/union_set_test.dart
@@ -0,0 +1,222 @@
+// Copyright (c) 2016, 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:test/test.dart";
+
+import "package:collection/collection.dart";
+
+void main() {
+  group("with an empty outer set", () {
+    var set;
+    setUp(() {
+      set = new UnionSet<int>(new Set());
+    });
+
+    test("length returns 0", () {
+      expect(set.length, equals(0));
+    });
+
+    test("contains() returns false", () {
+      expect(set.contains(0), isFalse);
+      expect(set.contains(null), isFalse);
+      expect(set.contains("foo"), isFalse);
+    });
+
+    test("lookup() returns null", () {
+      expect(set.lookup(0), isNull);
+      expect(set.lookup(null), isNull);
+      expect(set.lookup("foo"), isNull);
+    });
+
+    test("toSet() returns an empty set", () {
+      expect(set.toSet(), isEmpty);
+      expect(set.toSet(), isNot(same(set)));
+    });
+
+    test("map() doesn't run on any elements", () {
+      expect(set.map(expectAsync1((_) {}, count: 0)), isEmpty);
+    });
+  });
+
+  group("with multiple disjoint sets", () {
+    var set;
+    setUp(() {
+      set = new UnionSet<int>.from([
+        new Set.from([1, 2]),
+        new Set.from([3, 4]),
+        new Set.from([5]),
+        new Set()
+      ], disjoint: true);
+    });
+
+    test("length returns the total length", () {
+      expect(set.length, equals(5));
+    });
+
+    test("contains() returns whether any set contains the element", () {
+      expect(set.contains(1), isTrue);
+      expect(set.contains(4), isTrue);
+      expect(set.contains(5), isTrue);
+      expect(set.contains(6), isFalse);
+    });
+
+    test("lookup() returns elements that are in any set", () {
+      expect(set.lookup(1), equals(1));
+      expect(set.lookup(4), equals(4));
+      expect(set.lookup(5), equals(5));
+      expect(set.lookup(6), isNull);
+    });
+
+    test("toSet() returns the union of all the sets", () {
+      expect(set.toSet(), unorderedEquals([1, 2, 3, 4, 5]));
+      expect(set.toSet(), isNot(same(set)));
+    });
+
+    test("map() maps the elements", () {
+      expect(set.map((i) => i * 2), unorderedEquals([2, 4, 6, 8, 10]));
+    });
+  });
+
+  group("with multiple overlapping sets", () {
+    var set;
+    setUp(() {
+      set = new UnionSet<int>.from([
+        new Set.from([1, 2, 3]),
+        new Set.from([3, 4]),
+        new Set.from([5, 1]),
+        new Set()
+      ]);
+    });
+
+    test("length returns the total length", () {
+      expect(set.length, equals(5));
+    });
+
+    test("contains() returns whether any set contains the element", () {
+      expect(set.contains(1), isTrue);
+      expect(set.contains(4), isTrue);
+      expect(set.contains(5), isTrue);
+      expect(set.contains(6), isFalse);
+    });
+
+    test("lookup() returns elements that are in any set", () {
+      expect(set.lookup(1), equals(1));
+      expect(set.lookup(4), equals(4));
+      expect(set.lookup(5), equals(5));
+      expect(set.lookup(6), isNull);
+    });
+
+    test("lookup() returns the first element in an ordered context", () {
+      var duration1 = new Duration(seconds: 0);
+      var duration2 = new Duration(seconds: 0);
+      expect(duration1, equals(duration2));
+      expect(duration1, isNot(same(duration2)));
+
+      var set = new UnionSet.from([
+        new Set.from([duration1]),
+        new Set.from([duration2])
+      ]);
+
+      expect(set.lookup(new Duration(seconds: 0)), same(duration1));
+    });
+
+    test("toSet() returns the union of all the sets", () {
+      expect(set.toSet(), unorderedEquals([1, 2, 3, 4, 5]));
+      expect(set.toSet(), isNot(same(set)));
+    });
+
+    test("map() maps the elements", () {
+      expect(set.map((i) => i * 2), unorderedEquals([2, 4, 6, 8, 10]));
+    });
+  });
+
+  group("after an inner set was modified", () {
+    var set;
+    setUp(() {
+      var innerSet = new Set<int>.from([3, 7]);
+      set = new UnionSet<int>.from([
+        new Set.from([1, 2]),
+        new Set.from([5]),
+        innerSet
+      ]);
+
+      innerSet.add(4);
+      innerSet.remove(7);
+    });
+
+    test("length returns the total length", () {
+      expect(set.length, equals(5));
+    });
+
+    test("contains() returns true for a new element", () {
+      expect(set.contains(4), isTrue);
+    });
+
+    test("contains() returns false for a removed element", () {
+      expect(set.contains(7), isFalse);
+    });
+
+    test("lookup() returns a new element", () {
+      expect(set.lookup(4), equals(4));
+    });
+
+    test("lookup() doesn't returns a removed element", () {
+      expect(set.lookup(7), isNull);
+    });
+
+    test("toSet() returns the union of all the sets", () {
+      expect(set.toSet(), unorderedEquals([1, 2, 3, 4, 5]));
+      expect(set.toSet(), isNot(same(set)));
+    });
+
+    test("map() maps the elements", () {
+      expect(set.map((i) => i * 2), unorderedEquals([2, 4, 6, 8, 10]));
+    });
+  });
+
+  group("after the outer set was modified", () {
+    var set;
+    setUp(() {
+      var innerSet = new Set.from([6]);
+      var outerSet = new Set<Set<int>>.from([
+        new Set.from([1, 2]),
+        new Set.from([5]),
+        innerSet
+      ]);
+
+      set = new UnionSet<int>(outerSet);
+      outerSet.remove(innerSet);
+      outerSet.add(new Set.from([3, 4]));
+    });
+
+    test("length returns the total length", () {
+      expect(set.length, equals(5));
+    });
+
+    test("contains() returns true for a new element", () {
+      expect(set.contains(4), isTrue);
+    });
+
+    test("contains() returns false for a removed element", () {
+      expect(set.contains(6), isFalse);
+    });
+
+    test("lookup() returns a new element", () {
+      expect(set.lookup(4), equals(4));
+    });
+
+    test("lookup() doesn't returns a removed element", () {
+      expect(set.lookup(6), isNull);
+    });
+
+    test("toSet() returns the union of all the sets", () {
+      expect(set.toSet(), unorderedEquals([1, 2, 3, 4, 5]));
+      expect(set.toSet(), isNot(same(set)));
+    });
+
+    test("map() maps the elements", () {
+      expect(set.map((i) => i * 2), unorderedEquals([2, 4, 6, 8, 10]));
+    });
+  });
+}
diff --git a/packages/collection/test/unmodifiable_collection_test.dart b/packages/collection/test/unmodifiable_collection_test.dart
new file mode 100644
index 0000000..a6705f6
--- /dev/null
+++ b/packages/collection/test/unmodifiable_collection_test.dart
@@ -0,0 +1,625 @@
+// 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.
+
+import "package:test/test.dart";
+
+import "package:collection/collection.dart";
+
+// Test unmodifiable collection views.
+// The collections should pass through the operations that are allowed,
+// an throw on the ones that aren't without affecting the original.
+
+main() {
+  var list = <int>[];
+  testUnmodifiableList(list, new UnmodifiableListView(list), "empty");
+  list = [42];
+  testUnmodifiableList(list, new UnmodifiableListView(list), "single-42");
+  list = [7];
+  testUnmodifiableList(list, new UnmodifiableListView(list), "single!42");
+  list = [1, 42, 10];
+  testUnmodifiableList(list, new UnmodifiableListView(list), "three-42");
+  list = [1, 7, 10];
+  testUnmodifiableList(list, new UnmodifiableListView(list), "three!42");
+
+  list = [];
+  testNonGrowableList(list, new NonGrowableListView(list), "empty");
+  list = [42];
+  testNonGrowableList(list, new NonGrowableListView(list), "single-42");
+  list = [7];
+  testNonGrowableList(list, new NonGrowableListView(list), "single!42");
+  list = [1, 42, 10];
+  testNonGrowableList(list, new NonGrowableListView(list), "three-42");
+  list = [1, 7, 10];
+  testNonGrowableList(list, new NonGrowableListView(list), "three!42");
+
+  var aSet = new Set<int>();
+  testUnmodifiableSet(aSet, new UnmodifiableSetView(aSet), "empty");
+  aSet = new Set();
+  testUnmodifiableSet(aSet, const UnmodifiableSetView.empty(), "const empty");
+  aSet = new Set.from([42]);
+  testUnmodifiableSet(aSet, new UnmodifiableSetView(aSet), "single-42");
+  aSet = new Set.from([7]);
+  testUnmodifiableSet(aSet, new UnmodifiableSetView(aSet), "single!42");
+  aSet = new Set.from([1, 42, 10]);
+  testUnmodifiableSet(aSet, new UnmodifiableSetView(aSet), "three-42");
+  aSet = new Set.from([1, 7, 10]);
+  testUnmodifiableSet(aSet, new UnmodifiableSetView(aSet), "three!42");
+}
+
+void testUnmodifiableList(List<int> original, List<int> wrapped, String name) {
+  name = "unmodifiable-list-$name";
+  testIterable(original, wrapped, name);
+  testReadList(original, wrapped, name);
+  testNoWriteList(original, wrapped, name);
+  testNoChangeLengthList(original, wrapped, name);
+}
+
+void testNonGrowableList(List<int> original, List<int> wrapped, String name) {
+  name = "nongrowable-list-$name";
+  testIterable(original, wrapped, name);
+  testReadList(original, wrapped, name);
+  testWriteList(original, wrapped, name);
+  testNoChangeLengthList(original, wrapped, name);
+}
+
+void testUnmodifiableSet(Set<int> original, Set<int> wrapped, String name) {
+  name = "unmodifiable-set-$name";
+  testIterable(original, wrapped, name);
+  testReadSet(original, wrapped, name);
+  testNoChangeSet(original, wrapped, name);
+}
+
+void testIterable(Iterable<int> original, Iterable<int> wrapped, String name) {
+  test("$name - any", () {
+    expect(wrapped.any((x) => true), equals(original.any((x) => true)));
+    expect(wrapped.any((x) => false), equals(original.any((x) => false)));
+  });
+
+  test("$name - contains", () {
+    expect(wrapped.contains(0), equals(original.contains(0)));
+  });
+
+  test("$name - elementAt", () {
+    if (original.isEmpty) {
+      expect(() => wrapped.elementAt(0), throwsRangeError);
+    } else {
+      expect(wrapped.elementAt(0), equals(original.elementAt(0)));
+    }
+  });
+
+  test("$name - every", () {
+    expect(wrapped.every((x) => true), equals(original.every((x) => true)));
+    expect(wrapped.every((x) => false), equals(original.every((x) => false)));
+  });
+
+  test("$name - expand", () {
+    expect(
+        wrapped.expand((x) => [x, x]), equals(original.expand((x) => [x, x])));
+  });
+
+  test("$name - first", () {
+    if (original.isEmpty) {
+      expect(() => wrapped.first, throwsStateError);
+    } else {
+      expect(wrapped.first, equals(original.first));
+    }
+  });
+
+  test("$name - firstWhere", () {
+    if (original.isEmpty) {
+      expect(() => wrapped.firstWhere((_) => true), throwsStateError);
+    } else {
+      expect(wrapped.firstWhere((_) => true),
+          equals(original.firstWhere((_) => true)));
+    }
+    expect(() => wrapped.firstWhere((_) => false), throwsStateError);
+  });
+
+  test("$name - fold", () {
+    expect(wrapped.fold(0, (x, y) => x + y),
+        equals(original.fold(0, (x, y) => x + y)));
+  });
+
+  test("$name - forEach", () {
+    int wrapCtr = 0;
+    int origCtr = 0;
+    wrapped.forEach((x) {
+      wrapCtr += x;
+    });
+    original.forEach((x) {
+      origCtr += x;
+    });
+    expect(wrapCtr, equals(origCtr));
+  });
+
+  test("$name - isEmpty", () {
+    expect(wrapped.isEmpty, equals(original.isEmpty));
+  });
+
+  test("$name - isNotEmpty", () {
+    expect(wrapped.isNotEmpty, equals(original.isNotEmpty));
+  });
+
+  test("$name - iterator", () {
+    Iterator wrapIter = wrapped.iterator;
+    Iterator origIter = original.iterator;
+    while (origIter.moveNext()) {
+      expect(wrapIter.moveNext(), equals(true));
+      expect(wrapIter.current, equals(origIter.current));
+    }
+    expect(wrapIter.moveNext(), equals(false));
+  });
+
+  test("$name - join", () {
+    expect(wrapped.join(""), equals(original.join("")));
+    expect(wrapped.join("-"), equals(original.join("-")));
+  });
+
+  test("$name - last", () {
+    if (original.isEmpty) {
+      expect(() => wrapped.last, throwsStateError);
+    } else {
+      expect(wrapped.last, equals(original.last));
+    }
+  });
+
+  test("$name - lastWhere", () {
+    if (original.isEmpty) {
+      expect(() => wrapped.lastWhere((_) => true), throwsStateError);
+    } else {
+      expect(wrapped.lastWhere((_) => true),
+          equals(original.lastWhere((_) => true)));
+    }
+    expect(() => wrapped.lastWhere((_) => false), throwsStateError);
+  });
+
+  test("$name - length", () {
+    expect(wrapped.length, equals(original.length));
+  });
+
+  test("$name - map", () {
+    expect(wrapped.map((x) => "[$x]"), equals(original.map((x) => "[$x]")));
+  });
+
+  test("$name - reduce", () {
+    if (original.isEmpty) {
+      expect(() => wrapped.reduce((x, y) => x + y), throwsStateError);
+    } else {
+      expect(wrapped.reduce((x, y) => x + y),
+          equals(original.reduce((x, y) => x + y)));
+    }
+  });
+
+  test("$name - single", () {
+    if (original.length != 1) {
+      expect(() => wrapped.single, throwsStateError);
+    } else {
+      expect(wrapped.single, equals(original.single));
+    }
+  });
+
+  test("$name - singleWhere", () {
+    if (original.length != 1) {
+      expect(() => wrapped.singleWhere((_) => true), throwsStateError);
+    } else {
+      expect(wrapped.singleWhere((_) => true),
+          equals(original.singleWhere((_) => true)));
+    }
+    expect(() => wrapped.singleWhere((_) => false), throwsStateError);
+  });
+
+  test("$name - skip", () {
+    expect(wrapped.skip(0), orderedEquals(original.skip(0)));
+    expect(wrapped.skip(1), orderedEquals(original.skip(1)));
+    expect(wrapped.skip(5), orderedEquals(original.skip(5)));
+  });
+
+  test("$name - skipWhile", () {
+    expect(wrapped.skipWhile((x) => true),
+        orderedEquals(original.skipWhile((x) => true)));
+    expect(wrapped.skipWhile((x) => false),
+        orderedEquals(original.skipWhile((x) => false)));
+    expect(wrapped.skipWhile((x) => x != 42),
+        orderedEquals(original.skipWhile((x) => x != 42)));
+  });
+
+  test("$name - take", () {
+    expect(wrapped.take(0), orderedEquals(original.take(0)));
+    expect(wrapped.take(1), orderedEquals(original.take(1)));
+    expect(wrapped.take(5), orderedEquals(original.take(5)));
+  });
+
+  test("$name - takeWhile", () {
+    expect(wrapped.takeWhile((x) => true),
+        orderedEquals(original.takeWhile((x) => true)));
+    expect(wrapped.takeWhile((x) => false),
+        orderedEquals(original.takeWhile((x) => false)));
+    expect(wrapped.takeWhile((x) => x != 42),
+        orderedEquals(original.takeWhile((x) => x != 42)));
+  });
+
+  test("$name - toList", () {
+    expect(wrapped.toList(), orderedEquals(original.toList()));
+    expect(wrapped.toList(growable: false),
+        orderedEquals(original.toList(growable: false)));
+  });
+
+  test("$name - toSet", () {
+    expect(wrapped.toSet(), unorderedEquals(original.toSet()));
+  });
+
+  test("$name - where", () {
+    expect(
+        wrapped.where((x) => true), orderedEquals(original.where((x) => true)));
+    expect(wrapped.where((x) => false),
+        orderedEquals(original.where((x) => false)));
+    expect(wrapped.where((x) => x != 42),
+        orderedEquals(original.where((x) => x != 42)));
+  });
+}
+
+void testReadList(List original, List wrapped, String name) {
+  test("$name - length", () {
+    expect(wrapped.length, equals(original.length));
+  });
+
+  test("$name - isEmpty", () {
+    expect(wrapped.isEmpty, equals(original.isEmpty));
+  });
+
+  test("$name - isNotEmpty", () {
+    expect(wrapped.isNotEmpty, equals(original.isNotEmpty));
+  });
+
+  test("$name - []", () {
+    if (original.isEmpty) {
+      expect(() {
+        wrapped[0];
+      }, throwsRangeError);
+    } else {
+      expect(wrapped[0], equals(original[0]));
+    }
+  });
+
+  test("$name - indexOf", () {
+    expect(wrapped.indexOf(42), equals(original.indexOf(42)));
+  });
+
+  test("$name - lastIndexOf", () {
+    expect(wrapped.lastIndexOf(42), equals(original.lastIndexOf(42)));
+  });
+
+  test("$name - getRange", () {
+    int len = original.length;
+    expect(wrapped.getRange(0, len), equals(original.getRange(0, len)));
+    expect(wrapped.getRange(len ~/ 2, len),
+        equals(original.getRange(len ~/ 2, len)));
+    expect(
+        wrapped.getRange(0, len ~/ 2), equals(original.getRange(0, len ~/ 2)));
+  });
+
+  test("$name - sublist", () {
+    int len = original.length;
+    expect(wrapped.sublist(0), equals(original.sublist(0)));
+    expect(wrapped.sublist(len ~/ 2), equals(original.sublist(len ~/ 2)));
+    expect(wrapped.sublist(0, len ~/ 2), equals(original.sublist(0, len ~/ 2)));
+  });
+
+  test("$name - asMap", () {
+    expect(wrapped.asMap(), equals(original.asMap()));
+  });
+}
+
+void testNoWriteList(List original, List wrapped, String name) {
+  List copy = new List.from(original);
+
+  testThrows(name, thunk) {
+    test(name, () {
+      expect(thunk, throwsUnsupportedError);
+      // No modifications happened.
+      expect(original, equals(copy));
+    });
+  }
+
+  testThrows("$name - []= throws", () {
+    wrapped[0] = 42;
+  });
+
+  testThrows("$name - sort throws", () {
+    wrapped.sort();
+  });
+
+  testThrows("$name - fillRange throws", () {
+    wrapped.fillRange(0, wrapped.length, 42);
+  });
+
+  testThrows("$name - setRange throws", () {
+    wrapped.setRange(
+        0, wrapped.length, new Iterable.generate(wrapped.length, (i) => i));
+  });
+
+  testThrows("$name - setAll throws", () {
+    wrapped.setAll(0, new Iterable.generate(wrapped.length, (i) => i));
+  });
+}
+
+void testWriteList(List<int> original, List wrapped, String name) {
+  var copy = new List<int>.from(original);
+
+  test("$name - []=", () {
+    if (original.isNotEmpty) {
+      int originalFirst = original[0];
+      wrapped[0] = originalFirst + 1;
+      expect(original[0], equals(originalFirst + 1));
+      original[0] = originalFirst;
+    } else {
+      expect(() {
+        wrapped[0] = 42;
+      }, throwsRangeError);
+    }
+  });
+
+  test("$name - sort", () {
+    List sortCopy = new List.from(original);
+    sortCopy.sort();
+    wrapped.sort();
+    expect(original, orderedEquals(sortCopy));
+    original.setAll(0, copy);
+  });
+
+  test("$name - fillRange", () {
+    wrapped.fillRange(0, wrapped.length, 37);
+    for (int i = 0; i < original.length; i++) {
+      expect(original[i], equals(37));
+    }
+    original.setAll(0, copy);
+  });
+
+  test("$name - setRange", () {
+    List reverseList = original.reversed.toList();
+    wrapped.setRange(0, wrapped.length, reverseList);
+    expect(original, equals(reverseList));
+    original.setAll(0, copy);
+  });
+
+  test("$name - setAll", () {
+    List reverseList = original.reversed.toList();
+    wrapped.setAll(0, reverseList);
+    expect(original, equals(reverseList));
+    original.setAll(0, copy);
+  });
+}
+
+void testNoChangeLengthList(List original, List wrapped, String name) {
+  List copy = new List.from(original);
+
+  void testThrows(String name, thunk) {
+    test(name, () {
+      expect(thunk, throwsUnsupportedError);
+      // No modifications happened.
+      expect(original, equals(copy));
+    });
+  }
+
+  testThrows("$name - length= throws", () {
+    wrapped.length = 100;
+  });
+
+  testThrows("$name - add throws", () {
+    wrapped.add(42);
+  });
+
+  testThrows("$name - addAll throws", () {
+    wrapped.addAll([42]);
+  });
+
+  testThrows("$name - insert throws", () {
+    wrapped.insert(0, 42);
+  });
+
+  testThrows("$name - insertAll throws", () {
+    wrapped.insertAll(0, [42]);
+  });
+
+  testThrows("$name - remove throws", () {
+    wrapped.remove(42);
+  });
+
+  testThrows("$name - removeAt throws", () {
+    wrapped.removeAt(0);
+  });
+
+  testThrows("$name - removeLast throws", () {
+    wrapped.removeLast();
+  });
+
+  testThrows("$name - removeWhere throws", () {
+    wrapped.removeWhere((element) => false);
+  });
+
+  testThrows("$name - retainWhere throws", () {
+    wrapped.retainWhere((element) => true);
+  });
+
+  testThrows("$name - removeRange throws", () {
+    wrapped.removeRange(0, wrapped.length);
+  });
+
+  testThrows("$name - replaceRange throws", () {
+    wrapped.replaceRange(0, wrapped.length, [42]);
+  });
+
+  testThrows("$name - clear throws", () {
+    wrapped.clear();
+  });
+}
+
+void testReadSet(Set original, Set wrapped, String name) {
+  Set copy = new Set.from(original);
+
+  test("$name - containsAll", () {
+    expect(wrapped.containsAll(copy), isTrue);
+    expect(wrapped.containsAll(copy.toList()), isTrue);
+    expect(wrapped.containsAll([]), isTrue);
+    expect(wrapped.containsAll([42]), equals(original.containsAll([42])));
+  });
+
+  test("$name - intersection", () {
+    expect(wrapped.intersection(new Set()), isEmpty);
+    expect(wrapped.intersection(copy), unorderedEquals(original));
+    expect(wrapped.intersection(new Set.from([42])),
+        new Set.from(original.contains(42) ? [42] : []));
+  });
+
+  test("$name - union", () {
+    expect(wrapped.union(new Set()), unorderedEquals(original));
+    expect(wrapped.union(copy), unorderedEquals(original));
+    expect(wrapped.union(new Set.from([42])),
+        equals(original.union(new Set.from([42]))));
+  });
+
+  test("$name - difference", () {
+    expect(wrapped.difference(new Set()), unorderedEquals(original));
+    expect(wrapped.difference(copy), isEmpty);
+    expect(wrapped.difference(new Set.from([42])),
+        equals(original.difference(new Set.from([42]))));
+  });
+}
+
+void testNoChangeSet(Set original, Set wrapped, String name) {
+  List originalElements = original.toList();
+
+  testThrows(name, thunk) {
+    test(name, () {
+      expect(thunk, throwsUnsupportedError);
+      // No modifications happened.
+      expect(original.toList(), equals(originalElements));
+    });
+  }
+
+  testThrows("$name - add throws", () {
+    wrapped.add(42);
+  });
+
+  testThrows("$name - addAll throws", () {
+    wrapped.addAll([42]);
+  });
+
+  testThrows("$name - addAll empty throws", () {
+    wrapped.addAll([]);
+  });
+
+  testThrows("$name - remove throws", () {
+    wrapped.remove(42);
+  });
+
+  testThrows("$name - removeAll throws", () {
+    wrapped.removeAll([42]);
+  });
+
+  testThrows("$name - removeAll empty throws", () {
+    wrapped.removeAll([]);
+  });
+
+  testThrows("$name - retainAll throws", () {
+    wrapped.retainAll([42]);
+  });
+
+  testThrows("$name - removeWhere throws", () {
+    wrapped.removeWhere((_) => false);
+  });
+
+  testThrows("$name - retainWhere throws", () {
+    wrapped.retainWhere((_) => true);
+  });
+
+  testThrows("$name - clear throws", () {
+    wrapped.clear();
+  });
+}
+
+void testReadMap(Map<int, int> original, Map<int, int> wrapped, String name) {
+  test("$name length", () {
+    expect(wrapped.length, equals(original.length));
+  });
+
+  test("$name isEmpty", () {
+    expect(wrapped.isEmpty, equals(original.isEmpty));
+  });
+
+  test("$name isNotEmpty", () {
+    expect(wrapped.isNotEmpty, equals(original.isNotEmpty));
+  });
+
+  test("$name operator[]", () {
+    expect(wrapped[0], equals(original[0]));
+    expect(wrapped[999], equals(original[999]));
+  });
+
+  test("$name containsKey", () {
+    expect(wrapped.containsKey(0), equals(original.containsKey(0)));
+    expect(wrapped.containsKey(999), equals(original.containsKey(999)));
+  });
+
+  test("$name containsValue", () {
+    expect(wrapped.containsValue(0), equals(original.containsValue(0)));
+    expect(wrapped.containsValue(999), equals(original.containsValue(999)));
+  });
+
+  test("$name forEach", () {
+    int origCnt = 0;
+    int wrapCnt = 0;
+    wrapped.forEach((k, v) {
+      wrapCnt += 1 << k + 3 * v;
+    });
+    original.forEach((k, v) {
+      origCnt += 1 << k + 3 * v;
+    });
+    expect(wrapCnt, equals(origCnt));
+  });
+
+  test("$name keys", () {
+    expect(wrapped.keys, orderedEquals(original.keys));
+  });
+
+  test("$name values", () {
+    expect(wrapped.values, orderedEquals(original.values));
+  });
+}
+
+testNoChangeMap(Map original, Map wrapped, String name) {
+  Map copy = new Map.from(original);
+
+  testThrows(name, thunk) {
+    test(name, () {
+      expect(thunk, throwsUnsupportedError);
+      // No modifications happened.
+      expect(original, equals(copy));
+    });
+  }
+
+  testThrows("$name operator[]= throws", () {
+    wrapped[0] = 42;
+  });
+
+  testThrows("$name putIfAbsent throws", () {
+    wrapped.putIfAbsent(0, () => 42);
+  });
+
+  testThrows("$name addAll throws", () {
+    wrapped.addAll(new Map()..[42] = 42);
+  });
+
+  testThrows("$name addAll empty throws", () {
+    wrapped.addAll(new Map());
+  });
+
+  testThrows("$name remove throws", () {
+    wrapped.remove(0);
+  });
+
+  testThrows("$name clear throws", () {
+    wrapped.clear();
+  });
+}
diff --git a/packages/collection/test/utils.dart b/packages/collection/test/utils.dart
new file mode 100644
index 0000000..d8ab082
--- /dev/null
+++ b/packages/collection/test/utils.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2016, 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:test/test.dart";
+
+final Matcher throwsCastError = throwsA(new isInstanceOf<CastError>());
diff --git a/packages/collection/test/wrapper_test.dart b/packages/collection/test/wrapper_test.dart
new file mode 100644
index 0000000..95e6f36
--- /dev/null
+++ b/packages/collection/test/wrapper_test.dart
@@ -0,0 +1,667 @@
+// 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.
+
+/// Tests wrapper utilities.
+
+import "dart:collection";
+import "package:collection/collection.dart";
+import "package:test/test.dart";
+
+// Test that any member access/call on the wrapper object is equal to
+// an expected access on the wrapped object.
+// This is implemented by capturing accesses using noSuchMethod and comparing
+// them to expected accesses captured previously.
+
+// Compare two Invocations for having equal type and arguments.
+void testInvocations(Invocation i1, Invocation i2) {
+  String name = "${i1.memberName}";
+  expect(i1.isGetter, equals(i2.isGetter), reason: name);
+  expect(i1.isSetter, equals(i2.isSetter), reason: name);
+  expect(i1.memberName, equals(i2.memberName), reason: name);
+  expect(i1.positionalArguments, equals(i2.positionalArguments), reason: name);
+  expect(i1.namedArguments, equals(i2.namedArguments), reason: name);
+}
+
+/// Utility class to record a member access and a member access on a wrapped
+/// object, and compare them for equality.
+abstract class Expector {
+  getWrappedObject(action(Invocation i));
+  // Hack to test assignment ([]=) because it doesn't return the result
+  // of the member call. Instead use (expect..[4]=5).equal[4]=5  where
+  // you would normally use expect[4].equals[4] for non-assignments.
+  var equals;
+
+  noSuchMethod(Invocation m) => new _Equals(equals = getWrappedObject((m2) {
+        testInvocations(m, m2);
+      }));
+
+  // dartanalyzer complains if this method is named `toString()`, since, if it
+  // truly overrides Object's `toString()`, it should return a String.
+  asString() => new _Equals(equals = getWrappedObject((m2) {
+        testInvocations(TO_STRING_INVOCATION, m2);
+      }));
+}
+
+// An object with a field called "equals", only introduced into the
+// flow to allow writing expect.xxx.equals.xxx.
+class _Equals {
+  final equals;
+  _Equals(this.equals);
+}
+
+class SyntheticInvocation implements Invocation {
+  static const int METHOD = 0x00;
+  static const int GETTER = 0x01;
+  static const int SETTER = 0x02;
+  final Symbol memberName;
+  final List positionalArguments;
+  final Map<Symbol, dynamic> namedArguments;
+  final int _type;
+  const SyntheticInvocation(this.memberName, this.positionalArguments,
+      this.namedArguments, this._type);
+
+  List<Type> get typeArguments => const <Type>[];
+
+  bool get isMethod => _type == METHOD;
+
+  bool get isGetter => _type == GETTER;
+
+  bool get isSetter => _type == SETTER;
+
+  bool get isAccessor => isGetter || isSetter;
+}
+
+// Parameterization of noSuchMethod.
+class NSM {
+  Function _action;
+  NSM(this._action);
+  noSuchMethod(Invocation i) => _action(i);
+}
+
+const TO_STRING_INVOCATION = const SyntheticInvocation(
+    #toString, const [], const {}, SyntheticInvocation.METHOD);
+
+// LikeNSM, but has types Iterable, Set and List to allow it as
+// argument to DelegatingIterable/Set/List.
+class IterableNSM extends NSM implements Iterable, Set, List, Queue {
+  IterableNSM(action(Invocation i)) : super(action);
+  toString() => super.noSuchMethod(TO_STRING_INVOCATION) as String;
+
+  Null cast<T>();
+  Null retype<T>();
+}
+
+// Expector that wraps in DelegatingIterable.
+class IterableExpector extends Expector {
+  getWrappedObject(void action(Invocation i)) {
+    return new DelegatingIterable(new IterableNSM(action));
+  }
+}
+
+// Expector that wraps in DelegatingList.
+class ListExpector extends Expector {
+  getWrappedObject(void action(Invocation i)) {
+    return new DelegatingList(new IterableNSM(action));
+  }
+}
+
+// Expector that wraps in DelegatingSet.
+class SetExpector extends Expector {
+  getWrappedObject(void action(Invocation i)) {
+    return new DelegatingSet(new IterableNSM(action));
+  }
+}
+
+// Expector that wraps in DelegatingSet.
+class QueueExpector extends Expector {
+  getWrappedObject(void action(Invocation i)) {
+    return new DelegatingQueue(new IterableNSM(action));
+  }
+}
+
+// Like NSM but implements Map to allow as argument for DelegatingMap.
+class MapNSM extends NSM implements Map {
+  MapNSM(action(Invocation i)) : super(action);
+  toString() => super.noSuchMethod(TO_STRING_INVOCATION) as String;
+}
+
+// Expector that wraps in DelegatingMap.
+class MapExpector extends Expector {
+  getWrappedObject(void action(Invocation i)) {
+    return new DelegatingMap(new MapNSM(action));
+  }
+}
+
+// Utility values to use as arguments in calls.
+func0() {}
+func1(x) {}
+func2(x, y) {}
+var val = new Object();
+
+void main() {
+  testIterable(var expect) {
+    expect.any(func1).equals.any(func1);
+    expect.contains(val).equals.contains(val);
+    expect.elementAt(0).equals.elementAt(0);
+    expect.every(func1).equals.every(func1);
+    expect.expand(func1).equals.expand(func1);
+    expect.first.equals.first;
+    // Default values of the Iterable interface will be added in the
+    // second call to firstWhere, so we must record them in our
+    // expectation (which doesn't have the interface implemented or
+    // its default values).
+    expect.firstWhere(func1, orElse: null).equals.firstWhere(func1);
+    expect
+        .firstWhere(func1, orElse: func0)
+        .equals
+        .firstWhere(func1, orElse: func0);
+    expect.fold(null, func2).equals.fold(null, func2);
+    expect.forEach(func1).equals.forEach(func1);
+    expect.isEmpty.equals.isEmpty;
+    expect.isNotEmpty.equals.isNotEmpty;
+    expect.iterator.equals.iterator;
+    expect.join('').equals.join();
+    expect.join("X").equals.join("X");
+    expect.last.equals.last;
+    expect.lastWhere(func1, orElse: null).equals.lastWhere(func1);
+    expect
+        .lastWhere(func1, orElse: func0)
+        .equals
+        .lastWhere(func1, orElse: func0);
+    expect.length.equals.length;
+    expect.map(func1).equals.map(func1);
+    expect.reduce(func2).equals.reduce(func2);
+    expect.single.equals.single;
+    expect.singleWhere(func1).equals.singleWhere(func1);
+    expect.skip(5).equals.skip(5);
+    expect.skipWhile(func1).equals.skipWhile(func1);
+    expect.take(5).equals.take(5);
+    expect.takeWhile(func1).equals.takeWhile(func1);
+    expect.toList(growable: true).equals.toList();
+    expect.toList(growable: true).equals.toList(growable: true);
+    expect.toList(growable: false).equals.toList(growable: false);
+    expect.toSet().equals.toSet();
+    expect.asString().equals.toString();
+    expect.where(func1).equals.where(func1);
+  }
+
+  void testList(var expect) {
+    testIterable(expect);
+
+    expect[4].equals[4];
+    (expect..[4] = 5).equals[4] = 5;
+
+    expect.add(val).equals.add(val);
+    expect.addAll([val]).equals.addAll([val]);
+    expect.asMap().equals.asMap();
+    expect.clear().equals.clear();
+    expect.fillRange(4, 5, null).equals.fillRange(4, 5);
+    expect.fillRange(4, 5, val).equals.fillRange(4, 5, val);
+    expect.getRange(4, 5).equals.getRange(4, 5);
+    expect.indexOf(val, 0).equals.indexOf(val);
+    expect.indexOf(val, 4).equals.indexOf(val, 4);
+    expect.insert(4, val).equals.insert(4, val);
+    expect.insertAll(4, [val]).equals.insertAll(4, [val]);
+    expect.lastIndexOf(val, null).equals.lastIndexOf(val);
+    expect.lastIndexOf(val, 4).equals.lastIndexOf(val, 4);
+    (expect..length = 4).equals.length = 4;
+    expect.remove(val).equals.remove(val);
+    expect.removeAt(4).equals.removeAt(4);
+    expect.removeLast().equals.removeLast();
+    expect.removeRange(4, 5).equals.removeRange(4, 5);
+    expect.removeWhere(func1).equals.removeWhere(func1);
+    expect.replaceRange(4, 5, [val]).equals.replaceRange(4, 5, [val]);
+    expect.retainWhere(func1).equals.retainWhere(func1);
+    expect.reversed.equals.reversed;
+    expect.setAll(4, [val]).equals.setAll(4, [val]);
+    expect.setRange(4, 5, [val], 0).equals.setRange(4, 5, [val]);
+    expect.setRange(4, 5, [val], 3).equals.setRange(4, 5, [val], 3);
+    expect.sort(null).equals.sort();
+    expect.sort(func2).equals.sort(func2);
+    expect.sublist(4, null).equals.sublist(4);
+    expect.sublist(4, 5).equals.sublist(4, 5);
+  }
+
+  void testSet(var expect) {
+    testIterable(expect);
+    Set set = new Set();
+    expect.add(val).equals.add(val);
+    expect.addAll([val]).equals.addAll([val]);
+    expect.clear().equals.clear();
+    expect.containsAll([val]).equals.containsAll([val]);
+    expect.difference(set).equals.difference(set);
+    expect.intersection(set).equals.intersection(set);
+    expect.remove(val).equals.remove(val);
+    expect.removeAll([val]).equals.removeAll([val]);
+    expect.removeWhere(func1).equals.removeWhere(func1);
+    expect.retainAll([val]).equals.retainAll([val]);
+    expect.retainWhere(func1).equals.retainWhere(func1);
+    expect.union(set).equals.union(set);
+  }
+
+  void testQueue(var expect) {
+    testIterable(expect);
+    expect.add(val).equals.add(val);
+    expect.addAll([val]).equals.addAll([val]);
+    expect.addFirst(val).equals.addFirst(val);
+    expect.addLast(val).equals.addLast(val);
+    expect.clear().equals.clear();
+    expect.remove(val).equals.remove(val);
+    expect.removeFirst().equals.removeFirst();
+    expect.removeLast().equals.removeLast();
+  }
+
+  void testMap(var expect) {
+    Map map = new Map();
+    expect[val].equals[val];
+    (expect..[val] = val).equals[val] = val;
+    expect.addAll(map).equals.addAll(map);
+    expect.clear().equals.clear();
+    expect.containsKey(val).equals.containsKey(val);
+    expect.containsValue(val).equals.containsValue(val);
+    expect.forEach(func2).equals.forEach(func2);
+    expect.isEmpty.equals.isEmpty;
+    expect.isNotEmpty.equals.isNotEmpty;
+    expect.keys.equals.keys;
+    expect.length.equals.length;
+    expect.putIfAbsent(val, func0).equals.putIfAbsent(val, func0);
+    expect.remove(val).equals.remove(val);
+    expect.values.equals.values;
+    expect.asString().equals.toString();
+  }
+
+  // Runs tests of Set behavior.
+  //
+  // [setUpSet] should return a set with two elements: "foo" and "bar".
+  void testTwoElementSet(Set<String> setUpSet()) {
+    group("with two elements", () {
+      var set;
+      setUp(() => set = setUpSet());
+
+      test(".any", () {
+        expect(set.any((element) => element == "foo"), isTrue);
+        expect(set.any((element) => element == "baz"), isFalse);
+      });
+
+      test(".elementAt", () {
+        expect(set.elementAt(0), equals("foo"));
+        expect(set.elementAt(1), equals("bar"));
+        expect(() => set.elementAt(2), throwsRangeError);
+      });
+
+      test(".every", () {
+        expect(set.every((element) => element == "foo"), isFalse);
+        expect(set.every((element) => element is String), isTrue);
+      });
+
+      test(".expand", () {
+        expect(set.expand((element) {
+          return [element.substring(0, 1), element.substring(1)];
+        }), equals(["f", "oo", "b", "ar"]));
+      });
+
+      test(".first", () {
+        expect(set.first, equals("foo"));
+      });
+
+      test(".firstWhere", () {
+        expect(set.firstWhere((element) => element is String), equals("foo"));
+        expect(set.firstWhere((element) => element.startsWith("b")),
+            equals("bar"));
+        expect(() => set.firstWhere((element) => element is int),
+            throwsStateError);
+        expect(set.firstWhere((element) => element is int, orElse: () => "baz"),
+            equals("baz"));
+      });
+
+      test(".fold", () {
+        expect(set.fold("start", (previous, element) => previous + element),
+            equals("startfoobar"));
+      });
+
+      test(".forEach", () {
+        var values = [];
+        set.forEach(values.add);
+        expect(values, equals(["foo", "bar"]));
+      });
+
+      test(".iterator", () {
+        var values = [];
+        for (var element in set) {
+          values.add(element);
+        }
+        expect(values, equals(["foo", "bar"]));
+      });
+
+      test(".join", () {
+        expect(set.join(", "), equals("foo, bar"));
+      });
+
+      test(".last", () {
+        expect(set.last, equals("bar"));
+      });
+
+      test(".lastWhere", () {
+        expect(set.lastWhere((element) => element is String), equals("bar"));
+        expect(
+            set.lastWhere((element) => element.startsWith("f")), equals("foo"));
+        expect(
+            () => set.lastWhere((element) => element is int), throwsStateError);
+        expect(set.lastWhere((element) => element is int, orElse: () => "baz"),
+            equals("baz"));
+      });
+
+      test(".map", () {
+        expect(
+            set.map((element) => element.substring(1)), equals(["oo", "ar"]));
+      });
+
+      test(".reduce", () {
+        expect(set.reduce((previous, element) => previous + element),
+            equals("foobar"));
+      });
+
+      test(".singleWhere", () {
+        expect(() => set.singleWhere((element) => element == "baz"),
+            throwsStateError);
+        expect(set.singleWhere((element) => element == "foo"), "foo");
+        expect(() => set.singleWhere((element) => element is String),
+            throwsStateError);
+      });
+
+      test(".skip", () {
+        expect(set.skip(0), equals(["foo", "bar"]));
+        expect(set.skip(1), equals(["bar"]));
+        expect(set.skip(2), equals([]));
+      });
+
+      test(".skipWhile", () {
+        expect(set.skipWhile((element) => element.startsWith("f")),
+            equals(["bar"]));
+        expect(set.skipWhile((element) => element.startsWith("z")),
+            equals(["foo", "bar"]));
+        expect(set.skipWhile((element) => element is String), equals([]));
+      });
+
+      test(".take", () {
+        expect(set.take(0), equals([]));
+        expect(set.take(1), equals(["foo"]));
+        expect(set.take(2), equals(["foo", "bar"]));
+      });
+
+      test(".takeWhile", () {
+        expect(set.takeWhile((element) => element.startsWith("f")),
+            equals(["foo"]));
+        expect(set.takeWhile((element) => element.startsWith("z")), equals([]));
+        expect(set.takeWhile((element) => element is String),
+            equals(["foo", "bar"]));
+      });
+
+      test(".toList", () {
+        expect(set.toList(), equals(["foo", "bar"]));
+        expect(() => set.toList(growable: false).add("baz"),
+            throwsUnsupportedError);
+        expect(set.toList()..add("baz"), equals(["foo", "bar", "baz"]));
+      });
+
+      test(".toSet", () {
+        expect(set.toSet(), equals(new Set.from(["foo", "bar"])));
+      });
+
+      test(".where", () {
+        expect(
+            set.where((element) => element.startsWith("f")), equals(["foo"]));
+        expect(set.where((element) => element.startsWith("z")), equals([]));
+        expect(
+            set.where((element) => element is String), equals(["foo", "bar"]));
+      });
+
+      test(".containsAll", () {
+        expect(set.containsAll(["foo", "bar"]), isTrue);
+        expect(set.containsAll(["foo"]), isTrue);
+        expect(set.containsAll(["foo", "bar", "qux"]), isFalse);
+      });
+
+      test(".difference", () {
+        expect(set.difference(new Set.from(["foo", "baz"])),
+            equals(new Set.from(["bar"])));
+      });
+
+      test(".intersection", () {
+        expect(set.intersection(new Set.from(["foo", "baz"])),
+            equals(new Set.from(["foo"])));
+      });
+
+      test(".union", () {
+        expect(set.union(new Set.from(["foo", "baz"])),
+            equals(new Set.from(["foo", "bar", "baz"])));
+      });
+    });
+  }
+
+  test("Iterable", () {
+    testIterable(new IterableExpector());
+  });
+
+  test("List", () {
+    testList(new ListExpector());
+  });
+
+  test("Set", () {
+    testSet(new SetExpector());
+  });
+
+  test("Queue", () {
+    testQueue(new QueueExpector());
+  });
+
+  test("Map", () {
+    testMap(new MapExpector());
+  });
+
+  group("MapKeySet", () {
+    Map<String, dynamic> map;
+    Set<String> set;
+
+    setUp(() {
+      map = new Map<String, int>();
+      set = new MapKeySet<String>(map);
+    });
+
+    testTwoElementSet(() {
+      map["foo"] = 1;
+      map["bar"] = 2;
+      return set;
+    });
+
+    test(".single", () {
+      expect(() => set.single, throwsStateError);
+      map["foo"] = 1;
+      expect(set.single, equals("foo"));
+      map["bar"] = 1;
+      expect(() => set.single, throwsStateError);
+    });
+
+    test(".toString", () {
+      expect(set.toString(), equals("{}"));
+      map["foo"] = 1;
+      map["bar"] = 2;
+      expect(set.toString(), equals("{foo, bar}"));
+    });
+
+    test(".contains", () {
+      expect(set.contains("foo"), isFalse);
+      map["foo"] = 1;
+      expect(set.contains("foo"), isTrue);
+    });
+
+    test(".isEmpty", () {
+      expect(set.isEmpty, isTrue);
+      map["foo"] = 1;
+      expect(set.isEmpty, isFalse);
+    });
+
+    test(".isNotEmpty", () {
+      expect(set.isNotEmpty, isFalse);
+      map["foo"] = 1;
+      expect(set.isNotEmpty, isTrue);
+    });
+
+    test(".length", () {
+      expect(set, hasLength(0));
+      map["foo"] = 1;
+      expect(set, hasLength(1));
+      map["bar"] = 2;
+      expect(set, hasLength(2));
+    });
+
+    test("is unmodifiable", () {
+      expect(() => set.add("baz"), throwsUnsupportedError);
+      expect(() => set.addAll(["baz", "bang"]), throwsUnsupportedError);
+      expect(() => set.remove("foo"), throwsUnsupportedError);
+      expect(() => set.removeAll(["baz", "bang"]), throwsUnsupportedError);
+      expect(() => set.retainAll(["foo"]), throwsUnsupportedError);
+      expect(() => set.removeWhere((_) => true), throwsUnsupportedError);
+      expect(() => set.retainWhere((_) => true), throwsUnsupportedError);
+      expect(() => set.clear(), throwsUnsupportedError);
+    });
+  });
+
+  group("MapValueSet", () {
+    Map<String, String> map;
+    Set<String> set;
+
+    setUp(() {
+      map = new Map<String, String>();
+      set = new MapValueSet<String, String>(
+          map, (string) => string.substring(0, 1));
+    });
+
+    testTwoElementSet(() {
+      map["f"] = "foo";
+      map["b"] = "bar";
+      return set;
+    });
+
+    test(".single", () {
+      expect(() => set.single, throwsStateError);
+      map["f"] = "foo";
+      expect(set.single, equals("foo"));
+      map["b"] = "bar";
+      expect(() => set.single, throwsStateError);
+    });
+
+    test(".toString", () {
+      expect(set.toString(), equals("{}"));
+      map["f"] = "foo";
+      map["b"] = "bar";
+      expect(set.toString(), equals("{foo, bar}"));
+    });
+
+    test(".contains", () {
+      expect(set.contains("foo"), isFalse);
+      map["f"] = "foo";
+      expect(set.contains("foo"), isTrue);
+      expect(set.contains("fblthp"), isTrue);
+    });
+
+    test(".isEmpty", () {
+      expect(set.isEmpty, isTrue);
+      map["f"] = "foo";
+      expect(set.isEmpty, isFalse);
+    });
+
+    test(".isNotEmpty", () {
+      expect(set.isNotEmpty, isFalse);
+      map["f"] = "foo";
+      expect(set.isNotEmpty, isTrue);
+    });
+
+    test(".length", () {
+      expect(set, hasLength(0));
+      map["f"] = "foo";
+      expect(set, hasLength(1));
+      map["b"] = "bar";
+      expect(set, hasLength(2));
+    });
+
+    test(".lookup", () {
+      map["f"] = "foo";
+      expect(set.lookup("fblthp"), equals("foo"));
+      expect(set.lookup("bar"), isNull);
+    });
+
+    test(".add", () {
+      set.add("foo");
+      set.add("bar");
+      expect(map, equals({"f": "foo", "b": "bar"}));
+    });
+
+    test(".addAll", () {
+      set.addAll(["foo", "bar"]);
+      expect(map, equals({"f": "foo", "b": "bar"}));
+    });
+
+    test(".clear", () {
+      map["f"] = "foo";
+      map["b"] = "bar";
+      set.clear();
+      expect(map, isEmpty);
+    });
+
+    test(".remove", () {
+      map["f"] = "foo";
+      map["b"] = "bar";
+      set.remove("fblthp");
+      expect(map, equals({"b": "bar"}));
+    });
+
+    test(".removeAll", () {
+      map["f"] = "foo";
+      map["b"] = "bar";
+      map["q"] = "qux";
+      set.removeAll(["fblthp", "qux"]);
+      expect(map, equals({"b": "bar"}));
+    });
+
+    test(".removeWhere", () {
+      map["f"] = "foo";
+      map["b"] = "bar";
+      map["q"] = "qoo";
+      set.removeWhere((element) => element.endsWith("o"));
+      expect(map, equals({"b": "bar"}));
+    });
+
+    test(".retainAll", () {
+      map["f"] = "foo";
+      map["b"] = "bar";
+      map["q"] = "qux";
+      set.retainAll(["fblthp", "qux"]);
+      expect(map, equals({"f": "foo", "q": "qux"}));
+    });
+
+    test(".retainAll respects an unusual notion of equality", () {
+      map = new HashMap<String, String>(
+          equals: (value1, value2) =>
+              value1.toLowerCase() == value2.toLowerCase(),
+          hashCode: (value) => value.toLowerCase().hashCode);
+      set = new MapValueSet<String, String>(
+          map, (string) => string.substring(0, 1));
+
+      map["f"] = "foo";
+      map["B"] = "bar";
+      map["Q"] = "qux";
+      set.retainAll(["fblthp", "qux"]);
+      expect(map, equals({"f": "foo", "Q": "qux"}));
+    });
+
+    test(".retainWhere", () {
+      map["f"] = "foo";
+      map["b"] = "bar";
+      map["q"] = "qoo";
+      set.retainWhere((element) => element.endsWith("o"));
+      expect(map, equals({"f": "foo", "q": "qoo"}));
+    });
+  });
+}
diff --git a/packages/intl/CHANGELOG.md b/packages/intl/CHANGELOG.md
index 3dbf1bd..1f733ab 100644
--- a/packages/intl/CHANGELOG.md
+++ b/packages/intl/CHANGELOG.md
@@ -1,3 +1,12 @@
+## 0.15.6
+ * More upper case constant removal.
+
+## 0.15.5
+ * Add type parameters on numberFormatSymbols for Dart 2 compatibility. Note
+   that it only adds them on the right-hand side because adding them to the
+   static type can cause unnecessary cast warnings.
+ * Replace uses of JSON constant for Dart 2 compatibility.
+
 ## 0.15.4
  * A couple of minor Dart 2 fixes.
 
diff --git a/packages/intl/PATENTS b/packages/intl/PATENTS
new file mode 100644
index 0000000..6954196
--- /dev/null
+++ b/packages/intl/PATENTS
@@ -0,0 +1,23 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of the Dart Project.
+
+Google hereby grants to you a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this
+section) patent license to make, have made, use, offer to sell, sell,
+import, transfer, and otherwise run, modify and propagate the contents
+of this implementation of Dart, where such license applies only to
+those patent claims, both currently owned by Google and acquired in
+the future, licensable by Google that are necessarily infringed by
+this implementation of Dart. This grant does not include claims that
+would be infringed only as a consequence of further modification of
+this implementation. If you or your agent or exclusive licensee
+institute or order or agree to the institution of patent litigation
+against any entity (including a cross-claim or counterclaim in a
+lawsuit) alleging that this implementation of Dart or any code
+incorporated within this implementation of Dart constitutes direct or
+contributory patent infringement, or inducement of patent
+infringement, then any patent rights granted to you under this License
+for this implementation of Dart shall terminate as of the date such
+litigation is filed.
diff --git a/packages/intl/README.md b/packages/intl/README.md
index 47e18c3..eaac7cf 100644
--- a/packages/intl/README.md
+++ b/packages/intl/README.md
@@ -143,7 +143,7 @@
         Intl.message(
           "${Intl.plural(howMany,
               zero: 'There are no emails left for $userName.',
-              one: 'There is one email left for $userName.',
+              one: 'There is $howMany email left for $userName.',
               other: 'There are $howMany emails left for $userName.')}",
         name: "remainingEmailsMessage",
         args: [howMany, userName],
@@ -160,7 +160,7 @@
         Intl.plural(
           howMany,
           zero: 'There are no emails left for $userName.',
-          one: 'There is one email left for $userName.',
+          one: 'There is $howMany email left for $userName.',
           other: 'There are $howMany emails left for $userName.',
           name: "remainingEmailsMessage",
           args: [howMany, userName],
diff --git a/packages/intl/lib/intl.dart b/packages/intl/lib/intl.dart
index b1f1de8..1d8e2b8 100644
--- a/packages/intl/lib/intl.dart
+++ b/packages/intl/lib/intl.dart
@@ -55,7 +55,7 @@
 ///
 ///      howManyPeople(numberOfPeople, place) => Intl.plural(
 ///            zero: 'I see no one at all',
-///            one: 'I see one other person',
+///            one: 'I see $numberOfPeople other person',
 ///            other: 'I see $numberOfPeople other people')} in $place.''',
 ///          name: 'msg',
 ///          args: [numberOfPeople, place],
diff --git a/packages/intl/lib/number_symbols_data.dart b/packages/intl/lib/number_symbols_data.dart
index f389263..843ec3e 100644
--- a/packages/intl/lib/number_symbols_data.dart
+++ b/packages/intl/lib/number_symbols_data.dart
@@ -18,7 +18,7 @@
 
 import "number_symbols.dart";
 
-Map numberFormatSymbols = {
+Map numberFormatSymbols = <String, NumberSymbols>{
   // Number formatting symbols for locale af.
   "af": new NumberSymbols(
       NAME: "af",
diff --git a/packages/intl/lib/src/intl/bidi_formatter.dart b/packages/intl/lib/src/intl/bidi_formatter.dart
index 87f955a..c40fd5e 100644
--- a/packages/intl/lib/src/intl/bidi_formatter.dart
+++ b/packages/intl/lib/src/intl/bidi_formatter.dart
@@ -54,7 +54,6 @@
 /// then up to the caller to insert the return value in the output.
 
 class BidiFormatter {
-
   /// The direction of the surrounding text (the context).
   TextDirection contextDirection;
 
@@ -97,7 +96,7 @@
       {bool isHtml: false, bool resetDir: true, TextDirection direction}) {
     if (direction == null) direction = estimateDirection(text, isHtml: isHtml);
     var result;
-    if (!isHtml) text = HTML_ESCAPE.convert(text);
+    if (!isHtml) text = const HtmlEscape().convert(text);
     var directionChange = contextDirection.isDirectionChange(direction);
     if (_alwaysSpan || directionChange) {
       var spanDirection = '';
diff --git a/packages/intl/lib/src/intl/number_format.dart b/packages/intl/lib/src/intl/number_format.dart
index ffcad51..75c0a6c 100644
--- a/packages/intl/lib/src/intl/number_format.dart
+++ b/packages/intl/lib/src/intl/number_format.dart
@@ -80,6 +80,8 @@
   int minimumExponentDigits = 0;
   int _significantDigits = 0;
 
+  static final _ln10 = log(10);
+
   ///  How many significant digits should we print.
   ///
   ///  Note that if significantDigitsInUse is the default false, this
@@ -97,7 +99,7 @@
   int get _multiplier => _internalMultiplier;
   set _multiplier(int x) {
     _internalMultiplier = x;
-    _multiplierDigits = (log(_multiplier) / LN10).round();
+    _multiplierDigits = (log(_multiplier) / _ln10).round();
   }
 
   int _internalMultiplier = 1;
@@ -602,7 +604,7 @@
       return;
     }
 
-    var exponent = (log(number) / LN10).floor();
+    var exponent = (log(number) / _ln10).floor();
     var mantissa = number / pow(10.0, exponent);
 
     if (maximumIntegerDigits > 1 &&
@@ -708,7 +710,7 @@
     if (simpleNumber < 10000000000000000) return 16;
     // We're past the point where being off by one on the number of digits
     // will affect the pattern, so now we can use logs.
-    return max(1, (log(simpleNumber) / LN10).ceil());
+    return max(1, (log(simpleNumber) / _ln10).ceil());
   }
 
   int _fractionDigitsAfter(int remainingSignificantDigits) =>
@@ -806,7 +808,7 @@
     // so pad out the rest of it with zeros.
     var paddingDigits = '';
     if (integerPart is num && integerPart > _maxInt) {
-      var howManyDigitsTooBig = (log(integerPart) / LN10).ceil() - _maxDigits;
+      var howManyDigitsTooBig = (log(integerPart) / _ln10).ceil() - _maxDigits;
       num divisor = pow(10, howManyDigitsTooBig).round();
       // pow() produces 0 if the result is too large for a 64-bit int.
       // If that happens, use a floating point divisor instead.
@@ -1149,12 +1151,12 @@
   /// Parse [text] and return the resulting number. Throws [FormatException]
   /// if we can't parse it.
   num parse() {
-    if (text == symbols.NAN) return double.NAN;
+    if (text == symbols.NAN) return (0.0 / 0.0);
     if (text == "$_positivePrefix${symbols.INFINITY}$_positiveSuffix") {
-      return double.INFINITY;
+      return 1.0 / 0.0;
     }
     if (text == "$_negativePrefix${symbols.INFINITY}$_negativeSuffix") {
-      return double.NEGATIVE_INFINITY;
+      return -1.0 / 0.0;
     }
 
     checkPrefixes();
diff --git a/packages/intl/lib/src/lazy_locale_data.dart b/packages/intl/lib/src/lazy_locale_data.dart
index 07d46f7..b975a4e 100644
--- a/packages/intl/lib/src/lazy_locale_data.dart
+++ b/packages/intl/lib/src/lazy_locale_data.dart
@@ -35,6 +35,8 @@
   /// The set of available locales.
   Set availableLocaleSet;
 
+  static const jsonDecoder = const JsonCodec();
+
   /// The constructor. The [_reader] specifies where the data comes
   /// from. The [_creationFunction] creates the appropriate data type
   /// from the remote data (which typically comes in as a Map). The
@@ -90,6 +92,6 @@
   /// Given a Future [input] whose value is expected to be a string in JSON
   /// form, return another future that parses the JSON into a usable format.
   Future jsonData(Future input) {
-    return input.then((response) => JSON.decode(response));
+    return input.then((response) => jsonDecoder.decode(response));
   }
 }
diff --git a/packages/intl/pubspec.yaml b/packages/intl/pubspec.yaml
index fd661ca..c065a1a 100644
--- a/packages/intl/pubspec.yaml
+++ b/packages/intl/pubspec.yaml
@@ -1,5 +1,5 @@
 name: intl
-version: 0.15.4
+version: 0.15.6
 author: Dart Team <misc@dartlang.org>
 description: Contains code to deal with internationalized/localized messages, date and number formatting and parsing, bi-directional text, and other internationalization issues.
 homepage: https://github.com/dart-lang/intl
diff --git a/packages/intl/test/number_format_test.dart b/packages/intl/test/number_format_test.dart
index 8c1d61f..1c1339d 100644
--- a/packages/intl/test/number_format_test.dart
+++ b/packages/intl/test/number_format_test.dart
@@ -8,7 +8,6 @@
 import 'package:intl/number_symbols_data.dart';
 import 'package:intl/intl.dart';
 import 'number_test_data.dart';
-import 'dart:math';
 
 /// Tests the Numeric formatting library in dart.
 var testNumbersWeCanReadBack = {
@@ -29,14 +28,14 @@
   "1,234": 1234.0,
   "1.234": 1.234,
   "1.23": 1.230,
-  "NaN": double.NAN,
-  "∞": double.INFINITY,
-  "-∞": double.NEGATIVE_INFINITY,
+  "NaN": 0.0 / 0.0,
+  "∞": 1.0 / 0.0,
+  "-∞": -1.0 / 0.0,
 };
 
 /// Test numbers that we can't parse because we lose precision in formatting.
 var testNumbersWeCannotReadBack = {
-  "3.142": PI,
+  "3.142": 3.1415926535897932,
   "-1.234": -1.2342,
   "-1.235": -1.2348,
   "1.234": 1.2342,
@@ -45,11 +44,8 @@
 
 /// Test numbers that won't work in Javascript because they're too big.
 var testNumbersOnlyForTheVM = {
-  "9,000,000,000,000,000,000":
-      9000000000000000000,
-  "9,223,372,036,854,775,807":
-      9223372036854775807
-
+  "9,000,000,000,000,000,000": 9000000000000000000,
+  "9,223,372,036,854,775,807": 9223372036854775807
 };
 
 get allTestNumbers => new Map.from(testNumbersWeCanReadBack)
diff --git a/packages/intl/test/timezone_test_core.dart b/packages/intl/test/timezone_test_core.dart
index 435f2a7..69dff06 100644
--- a/packages/intl/test/timezone_test_core.dart
+++ b/packages/intl/test/timezone_test_core.dart
@@ -40,8 +40,8 @@
       environment['EXPECTED_TZ_OFFSET_FOR_TEST'] = '$expectedUtcOffset';
     }
     var result = await Process.run(dart, args,
-        stdoutEncoding: UTF8,
-        stderrEncoding: UTF8,
+        stdoutEncoding: new Utf8Codec(),
+        stderrEncoding: new Utf8Codec(),
         includeParentEnvironment: true,
         environment: environment);
     // Because the actual tests are run in a spawned parocess their output isn't
diff --git a/packages/intl/tool/generate_locale_data_files.dart b/packages/intl/tool/generate_locale_data_files.dart
index 2fde04b..498d405 100644
--- a/packages/intl/tool/generate_locale_data_files.dart
+++ b/packages/intl/tool/generate_locale_data_files.dart
@@ -67,10 +67,10 @@
 void writePatterns(locale, patterns) {
   var file = new File(path.join(dataDirectory, 'patterns', '${locale}.json'));
   file.openWrite()
-    ..write(JSON.encode(patterns))
+    ..write(new JsonCodec().encode(patterns))
     ..close();
 }
 
 void writeToJSON(dynamic data, IOSink out) {
-  out.write(JSON.encode(data.serializeToMap()));
+  out.write(new JsonCodec().encode(data.serializeToMap()));
 }
diff --git a/packages/matcher/.travis.yml b/packages/matcher/.travis.yml
index 61ef6f5..aa48444 100644
--- a/packages/matcher/.travis.yml
+++ b/packages/matcher/.travis.yml
@@ -1,14 +1,13 @@
 language: dart
-sudo: false
+
 dart:
   - stable
   - dev
+
 dart_task:
   - test: -p vm
     xvfb: false
   - test: -p firefox
-  - test: -p dartium
-    install_dartium: true
   - dartanalyzer
 
 matrix:
diff --git a/packages/matcher/CHANGELOG.md b/packages/matcher/CHANGELOG.md
index d6fe1c6..a5ebcfd 100644
--- a/packages/matcher/CHANGELOG.md
+++ b/packages/matcher/CHANGELOG.md
@@ -1,3 +1,13 @@
+## 0.12.2
+
+* Fixed `unorderedMatches` in cases where the matchers may match more than one
+  element and order of the elements doesn't line up with the order of the
+  matchers.
+
+* Add containsAll matcher for Iterables. This Matcher checks that all
+  values/matchers in an expected iterable are satisfied by an element in the
+  value without allowing the same value to satisfy multiple matchers.
+
 ## 0.12.1+4
 
 * Fixed SDK constraint to allow edge builds.
diff --git a/packages/matcher/lib/src/iterable_matchers.dart b/packages/matcher/lib/src/iterable_matchers.dart
index 759c252..a4a122c 100644
--- a/packages/matcher/lib/src/iterable_matchers.dart
+++ b/packages/matcher/lib/src/iterable_matchers.dart
@@ -111,7 +111,8 @@
 /// Returns a matcher which matches [Iterable]s that have the same length and
 /// the same elements as [expected], but not necessarily in the same order.
 ///
-/// Note that this is O(n^2) so should only be used on small objects.
+/// Note that this is worst case O(n^2) runtime and memory usage so it should
+/// only be used on small iterables.
 Matcher unorderedEquals(Iterable expected) => new _UnorderedEquals(expected);
 
 class _UnorderedEquals extends _UnorderedMatches {
@@ -145,55 +146,63 @@
 /// Returns a matcher which matches [Iterable]s whose elements match the
 /// matchers in [expected], but not necessarily in the same order.
 ///
-///  Note that this is `O(n^2)` and so should only be used on small objects.
+/// Note that this is worst case O(n^2) runtime and memory usage so it should
+/// only be used on small iterables.
 Matcher unorderedMatches(Iterable expected) => new _UnorderedMatches(expected);
 
 class _UnorderedMatches extends Matcher {
   final List<Matcher> _expected;
+  final bool _allowUnmatchedValues;
 
-  _UnorderedMatches(Iterable expected)
-      : _expected = expected.map(wrapMatcher).toList();
+  _UnorderedMatches(Iterable expected, {bool allowUnmatchedValues})
+      : _expected = expected.map(wrapMatcher).toList(),
+        _allowUnmatchedValues = allowUnmatchedValues ?? false;
 
   String _test(item) {
-    if (item is Iterable) {
-      var list = item.toList();
+    if (item is! Iterable) return 'not iterable';
 
-      // Check the lengths are the same.
-      if (_expected.length > list.length) {
-        return 'has too few elements (${list.length} < ${_expected.length})';
-      } else if (_expected.length < list.length) {
-        return 'has too many elements (${list.length} > ${_expected.length})';
-      }
+    var values = item.toList();
 
-      var matched = new List<bool>.filled(list.length, false);
-      var expectedPosition = 0;
-      for (var expectedMatcher in _expected) {
-        var actualPosition = 0;
-        var gotMatch = false;
-        for (var actualElement in list) {
-          if (!matched[actualPosition]) {
-            if (expectedMatcher.matches(actualElement, {})) {
-              matched[actualPosition] = gotMatch = true;
-              break;
-            }
-          }
-          ++actualPosition;
-        }
-
-        if (!gotMatch) {
-          return new StringDescription()
-              .add('has no match for ')
-              .addDescriptionOf(expectedMatcher)
-              .add(' at index $expectedPosition')
-              .toString();
-        }
-
-        ++expectedPosition;
-      }
-      return null;
-    } else {
-      return 'not iterable';
+    // Check the lengths are the same.
+    if (_expected.length > values.length) {
+      return 'has too few elements (${values.length} < ${_expected.length})';
+    } else if (!_allowUnmatchedValues && _expected.length < values.length) {
+      return 'has too many elements (${values.length} > ${_expected.length})';
     }
+
+    var edges =
+        new List.generate(values.length, (_) => <int>[], growable: false);
+    for (int v = 0; v < values.length; v++) {
+      for (int m = 0; m < _expected.length; m++) {
+        if (_expected[m].matches(values[v], {})) {
+          edges[v].add(m);
+        }
+      }
+    }
+    // The index into `values` matched with each matcher or `null` if no value
+    // has been matched yet.
+    var matched = new List<int>(_expected.length);
+    for (int valueIndex = 0; valueIndex < values.length; valueIndex++) {
+      _findPairing(edges, valueIndex, matched);
+    }
+    for (int matcherIndex = 0;
+        matcherIndex < _expected.length;
+        matcherIndex++) {
+      if (matched[matcherIndex] == null) {
+        final description = new StringDescription()
+            .add('has no match for ')
+            .addDescriptionOf(_expected[matcherIndex])
+            .add(' at index $matcherIndex');
+        final remainingUnmatched =
+            matched.sublist(matcherIndex + 1).where((m) => m == null).length;
+        return remainingUnmatched == 0
+            ? description.toString()
+            : description
+                .add(' along with $remainingUnmatched other unmatched')
+                .toString();
+      }
+    }
+    return null;
   }
 
   bool matches(item, Map mismatchState) => _test(item) == null;
@@ -206,6 +215,31 @@
   Description describeMismatch(item, Description mismatchDescription,
           Map matchState, bool verbose) =>
       mismatchDescription.add(_test(item));
+
+  /// Returns [true] if the value at [valueIndex] can be paired with some
+  /// unmatched matcher and updates the state of [matched].
+  ///
+  /// If there is a conflic where multiple values may match the same matcher
+  /// recursively looks for a new place to match the old value. [reserved]
+  /// tracks the matchers that have been used _during_ this search.
+  bool _findPairing(List<List<int>> edges, int valueIndex, List<int> matched,
+      [Set<int> reserved]) {
+    reserved ??= new Set<int>();
+    final possiblePairings =
+        edges[valueIndex].where((m) => !reserved.contains(m));
+    for (final matcherIndex in possiblePairings) {
+      reserved.add(matcherIndex);
+      final previouslyMatched = matched[matcherIndex];
+      if (previouslyMatched == null ||
+          // If the matcher isn't already free, check whether the existing value
+          // occupying the matcher can be bumped to another one.
+          _findPairing(edges, matched[matcherIndex], matched, reserved)) {
+        matched[matcherIndex] = valueIndex;
+        return true;
+      }
+    }
+    return false;
+  }
 }
 
 /// A pairwise matcher for [Iterable]s.
@@ -268,11 +302,43 @@
 }
 
 /// Matches [Iterable]s which contain an element matching every value in
+/// [expected] in any order, and may contain additional values.
+///
+/// For example: `[0, 1, 0, 2, 0]` matches `containsAll([1, 2])` and
+/// `containsAll([2, 1])` but not `containsAll([1, 2, 3])`.
+///
+/// Will only match values which implement [Iterable].
+///
+/// Each element in the value will only be considered a match for a single
+/// matcher in [expected] even if it could satisfy more than one. For instance
+/// `containsAll([greaterThan(1), greaterThan(2)])` will not be satisfied by
+/// `[3]`. To check that all matchers are satisfied within an iterable and allow
+/// the same element to satisfy multiple matchers use
+/// `allOf(matchers.map(contains))`.
+///
+/// Note that this is worst case O(n^2) runtime and memory usage so it should
+/// only be used on small iterables.
+Matcher containsAll(Iterable expected) => new _ContainsAll(expected);
+
+class _ContainsAll extends _UnorderedMatches {
+  final Iterable _unwrappedExpected;
+
+  _ContainsAll(Iterable expected)
+      : _unwrappedExpected = expected,
+        super(expected.map(wrapMatcher), allowUnmatchedValues: true);
+  @override
+  Description describe(Description description) =>
+      description.add('contains all of ').addDescriptionOf(_unwrappedExpected);
+}
+
+/// Matches [Iterable]s which contain an element matching every value in
 /// [expected] in the same order, but may contain additional values interleaved
 /// throughout.
 ///
 /// For example: `[0, 1, 0, 2, 0]` matches `containsAllInOrder([1, 2])` but not
 /// `containsAllInOrder([2, 1])` or `containsAllInOrder([1, 2, 3])`.
+///
+/// Will only match values which implement [Iterable].
 Matcher containsAllInOrder(Iterable expected) =>
     new _ContainsAllInOrder(expected);
 
diff --git a/packages/matcher/pubspec.yaml b/packages/matcher/pubspec.yaml
index 0c5468e..c898554 100644
--- a/packages/matcher/pubspec.yaml
+++ b/packages/matcher/pubspec.yaml
@@ -1,5 +1,5 @@
 name: matcher
-version: 0.12.1+4
+version: 0.12.2
 author: Dart Team <misc@dartlang.org>
 description: Support for specifying test expectations
 homepage: https://github.com/dart-lang/matcher
@@ -9,3 +9,6 @@
   stack_trace: '^1.2.0'
 dev_dependencies:
   test: '>=0.12.0 <0.13.0'
+
+dependency_overrides:
+  test: ^0.12.0
diff --git a/packages/matcher/test/iterable_matchers_test.dart b/packages/matcher/test/iterable_matchers_test.dart
index 604a1b6..e8a8df8 100644
--- a/packages/matcher/test/iterable_matchers_test.dart
+++ b/packages/matcher/test/iterable_matchers_test.dart
@@ -144,12 +144,34 @@
         "Expected: equals [3, 1] unordered "
         "Actual: [1, 2] "
         "Which: has no match for <3> at index 0");
+    shouldFail(
+        d,
+        unorderedEquals([3, 4]),
+        "Expected: equals [3, 4] unordered "
+        "Actual: [1, 2] "
+        "Which: has no match for <3> at index 0"
+        " along with 1 other unmatched");
   });
 
-  test('unorderedMatchess', () {
+  test('unorderedMatches', () {
     var d = [1, 2];
     shouldPass(d, unorderedMatches([2, 1]));
     shouldPass(d, unorderedMatches([greaterThan(1), greaterThan(0)]));
+    shouldPass(d, unorderedMatches([greaterThan(0), greaterThan(1)]));
+    shouldPass([2, 1], unorderedMatches([greaterThan(1), greaterThan(0)]));
+
+    shouldPass([2, 1], unorderedMatches([greaterThan(0), greaterThan(1)]));
+    // Excersize the case where pairings should get "bumped" multiple times
+    shouldPass(
+        [0, 1, 2, 3, 5, 6],
+        unorderedMatches([
+          greaterThan(1), // 6
+          equals(2), // 2
+          allOf([lessThan(3), isNot(0)]), // 1
+          equals(0), // 0
+          predicate((v) => v % 2 == 1), // 3
+          equals(5), // 5
+        ]));
     shouldFail(
         d,
         unorderedMatches([greaterThan(0)]),
@@ -177,6 +199,33 @@
         "Which: has no match for a value greater than <3> at index 0");
   });
 
+  test('containsAll', () {
+    var d = [0, 1, 2];
+    shouldPass(d, containsAll([1, 2]));
+    shouldPass(d, containsAll([2, 1]));
+    shouldPass(d, containsAll([greaterThan(0), greaterThan(1)]));
+    shouldPass([2, 1], containsAll([greaterThan(0), greaterThan(1)]));
+    shouldFail(
+        d,
+        containsAll([1, 2, 3]),
+        "Expected: contains all of [1, 2, 3] "
+        "Actual: [0, 1, 2] "
+        "Which: has no match for <3> at index 2");
+    shouldFail(
+        1,
+        containsAll([1]),
+        "Expected: contains all of [1] "
+        "Actual: <1> "
+        "Which: not iterable");
+    shouldFail(
+        [-1, 2],
+        containsAll([greaterThan(0), greaterThan(1)]),
+        "Expected: contains all of [<a value greater than <0>>, "
+        "<a value greater than <1>>] "
+        "Actual: [-1, 2] "
+        "Which: has no match for a value greater than <1> at index 1");
+  });
+
   test('containsAllInOrder', () {
     var d = [0, 1, 0, 2];
     shouldPass(d, containsAllInOrder([1, 2]));
diff --git a/packages/matcher/test/mirror_matchers_test.dart b/packages/matcher/test/mirror_matchers_test.dart
index 729768e..bbd80a1 100644
--- a/packages/matcher/test/mirror_matchers_test.dart
+++ b/packages/matcher/test/mirror_matchers_test.dart
@@ -2,6 +2,8 @@
 // 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.
 
+@TestOn('vm')
+
 import 'package:matcher/mirror_matchers.dart';
 import 'package:test/test.dart';
 
diff --git a/packages/meta/CHANGELOG.md b/packages/meta/CHANGELOG.md
index 3f893d4..ed639d0 100644
--- a/packages/meta/CHANGELOG.md
+++ b/packages/meta/CHANGELOG.md
@@ -1,11 +1,22 @@
+## 1.1.5
+
+* Introduce @isTest and @isTestGroup to declare a function that is a
+  test, or a test group.
+
+## 1.1.4
+
+* Added dart2js.dart.
+
 ## 1.1.2
 
 * Rollback SDK constraint update for 2.0.0. No longer needed.
 
 ## 1.1.1
+
 * Update SDK constraint to be 2.0.0 dev friendly.
 
 ## 1.1.0
+
 * Introduce `@alwaysThrows` to declare that a function always throws
     (SDK issue [17999](https://github.com/dart-lang/sdk/issues/17999)). This
     is first available in Dart SDK 1.25.0-dev.1.0.
@@ -28,7 +39,17 @@
     }
     ```
 
+## 1.0.5
+
+* Introduce `@experimental` to annotate a library, or any declaration that is
+  part of the public interface of a library (such as top-level members, class
+  members, and function parameters) to indicate that the annotated API is
+  experimental and may be removed or changed at any-time without updating the
+  version of the containing package, despite the fact that it would otherwise
+  be a breaking change.
+
 ## 1.0.4
+
 * Introduce `@virtual` to allow field overrides in strong mode
     (SDK issue [27384](https://github.com/dart-lang/sdk/issues/27384)).
 
@@ -47,6 +68,7 @@
     ```
 
 ## 1.0.3
+
 * Introduce `@checked` to override a method and tighten a parameter
     type (SDK issue [25578](https://github.com/dart-lang/sdk/issues/25578)).
 
@@ -67,34 +89,42 @@
     ```
 
 ## 1.0.2
+
 * Introduce `@visibleForTesting` annotation for declarations that may be referenced only in the library or in a test.
 
 ## 1.0.1
+
 * Updated `@factory` to allow statics and methods returning `null`.
 
 ## 1.0.0
+
 * First stable API release.
 
 ## 0.12.2
+
 * Updated `@protected` to include implemented interfaces (linter#252).
 
 ## 0.12.1
+
 * Fixed markdown in dartdocs.
 
 ## 0.12.0
+
 * Introduce `@optionalTypeArgs` annotation for classes whose type arguments are to be treated as optional.
 
 ## 0.11.0
+
 * Added new `Required` constructor with a means to specify a reason to explain why a parameter is required.
 
 ## 0.10.0
-* Introduce `@factory` annotation for methods that must either be abstract or
-must return a newly allocated object.
+
+* Introduce `@factory` annotation for methods that must either be abstract or must return a newly allocated object.
 * Introduce `@literal` annotation that indicates that any invocation of a
 constructor must use the keyword `const` unless one or more of the
 arguments to the constructor is not a compile-time constant.
 
 ## 0.9.0
+
 * Introduce `@protected` annotation for members that must only be called from
 instance members of subclasses.
 * Introduce `@required` annotation for optional parameters that should be treated
diff --git a/packages/meta/README.md b/packages/meta/README.md
new file mode 100644
index 0000000..c6c710d
--- /dev/null
+++ b/packages/meta/README.md
@@ -0,0 +1,34 @@
+# Annotations for Static Analysis
+
+This package defines annotations that can be used by the tools that are shipped
+with the Dart SDK.
+
+## Library Structure
+
+The annotations in this package are defined in two libraries.
+
+The library in `meta.dart` defines annotations that can be used by static
+analysis tools to provide a more complete analysis of the code that uses them.
+Within the SDK, these tools include the command-line analyzer (`dartanalyzer`)
+and the analysis server that is used to power many of the Dart-enabled
+development tools.
+
+The library in `dart2js.dart` defines annotations that provide hints to dart2js
+to improve the quality of the JavaScript code that it produces. These
+annotations are currently experimental and might be removed in a future version
+of this package.
+
+## Support
+
+Post issues and feature requests on the GitHub [issue tracker][issues].
+
+Questions and discussions are welcome at the
+[Dart Analyzer Discussion Group][list].
+
+## License
+
+See the [LICENSE][license] file.
+
+[issues]: https://github.com/dart-lang/sdk/issues
+[license]: https://github.com/dart-lang/sdk/blob/master/pkg/analyzer/LICENSE
+[list]: https://groups.google.com/a/dartlang.org/forum/#!forum/analyzer-discuss
diff --git a/packages/meta/lib/dart2js.dart b/packages/meta/lib/dart2js.dart
new file mode 100644
index 0000000..4dc1ba7
--- /dev/null
+++ b/packages/meta/lib/dart2js.dart
@@ -0,0 +1,45 @@
+// Copyright (c) 2017, 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.
+
+/// Constants for use in metadata annotations to provide hints to dart2js.
+///
+/// This is an experimental feature and not expected to be useful except for low
+/// level framework authors.
+///
+/// Added at sdk version 2.0.0-dev.6.0
+library meta_dart2js;
+
+/// An annotation for methods to request that dart2js does not inline the
+/// method.
+///
+///     import 'package:meta/dart2js.dart' as dart2js;
+///
+///     @dart2js.noInline
+///     String text() => 'A String of unusual size';
+const _NoInline noInline = const _NoInline();
+
+/// An annotation for methods method to request that dart2js always inlines the
+/// method.
+///
+/// dart2js will attempt to inline the method regardless of its size. Even with
+/// this annotation, there are conditions that can prevent dart2js from inlining
+/// a method, including complex control flow.
+///
+///     import 'package:meta/dart2js.dart' as dart2js;
+///
+///     @dart2js.tryInline
+///     String bigMethod() {
+///       for (int i in "Hello".runes) print(i);
+///     }
+///
+/// It is an error to use both `@noInline` and `@tryInline` on the same method.
+const _TryInline tryInline = const _TryInline();
+
+class _NoInline {
+  const _NoInline();
+}
+
+class _TryInline {
+  const _TryInline();
+}
diff --git a/packages/meta/lib/meta.dart b/packages/meta/lib/meta.dart
index 8dd0927..ea23c35 100644
--- a/packages/meta/lib/meta.dart
+++ b/packages/meta/lib/meta.dart
@@ -54,6 +54,9 @@
 /// Indicates that this parameter may have a tighter type than the parameter on
 /// its superclass. The actual argument will be checked at runtime to ensure it
 /// is a subtype of the overridden parameter type.
+///
+/// DEPRECATED: Use the `covariant` modifier instead.
+@deprecated
 const _Checked checked = const _Checked();
 
 /// Used to annotate a library, or any declaration that is part of the public
@@ -102,6 +105,22 @@
 ///   class that has this annotation is not immutable.
 const Immutable immutable = const Immutable();
 
+/// Used to annotate a test framework function that runs a single test.
+///
+/// Tools, such as IDEs, can show invocations of such function in a file
+/// structure view to help the user navigating in large test files.
+///
+/// The first parameter of the function must be the description of the test.
+const _IsTest isTest = const _IsTest();
+
+/// Used to annotate a test framework function that runs a group of tests.
+///
+/// Tools, such as IDEs, can show invocations of such function in a file
+/// structure view to help the user navigating in large test files.
+///
+/// The first parameter of the function must be the description of the group.
+const _IsTestGroup isTestGroup = const _IsTestGroup();
+
 /// Used to annotate a const constructor `c`. Indicates that any invocation of
 /// the constructor must use the keyword `const` unless one or more of the
 /// arguments to the constructor is not a compile-time constant.
@@ -241,6 +260,14 @@
   const _Factory();
 }
 
+class _IsTest {
+  const _IsTest();
+}
+
+class _IsTestGroup {
+  const _IsTestGroup();
+}
+
 class _Literal {
   const _Literal();
 }
diff --git a/packages/meta/pubspec.yaml b/packages/meta/pubspec.yaml
index c46d798..73e1dfd 100644
--- a/packages/meta/pubspec.yaml
+++ b/packages/meta/pubspec.yaml
@@ -1,5 +1,5 @@
 name: meta
-version: 1.1.2
+version: 1.1.5
 author: Dart Team <misc@dartlang.org>
 homepage: https://github.com/dart-lang/sdk/tree/master/pkg/meta
 description: >
diff --git a/packages/usage/.analysis_options b/packages/usage/.analysis_options
deleted file mode 100644
index a10d4c5..0000000
--- a/packages/usage/.analysis_options
+++ /dev/null
@@ -1,2 +0,0 @@
-analyzer:
-  strong-mode: true
diff --git a/packages/usage/.gitignore b/packages/usage/.gitignore
index f8192d2..a9df6e6 100644
--- a/packages/usage/.gitignore
+++ b/packages/usage/.gitignore
@@ -1,8 +1,6 @@
+.packages
+.idea/
+.pub/
 build/
 doc/api/
-.packages
-packages
-.buildlog
 pubspec.lock
-.settings/
-.pub/
diff --git a/packages/usage/.project b/packages/usage/.project
deleted file mode 100644
index 5e76d1e..0000000
--- a/packages/usage/.project
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>usage</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>com.google.dart.tools.core.dartBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>com.google.dart.tools.core.dartNature</nature>
-	</natures>
-	<filteredResources>
-		<filter>
-			<id>1418770673943</id>
-			<name></name>
-			<type>30</type>
-			<matcher>
-				<id>com.google.dart.tools.core.packagesFolderMatcher</id>
-			</matcher>
-		</filter>
-	</filteredResources>
-</projectDescription>
diff --git a/packages/usage/.travis.yml b/packages/usage/.travis.yml
index 75b3aac..96ba4fc 100644
--- a/packages/usage/.travis.yml
+++ b/packages/usage/.travis.yml
@@ -1,12 +1,4 @@
 language: dart
-dart:
-  - stable
-  - dev
+dart: dev
 sudo: false
-
-# before_install:
-#   - "export CHROME_ARGS=--no-sandbox"
-#   - "export DISPLAY=:99.0"
-#   - "sh -e /etc/init.d/xvfb start"
-
 script: ./tool/travis.sh
diff --git a/packages/usage/analysis_options.yaml b/packages/usage/analysis_options.yaml
new file mode 100644
index 0000000..2ef22c9
--- /dev/null
+++ b/packages/usage/analysis_options.yaml
@@ -0,0 +1,8 @@
+analyzer:
+  strong-mode: true
+linter:
+  rules:
+    - annotate_overrides
+    - directives_ordering
+    - empty_constructor_bodies
+    - empty_statements
diff --git a/packages/usage/changelog.md b/packages/usage/changelog.md
index 858df79..351cb38 100644
--- a/packages/usage/changelog.md
+++ b/packages/usage/changelog.md
@@ -1,10 +1,67 @@
 # Changelog
 
+## 3.3.0
+- added a `close()` method to the `Analytics` class
+- change our minimum SDK from `1.24.0-dev` to `1.24.0` stable
+
+## 3.2.0
+- expose the `Analytics.applicationName` and `Analytics.applicationVersion`
+  properties
+- make it easier for clients to extend the `AnalyticsIO` class
+- allow for custom parameters when sending a screenView
+
+## 3.1.1
+- make Analytics.clientId available immediately
+
+## 3.1.0
+- switch the technique we use to determine the locale to the new dart:io
+  `Platform.localeName` field
+- change our minimum SDK version to `1.24.0`
+
+## 3.0.1
+- expose the `Analytics.clientId` field
+
+## 3.0.0+1
+- fixed an NPE in the `usage_io` `getPlatformLocale()` method
+
+## 3.0.0
+- removed the use of configurable imports
+- removed the Flutter specific entry-point; Flutter apps can now use the
+  regular `dart:io` entrypoint (AnalyticsIO)
+- moved the uuid library from `lib/src/` to `lib/uuid/`
+- fixed an issue with reporting the user language for the dart:io provider
+- changed to send additional lines for reported exceptions
+
+## 2.2.2
+- adjust the Flutter usage client to Flutter API changes
+
+## 2.2.1
+- improve the user agent string for the CLI client
+
+## 2.2.0+1
+- bug fix to prevent frequently changing the settings file
+
+## 2.2.0
+- added `Analytics.firstRun`
+- added `Analytics.enabled`
+- removed `Analytics.optIn`
+
+## 2.1.0
+- added `Analytics.getSessionValue()`
+- added `Analytics.onSend`
+- added `AnalyticsImpl.sendRaw()`
+
+## 2.0.0
+- added a `usage` implementation for Flutter (uses conditional directives)
+- removed `lib/usage_html.dart`; use the new Analytics.create() static method
+- removed `lib/usage_io.dart`; use the new Analytics.create() static method
+- bumped to `2.0.0` for API changes and library refactorings
+
 ## 1.2.0
 - added an optional `analyticsUrl` parameter to the usage constructors
 
 ## 1.1.0
-- fix two strong mode analysis issues (overrridding a field declaration with a
+- fix two strong mode analysis issues (overriding a field declaration with a
   setter/getter pair)
 
 ## 1.0.1
diff --git a/packages/usage/example/example.dart b/packages/usage/example/example.dart
index 9d9342c..06578c3 100644
--- a/packages/usage/example/example.dart
+++ b/packages/usage/example/example.dart
@@ -2,9 +2,7 @@
 // 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 simple web app to hand-test the usage library.
- */
+/// A simple web app to hand-test the usage library.
 library usage_example;
 
 import 'dart:html';
@@ -16,9 +14,9 @@
 int _count = 0;
 
 void main() {
-  querySelector('#foo').onClick.listen((_) => _handleFoo(getAnalytics()));
-  querySelector('#bar').onClick.listen((_) => _handleBar(getAnalytics()));
-  querySelector('#page').onClick.listen((_) => _changePage(getAnalytics()));
+  querySelector('#foo').onClick.listen((_) => _handleFoo());
+  querySelector('#bar').onClick.listen((_) => _handleBar());
+  querySelector('#page').onClick.listen((_) => _changePage());
 }
 
 String _ua() => (querySelector('#ua') as InputElement).value.trim();
@@ -27,22 +25,24 @@
   if (_analytics == null || _lastUa != _ua()) {
     _lastUa = _ua();
     _analytics = new AnalyticsHtml(_lastUa, 'Test app', '1.0');
-    _analytics.optIn = true;
     _analytics.sendScreenView(window.location.pathname);
   }
 
   return _analytics;
 }
 
-void _handleFoo(Analytics analytics) {
+void _handleFoo() {
+  Analytics analytics = getAnalytics();
   analytics.sendEvent('main', 'foo');
 }
 
-void _handleBar(Analytics analytics) {
+void _handleBar() {
+  Analytics analytics = getAnalytics();
   analytics.sendEvent('main', 'bar');
 }
 
-void _changePage(Analytics analytics) {
+void _changePage() {
+  Analytics analytics = getAnalytics();
   window.history.pushState(null, 'new page', '${++_count}.html');
   analytics.sendScreenView(window.location.pathname);
 }
diff --git a/packages/usage/example/ga.dart b/packages/usage/example/ga.dart
index dcd9110..ad64249 100644
--- a/packages/usage/example/ga.dart
+++ b/packages/usage/example/ga.dart
@@ -2,14 +2,12 @@
 // 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 simple command-line app to hand-test the usage library.
- */
+/// A simple command-line app to hand-test the usage library.
 library usage_ga;
 
 import 'package:usage/usage_io.dart';
 
-void main(List args) {
+main(List args) async {
   final String DEFAULT_UA = 'UA-55029513-1';
 
   if (args.isEmpty) {
@@ -21,18 +19,17 @@
 
   String ua = args.isEmpty ? DEFAULT_UA : args.first;
 
-  Analytics ga = new AnalyticsIO(ua, 'ga_test', '1.0');
-  ga.optIn = true;
+  Analytics ga = new AnalyticsIO(ua, 'ga_test', '3.0');
 
-  ga.sendScreenView('home').then((_) {
-    return ga.sendScreenView('files');
-  }).then((_) {
-    return ga.sendException('foo exception, line 123:56');
-  }).then((_) {
-    return ga.sendTiming('writeDuration', 123);
-  }).then((_) {
-    return ga.sendEvent('create', 'consoleapp', label: 'Console App');
-  }).then((_) {
-    print('pinged ${ua}');
-  });
+  await ga.sendScreenView('home');
+  await ga.sendScreenView('files');
+  await ga
+      .sendException('foo error:\n${sanitizeStacktrace(StackTrace.current)}');
+  await ga.sendTiming('writeDuration', 123);
+  await ga.sendEvent('create', 'consoleapp', label: 'Console App');
+  print('pinged ${ua}');
+
+  await ga.waitForLastPing();
+
+  ga.close();
 }
diff --git a/packages/usage/lib/src/usage_impl.dart b/packages/usage/lib/src/usage_impl.dart
index 396f37a..df529e3 100644
--- a/packages/usage/lib/src/usage_impl.dart
+++ b/packages/usage/lib/src/usage_impl.dart
@@ -2,15 +2,11 @@
 // 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 usage_impl;
-
 import 'dart:async';
 import 'dart:math' as math;
 
-import 'uuid.dart';
 import '../usage.dart';
-
-final int _MAX_EXCEPTION_LENGTH = 100;
+import '../uuid/uuid.dart';
 
 String postEncode(Map<String, dynamic> map) {
   // &foo=bar
@@ -21,11 +17,11 @@
 }
 
 /**
- * A throttling algorithim. This models the throttling after a bucket with
+ * A throttling algorithm. This models the throttling after a bucket with
  * water dripping into it at the rate of 1 drop per second. If the bucket has
  * water when an operation is requested, 1 drop of water is removed and the
- * operation is performed. If not the operation is skipped. This algorithim
- * lets operations be peformed in bursts without throttling, but holds the
+ * operation is performed. If not the operation is skipped. This algorithm
+ * lets operations be performed in bursts without throttling, but holds the
  * overall average rate of operations to 1 per second.
  */
 class ThrottlingBucket {
@@ -60,11 +56,16 @@
   }
 }
 
-abstract class AnalyticsImpl extends Analytics {
-  static const String _defaultAnalyticsUrl = 'https://www.google-analytics.com/collect';
+class AnalyticsImpl implements Analytics {
+  static const String _defaultAnalyticsUrl =
+      'https://www.google-analytics.com/collect';
 
-  /// Tracking ID / Property ID.
+  @override
   final String trackingId;
+  @override
+  final String applicationName;
+  @override
+  final String applicationVersion;
 
   final PersistentProperties properties;
   final PostHandler postHandler;
@@ -74,16 +75,16 @@
 
   final List<Future> _futures = [];
 
+  @override
+  AnalyticsOpt analyticsOpt = AnalyticsOpt.optOut;
+
   String _url;
 
-  AnalyticsImpl(
-    this.trackingId,
-    this.properties,
-    this.postHandler, {
-    String applicationName,
-    String applicationVersion,
-    String analyticsUrl
-  }) {
+  StreamController<Map<String, dynamic>> _sendController =
+      new StreamController.broadcast(sync: true);
+
+  AnalyticsImpl(this.trackingId, this.properties, this.postHandler,
+      {this.applicationName, this.applicationVersion, String analyticsUrl}) {
     assert(trackingId != null);
 
     if (applicationName != null) setSessionValue('an', applicationName);
@@ -92,52 +93,82 @@
     _url = analyticsUrl ?? _defaultAnalyticsUrl;
   }
 
-  bool get optIn => properties['optIn'] == true;
+  bool _firstRun;
 
-  set optIn(bool value) {
-    properties['optIn'] = value;
+  @override
+  bool get firstRun {
+    if (_firstRun == null) {
+      _firstRun = properties['firstRun'] == null;
+
+      if (properties['firstRun'] != false) {
+        properties['firstRun'] = false;
+      }
+    }
+
+    return _firstRun;
   }
 
-  bool get hasSetOptIn => properties['optIn'] != null;
+  @override
+  bool get enabled {
+    bool optIn = analyticsOpt == AnalyticsOpt.optIn;
+    return optIn
+        ? properties['enabled'] == true
+        : properties['enabled'] != false;
+  }
 
-  Future sendScreenView(String viewName) {
+  @override
+  set enabled(bool value) {
+    properties['enabled'] = value;
+  }
+
+  @override
+  Future sendScreenView(String viewName, {Map<String, String> parameters}) {
     Map<String, dynamic> args = {'cd': viewName};
+    if (parameters != null) {
+      args.addAll(parameters);
+    }
     return _sendPayload('screenview', args);
   }
 
-  Future sendEvent(String category, String action, {String label, int value}) {
-    if (!optIn) return new Future.value();
-
+  @override
+  Future sendEvent(String category, String action,
+      {String label, int value, Map<String, String> parameters}) {
     Map<String, dynamic> args = {'ec': category, 'ea': action};
     if (label != null) args['el'] = label;
     if (value != null) args['ev'] = value;
+    if (parameters != null) {
+      args.addAll(parameters);
+    }
     return _sendPayload('event', args);
   }
 
+  @override
   Future sendSocial(String network, String action, String target) {
-    if (!optIn) return new Future.value();
-
     Map<String, dynamic> args = {'sn': network, 'sa': action, 'st': target};
     return _sendPayload('social', args);
   }
 
-  Future sendTiming(String variableName, int time, {String category,
-        String label}) {
-    if (!optIn) return new Future.value();
-
+  @override
+  Future sendTiming(String variableName, int time,
+      {String category, String label}) {
     Map<String, dynamic> args = {'utv': variableName, 'utt': time};
     if (label != null) args['utl'] = label;
     if (category != null) args['utc'] = category;
     return _sendPayload('timing', args);
   }
 
-  AnalyticsTimer startTimer(String variableName, {String category, String label}) {
-    return new AnalyticsTimer(this,
-        variableName, category: category, label: label);
+  @override
+  AnalyticsTimer startTimer(String variableName,
+      {String category, String label}) {
+    return new AnalyticsTimer(this, variableName,
+        category: category, label: label);
   }
 
+  @override
   Future sendException(String description, {bool fatal}) {
-    if (!optIn) return new Future.value();
+    // We trim exceptions to a max length; google analytics will apply it's own
+    // truncation, likely around 150 chars or so.
+    const int maxExceptionLength = 1000;
 
     // In order to ensure that the client of this API is not sending any PII
     // data, we strip out any stack trace that may reference a path on the
@@ -146,8 +177,10 @@
       description = description.substring(0, description.indexOf('file:/'));
     }
 
-    if (description != null && description.length > _MAX_EXCEPTION_LENGTH) {
-      description = description.substring(0, _MAX_EXCEPTION_LENGTH);
+    description = description.replaceAll('\n', '; ');
+
+    if (description.length > maxExceptionLength) {
+      description = description.substring(0, maxExceptionLength);
     }
 
     Map<String, dynamic> args = {'exd': description};
@@ -155,6 +188,10 @@
     return _sendPayload('exception', args);
   }
 
+  @override
+  dynamic getSessionValue(String param) => _variableMap[param];
+
+  @override
   void setSessionValue(String param, dynamic value) {
     if (value == null) {
       _variableMap.remove(param);
@@ -163,6 +200,10 @@
     }
   }
 
+  @override
+  Stream<Map<String, dynamic>> get onSend => _sendController.stream;
+
+  @override
   Future waitForLastPing({Duration timeout}) {
     Future f = Future.wait(_futures).catchError((e) => null);
 
@@ -173,32 +214,42 @@
     return f;
   }
 
-  /**
-   * Anonymous Client ID. The value of this field should be a random UUID v4.
-   */
-  String get _clientId => properties['clientId'];
+  @override
+  void close() => postHandler.close();
 
-  void _initClientId() {
-    if (_clientId == null) {
-      properties['clientId'] = new Uuid().generateV4();
-    }
+  @override
+  String get clientId => properties['clientId'] ??= new Uuid().generateV4();
+
+  /**
+   * Send raw data to analytics. Callers should generally use one of the typed
+   * methods (`sendScreenView`, `sendEvent`, ...).
+   *
+   * Valid values for [hitType] are: 'pageview', 'screenview', 'event',
+   * 'transaction', 'item', 'social', 'exception', and 'timing'.
+   */
+  Future sendRaw(String hitType, Map<String, dynamic> args) {
+    return _sendPayload(hitType, args);
   }
 
-  // Valid values for [hitType] are: 'pageview', 'screenview', 'event',
-  // 'transaction', 'item', 'social', 'exception', and 'timing'.
+  /**
+   * Valid values for [hitType] are: 'pageview', 'screenview', 'event',
+   * 'transaction', 'item', 'social', 'exception', and 'timing'.
+   */
   Future _sendPayload(String hitType, Map<String, dynamic> args) {
-    if (_bucket.removeDrop()) {
-      _initClientId();
+    if (!enabled) return new Future.value();
 
+    if (_bucket.removeDrop()) {
       _variableMap.forEach((key, value) {
         args[key] = value;
       });
 
       args['v'] = '1'; // protocol version
       args['tid'] = trackingId;
-      args['cid'] = _clientId;
+      args['cid'] = clientId;
       args['t'] = hitType;
 
+      _sendController.add(args);
+
       return _recordFuture(postHandler.sendPost(_url, args));
     } else {
       return new Future.value();
@@ -216,7 +267,7 @@
  * of these injected into it. There are default implementations for `dart:io`
  * and `dart:html` clients.
  *
- * The [name] paramater is used to uniquely store these properties on disk /
+ * The [name] parameter is used to uniquely store these properties on disk /
  * persistent storage.
  */
 abstract class PersistentProperties {
@@ -224,8 +275,12 @@
 
   PersistentProperties(this.name);
 
-  dynamic operator[](String key);
-  void operator[]=(String key, dynamic value);
+  dynamic operator [](String key);
+  void operator []=(String key, dynamic value);
+
+  /// Re-read settings from the backing store. This may be a no-op on some
+  /// platforms.
+  void syncSettings();
 }
 
 /**
@@ -239,4 +294,7 @@
  */
 abstract class PostHandler {
   Future sendPost(String url, Map<String, dynamic> parameters);
+
+  /// Free any used resources.
+  void close();
 }
diff --git a/packages/usage/lib/src/usage_impl_html.dart b/packages/usage/lib/src/usage_impl_html.dart
index e3f6496..c15cde6 100644
--- a/packages/usage/lib/src/usage_impl_html.dart
+++ b/packages/usage/lib/src/usage_impl_html.dart
@@ -2,19 +2,43 @@
 // 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 usage_impl_html;
-
 import 'dart:async';
 import 'dart:convert' show JSON;
 import 'dart:html';
 
 import 'usage_impl.dart';
 
+/// An interface to a Google Analytics session, suitable for use in web apps.
+///
+/// [analyticsUrl] is an optional replacement for the default Google Analytics
+/// URL (`https://www.google-analytics.com/collect`).
+class AnalyticsHtml extends AnalyticsImpl {
+  AnalyticsHtml(
+      String trackingId, String applicationName, String applicationVersion,
+      {String analyticsUrl})
+      : super(trackingId, new HtmlPersistentProperties(applicationName),
+            new HtmlPostHandler(),
+            applicationName: applicationName,
+            applicationVersion: applicationVersion,
+            analyticsUrl: analyticsUrl) {
+    int screenWidth = window.screen.width;
+    int screenHeight = window.screen.height;
+
+    setSessionValue('sr', '${screenWidth}x$screenHeight');
+    setSessionValue('sd', '${window.screen.pixelDepth}-bits');
+    setSessionValue('ul', window.navigator.language);
+  }
+}
+
+typedef Future<HttpRequest> HttpRequestor(String url,
+    {String method, sendData});
+
 class HtmlPostHandler extends PostHandler {
-  final Function mockRequestor;
+  final HttpRequestor mockRequestor;
 
-  HtmlPostHandler({Function this.mockRequestor});
+  HtmlPostHandler({this.mockRequestor});
 
+  @override
   Future sendPost(String url, Map<String, dynamic> parameters) {
     int viewportWidth = document.documentElement.clientWidth;
     int viewportHeight = document.documentElement.clientHeight;
@@ -22,12 +46,16 @@
     parameters['vp'] = '${viewportWidth}x$viewportHeight';
 
     String data = postEncode(parameters);
-    var request = mockRequestor == null ? HttpRequest.request : mockRequestor;
-    return request(url, method: 'POST', sendData: data).catchError((e) {
+    HttpRequestor requestor =
+        mockRequestor == null ? HttpRequest.request : mockRequestor;
+    return requestor(url, method: 'POST', sendData: data).catchError((e) {
       // Catch errors that can happen during a request, but that we can't do
-      // anything about, e.g. a missing internet conenction.
+      // anything about, e.g. a missing internet connection.
     });
   }
+
+  @override
+  void close() {}
 }
 
 class HtmlPersistentProperties extends PersistentProperties {
@@ -39,9 +67,11 @@
     _map = JSON.decode(str);
   }
 
-  dynamic operator[](String key) => _map[key];
+  @override
+  dynamic operator [](String key) => _map[key];
 
-  void operator[]=(String key, dynamic value) {
+  @override
+  void operator []=(String key, dynamic value) {
     if (value == null) {
       _map.remove(key);
     } else {
@@ -50,4 +80,7 @@
 
     window.localStorage[name] = JSON.encode(_map);
   }
+
+  @override
+  void syncSettings() {}
 }
diff --git a/packages/usage/lib/src/usage_impl_io.dart b/packages/usage/lib/src/usage_impl_io.dart
index d59793e..6e82d41 100644
--- a/packages/usage/lib/src/usage_impl_io.dart
+++ b/packages/usage/lib/src/usage_impl_io.dart
@@ -2,31 +2,71 @@
 // 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 usage_impl_io;
-
 import 'dart:async';
-import 'dart:convert' show JSON;
+import 'dart:convert' show JSON, JsonEncoder;
 import 'dart:io';
 
 import 'package:path/path.dart' as path;
 
 import 'usage_impl.dart';
 
-String _createUserAgent() {
-  // Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en)
-  // Dart/1.8.0-edge.41170 (macos; macos; macos; null)
-  String os = Platform.operatingSystem;
-  String locale = Platform.environment['LANG'];
-  return "Dart/${_dartVersion()} (${os}; ${os}; ${os}; ${locale})";
+/// An interface to a Google Analytics session, suitable for use in command-line
+/// applications.
+///
+/// `trackingId`, `applicationName`, and `applicationVersion` values should be supplied.
+/// `analyticsUrl` is optional, and lets user's substitute their own analytics URL for
+/// the default.
+///
+/// `documentDirectory` is where the analytics settings are stored. It
+/// defaults to the user home directory. For regular `dart:io` apps this doesn't need to
+/// be supplied. For Flutter applications, you should pass in a value like
+/// `PathProvider.getApplicationDocumentsDirectory()`.
+class AnalyticsIO extends AnalyticsImpl {
+  AnalyticsIO(
+      String trackingId, String applicationName, String applicationVersion,
+      {String analyticsUrl, Directory documentDirectory})
+      : super(
+            trackingId,
+            new IOPersistentProperties(applicationName,
+                documentDirPath: documentDirectory?.path),
+            new IOPostHandler(),
+            applicationName: applicationName,
+            applicationVersion: applicationVersion,
+            analyticsUrl: analyticsUrl) {
+    final String locale = getPlatformLocale();
+    if (locale != null) {
+      setSessionValue('ul', locale);
+    }
+  }
 }
 
-String _userHomeDir() {
+String _createUserAgent() {
+  final String locale = getPlatformLocale() ?? '';
+
+  if (Platform.isAndroid) {
+    return 'Mozilla/5.0 (Android; Mobile; ${locale})';
+  } else if (Platform.isIOS) {
+    return 'Mozilla/5.0 (iPhone; U; CPU iPhone OS like Mac OS X; ${locale})';
+  } else if (Platform.isMacOS) {
+    return 'Mozilla/5.0 (Macintosh; Intel Mac OS X; Macintosh; ${locale})';
+  } else if (Platform.isWindows) {
+    return 'Mozilla/5.0 (Windows; Windows; Windows; ${locale})';
+  } else if (Platform.isLinux) {
+    return 'Mozilla/5.0 (Linux; Linux; Linux; ${locale})';
+  } else {
+    // Dart/1.8.0 (macos; macos; macos; en_US)
+    String os = Platform.operatingSystem;
+    return "Dart/${getDartVersion()} (${os}; ${os}; ${os}; ${locale})";
+  }
+}
+
+String userHomeDir() {
   String envKey = Platform.operatingSystem == 'windows' ? 'APPDATA' : 'HOME';
   String value = Platform.environment[envKey];
   return value == null ? '.' : value;
 }
 
-String _dartVersion() {
+String getDartVersion() {
   String ver = Platform.version;
   int index = ver.indexOf(' ');
   if (index != -1) ver = ver.substring(0, index);
@@ -37,51 +77,103 @@
   final String _userAgent;
   final HttpClient mockClient;
 
-  IOPostHandler({HttpClient this.mockClient}) : _userAgent = _createUserAgent();
+  HttpClient _client;
 
-  Future sendPost(String url, Map<String, dynamic> parameters) {
-    // Add custom parameters for OS and the Dart version.
-    parameters['cd1'] = Platform.operatingSystem;
-    parameters['cd2'] = 'dart ${_dartVersion()}';
+  IOPostHandler({this.mockClient}) : _userAgent = _createUserAgent();
 
+  @override
+  Future sendPost(String url, Map<String, dynamic> parameters) async {
     String data = postEncode(parameters);
 
-    HttpClient client = mockClient != null ? mockClient : new HttpClient();
-    client.userAgent = _userAgent;
-    return client.postUrl(Uri.parse(url)).then((HttpClientRequest req) {
+    if (_client == null) {
+      _client = mockClient != null ? mockClient : new HttpClient();
+      _client.userAgent = _userAgent;
+    }
+
+    try {
+      HttpClientRequest req = await _client.postUrl(Uri.parse(url));
       req.write(data);
-      return req.close();
-    }).then((HttpClientResponse response) {
+      HttpClientResponse response = await req.close();
       response.drain();
-    }).catchError((e) {
+    } catch (exception) {
       // Catch errors that can happen during a request, but that we can't do
-      // anything about, e.g. a missing internet conenction.
-    });
+      // anything about, e.g. a missing internet connection.
+    }
   }
+
+  @override
+  void close() => _client?.close();
 }
 
+JsonEncoder _jsonEncoder = new JsonEncoder.withIndent('  ');
+
 class IOPersistentProperties extends PersistentProperties {
   File _file;
   Map _map;
 
-  IOPersistentProperties(String name) : super(name) {
+  IOPersistentProperties(String name, {String documentDirPath}) : super(name) {
     String fileName = '.${name.replaceAll(' ', '_')}';
-    _file = new File(path.join(_userHomeDir(), fileName));
-    _file.createSync();
-    String contents = _file.readAsStringSync();
-    if (contents.isEmpty) contents = '{}';
-    _map = JSON.decode(contents);
+    documentDirPath ??= userHomeDir();
+    _file = new File(path.join(documentDirPath, fileName));
+    if (!_file.existsSync()) {
+      _file.createSync();
+    }
+    syncSettings();
   }
 
-  dynamic operator[](String key) => _map[key];
+  IOPersistentProperties.fromFile(File file) : super(path.basename(file.path)) {
+    _file = file;
+    if (!_file.existsSync()) {
+      _file.createSync();
+    }
+    syncSettings();
+  }
 
-  void operator[]=(String key, dynamic value) {
+  @override
+  dynamic operator [](String key) => _map[key];
+
+  @override
+  void operator []=(String key, dynamic value) {
+    if (value == null && !_map.containsKey(key)) return;
+    if (_map[key] == value) return;
+
     if (value == null) {
       _map.remove(key);
     } else {
       _map[key] = value;
     }
 
-    _file.writeAsStringSync(JSON.encode(_map) + '\n');
+    try {
+      _file.writeAsStringSync(_jsonEncoder.convert(_map) + '\n');
+    } catch (_) {}
   }
+
+  @override
+  void syncSettings() {
+    try {
+      String contents = _file.readAsStringSync();
+      if (contents.isEmpty) contents = '{}';
+      _map = JSON.decode(contents);
+    } catch (_) {
+      _map = {};
+    }
+  }
+}
+
+/// Return the string for the platform's locale; return's `null` if the locale
+/// can't be determined.
+String getPlatformLocale() {
+  String locale = Platform.localeName;
+  if (locale == null) return null;
+
+  if (locale != null) {
+    // Convert `en_US.UTF-8` to `en_US`.
+    int index = locale.indexOf('.');
+    if (index != -1) locale = locale.substring(0, index);
+
+    // Convert `en_US` to `en-us`.
+    locale = locale.replaceAll('_', '-').toLowerCase();
+  }
+
+  return locale;
 }
diff --git a/packages/usage/lib/src/uuid.dart b/packages/usage/lib/src/uuid.dart
deleted file mode 100644
index 66e99ac..0000000
--- a/packages/usage/lib/src/uuid.dart
+++ /dev/null
@@ -1,48 +0,0 @@
-// 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.
-
-/**
- * A UUID generator library.
- */
-library usage.uuid;
-
-import 'dart:math' show Random;
-
-/**
- * A UUID generator. This will generate unique IDs in the format:
- *
- *     f47ac10b-58cc-4372-a567-0e02b2c3d479
- *
- * The generated uuids are 128 bit numbers encoded in a specific string format.
- *
- * For more information, see
- * http://en.wikipedia.org/wiki/Universally_unique_identifier.
- */
-class Uuid {
-  Random _random = new Random();
-
-  /**
-   * Generate a version 4 (random) uuid. This is a uuid scheme that only uses
-   * random numbers as the source of the generated uuid.
-   */
-  String generateV4() {
-    // Generate xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx / 8-4-4-4-12.
-    int special = 8 + _random.nextInt(4);
-
-    return
-        '${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}-'
-        '${_bitsDigits(16, 4)}-'
-        '4${_bitsDigits(12, 3)}-'
-        '${_printDigits(special,  1)}${_bitsDigits(12, 3)}-'
-        '${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}';
-  }
-
-  String _bitsDigits(int bitCount, int digitCount) =>
-      _printDigits(_generateBits(bitCount), digitCount);
-
-  int _generateBits(int bitCount) => _random.nextInt(1 << bitCount);
-
-  String _printDigits(int value, int count) =>
-      value.toRadixString(16).padLeft(count, '0');
-}
diff --git a/packages/usage/lib/usage.dart b/packages/usage/lib/usage.dart
index 9e9457d..e79701c 100644
--- a/packages/usage/lib/usage.dart
+++ b/packages/usage/lib/usage.dart
@@ -29,10 +29,13 @@
 // Matches file:/, non-ws, /, non-ws, .dart
 final RegExp _pathRegex = new RegExp(r'file:/\S+/(\S+\.dart)');
 
+// Match multiple tabs or spaces.
+final RegExp _tabOrSpaceRegex = new RegExp(r'[\t ]+');
+
 /**
  * An interface to a Google Analytics session. [AnalyticsHtml] and [AnalyticsIO]
  * are concrete implementations of this interface. [AnalyticsMock] can be used
- * for testing or for some varients of an opt-in workflow.
+ * for testing or for some variants of an opt-in workflow.
  *
  * The analytics information is sent on a best-effort basis. So, failures to
  * send the GA information will not result in errors from the asynchronous
@@ -44,29 +47,57 @@
    */
   String get trackingId;
 
-  /**
-   * Whether the user has opt-ed in to additional analytics.
-   */
-  bool get optIn;
+  /// The application name.
+  String get applicationName;
 
-  set optIn(bool value);
+  /// The application version.
+  String get applicationVersion;
 
   /**
-   * Whether the [optIn] value has been explicitly set (either `true` or
-   * `false`).
+   * Is this the first time the tool has run?
    */
-  bool get hasSetOptIn;
+  bool get firstRun;
+
+  /**
+   * Whether the [Analytics] instance is configured in an opt-in or opt-out manner.
+   */
+  AnalyticsOpt analyticsOpt = AnalyticsOpt.optOut;
+
+  /**
+   * Will analytics data be sent.
+   */
+  bool get enabled;
+
+  /**
+   * Enable or disable sending of analytics data.
+   */
+  set enabled(bool value);
+
+  /**
+   * Anonymous client ID in UUID v4 format.
+   *
+   * The value is randomly-generated and should be reasonably stable for the
+   * computer sending analytics data.
+   */
+  String get clientId;
 
   /**
    * Sends a screen view hit to Google Analytics.
+   *
+   * [parameters] can be any analytics key/value pair. Useful
+   * for custom dimensions, etc.
    */
-  Future sendScreenView(String viewName);
+  Future sendScreenView(String viewName, {Map<String, String> parameters});
 
   /**
    * Sends an Event hit to Google Analytics. [label] specifies the event label.
    * [value] specifies the event value. Values must be non-negative.
+   *
+   * [parameters] can be any analytics key/value pair. Useful
+   * for custom dimensions, etc.
    */
-  Future sendEvent(String category, String action, {String label, int value});
+  Future sendEvent(String category, String action,
+      {String label, int value, Map<String, String> parameters});
 
   /**
    * Sends a Social hit to Google Analytics. [network] specifies the social
@@ -83,8 +114,8 @@
    * milliseconds). [category] specifies the category of the timing. [label]
    * specifies the label of the timing.
    */
-  Future sendTiming(String variableName, int time, {String category,
-      String label});
+  Future sendTiming(String variableName, int time,
+      {String category, String label});
 
   /**
    * Start a timer. The time won't be calculated, and the analytics information
@@ -101,6 +132,11 @@
   Future sendException(String description, {bool fatal});
 
   /**
+   * Gets a session variable value.
+   */
+  dynamic getSessionValue(String param);
+
+  /**
    * Sets a session variable value. The value is persistent for the life of the
    * [Analytics] instance. This variable will be sent in with every analytics
    * hit. A list of valid variable names can be found here:
@@ -109,6 +145,16 @@
   void setSessionValue(String param, dynamic value);
 
   /**
+   * Fires events when the usage library sends any data over the network. This
+   * will not fire if analytics has been disabled or if the throttling algorithm
+   * has been engaged.
+   *
+   * This method is public to allow library clients to more easily test their
+   * analytics implementations.
+   */
+  Stream<Map<String, dynamic>> get onSend;
+
+  /**
    * Wait for all of the outstanding analytics pings to complete. The returned
    * `Future` will always complete without errors. You can pass in an optional
    * `Duration` to specify to only wait for a certain amount of time.
@@ -118,9 +164,27 @@
    * users won't want their CLI app to pause at the end of the process waiting
    * for Google analytics requests to complete. This method allows CLI apps to
    * delay for a short time waiting for GA requests to complete, and then do
-   * something like call `exit()` explicitly themselves.
+   * something like call `dart:io`'s `exit()` explicitly themselves (or the
+   * [close] method below).
    */
   Future waitForLastPing({Duration timeout});
+
+  /// Free any used resources.
+  ///
+  /// The [Analytics] instance should not be used after this call.
+  void close();
+}
+
+enum AnalyticsOpt {
+  /**
+   * Users must opt-in before any analytics data is sent.
+   */
+  optIn,
+
+  /**
+   * Users must opt-out for analytics data to not be sent.
+   */
+  optOut
 }
 
 /**
@@ -157,8 +221,8 @@
     if (_endMillis != null) return new Future.value();
 
     _endMillis = new DateTime.now().millisecondsSinceEpoch;
-    return analytics.sendTiming(
-        variableName, currentElapsedMillis, category: category, label: label);
+    return analytics.sendTiming(variableName, currentElapsedMillis,
+        category: category, label: label);
   }
 }
 
@@ -167,11 +231,20 @@
  * stand-in for that will never ping the GA server, or as a mock in test code.
  */
 class AnalyticsMock implements Analytics {
+  @override
   String get trackingId => 'UA-0';
+  @override
+  String get applicationName => 'mock-app';
+  @override
+  String get applicationVersion => '1.0.0';
+
   final bool logCalls;
 
-  bool optIn = false;
-  bool hasSetOptIn = true;
+  /**
+   * Events are never added to this controller for the mock implementation.
+   */
+  StreamController<Map<String, dynamic>> _sendController =
+      new StreamController.broadcast();
 
   /**
    * Create a new [AnalyticsMock]. If [logCalls] is true, all calls will be
@@ -179,36 +252,76 @@
    */
   AnalyticsMock([this.logCalls = false]);
 
-  Future sendScreenView(String viewName) =>
-      _log('screenView', {'viewName': viewName});
+  @override
+  bool get firstRun => false;
 
-  Future sendEvent(String category, String action, {String label, int value}) {
-    return _log('event', {'category': category, 'action': action,
-      'label': label, 'value': value});
+  @override
+  AnalyticsOpt analyticsOpt = AnalyticsOpt.optOut;
+
+  @override
+  bool enabled = true;
+
+  @override
+  String get clientId => '00000000-0000-4000-0000-000000000000';
+
+  @override
+  Future sendScreenView(String viewName, {Map<String, String> parameters}) {
+    parameters ??= <String, String>{};
+    parameters['viewName'] = viewName;
+    return _log('screenView', parameters);
   }
 
+  @override
+  Future sendEvent(String category, String action,
+      {String label, int value, Map<String, String> parameters}) {
+    parameters ??= <String, String>{};
+    return _log(
+        'event',
+        {'category': category, 'action': action, 'label': label, 'value': value}
+          ..addAll(parameters));
+  }
+
+  @override
   Future sendSocial(String network, String action, String target) =>
       _log('social', {'network': network, 'action': action, 'target': target});
 
-  Future sendTiming(String variableName, int time, {String category,
-      String label}) {
-    return _log('timing', {'variableName': variableName, 'time': time,
-      'category': category, 'label': label});
+  @override
+  Future sendTiming(String variableName, int time,
+      {String category, String label}) {
+    return _log('timing', {
+      'variableName': variableName,
+      'time': time,
+      'category': category,
+      'label': label
+    });
   }
 
+  @override
   AnalyticsTimer startTimer(String variableName,
       {String category, String label}) {
-    return new AnalyticsTimer(this,
-        variableName, category: category, label: label);
+    return new AnalyticsTimer(this, variableName,
+        category: category, label: label);
   }
 
+  @override
   Future sendException(String description, {bool fatal}) =>
       _log('exception', {'description': description, 'fatal': fatal});
 
-  void setSessionValue(String param, dynamic value) { }
+  @override
+  dynamic getSessionValue(String param) => null;
 
+  @override
+  void setSessionValue(String param, dynamic value) {}
+
+  @override
+  Stream<Map<String, dynamic>> get onSend => _sendController.stream;
+
+  @override
   Future waitForLastPing({Duration timeout}) => new Future.value();
 
+  @override
+  void close() {}
+
   Future _log(String hitType, Map m) {
     if (logCalls) {
       print('analytics: ${hitType} ${m}');
@@ -236,16 +349,13 @@
 
   for (Match match in iter) {
     String replacement = match.group(1);
-    str = str.substring(0, match.start)
-        + replacement + str.substring(match.end);
+    str =
+        str.substring(0, match.start) + replacement + str.substring(match.end);
   }
 
   if (shorten) {
     // Shorten the stacktrace up a bit.
-    str = str
-        .replaceAll('(package:', '(')
-        .replaceAll('(dart:', '(')
-        .replaceAll(new RegExp(r'\s+'), ' ');
+    str = str.replaceAll(_tabOrSpaceRegex, ' ');
   }
 
   return str;
diff --git a/packages/usage/lib/usage_html.dart b/packages/usage/lib/usage_html.dart
index f3dd555..569c1eb 100644
--- a/packages/usage/lib/usage_html.dart
+++ b/packages/usage/lib/usage_html.dart
@@ -2,44 +2,12 @@
 // 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.
 
-/**
- * In order to use this library import the `usage_html.dart` file and
- * instantiate the [AnalyticsHtml] class.
- *
- * You'll need to provide a Google Analytics tracking ID, the application name,
- * and the application version.
- */
+/// In order to use this library import the `usage_html.dart` file and
+/// instantiate the [AnalyticsHtml] class.
+///
+/// You'll need to provide a Google Analytics tracking ID, the application name,
+/// and the application version.
 library usage_html;
 
-import 'dart:html';
-
-import 'src/usage_impl.dart';
-import 'src/usage_impl_html.dart';
-
+export 'src/usage_impl_html.dart' show AnalyticsHtml;
 export 'usage.dart';
-
-/**
- * An interface to a Google Analytics session, suitable for use in web apps.
- *
- * [analyticsUrl] is an optional replacement for the default Google Analytics
- * URL (`https://www.google-analytics.com/collect`).
- */
-class AnalyticsHtml extends AnalyticsImpl {
-  AnalyticsHtml(String trackingId, String applicationName, String applicationVersion, {
-    String analyticsUrl
-  }) : super(
-      trackingId,
-      new HtmlPersistentProperties(applicationName),
-      new HtmlPostHandler(),
-      applicationName: applicationName,
-      applicationVersion: applicationVersion,
-      analyticsUrl: analyticsUrl
-  ) {
-    int screenWidth = window.screen.width;
-    int screenHeight = window.screen.height;
-
-    setSessionValue('sr', '${screenWidth}x$screenHeight');
-    setSessionValue('sd', '${window.screen.pixelDepth}-bits');
-    setSessionValue('ul', window.navigator.language);
-  }
-}
diff --git a/packages/usage/lib/usage_io.dart b/packages/usage/lib/usage_io.dart
index 61accf7..5e35e94 100644
--- a/packages/usage/lib/usage_io.dart
+++ b/packages/usage/lib/usage_io.dart
@@ -2,36 +2,12 @@
 // 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.
 
-/**
- * In order to use this library import the `usage_io.dart` file and
- * instantiate the [AnalyticsIO] class.
- *
- * You'll need to provide a Google Analytics tracking ID, the application name,
- * and the application version.
- */
+/// In order to use this library import the `usage_io.dart` file and
+/// instantiate the [AnalyticsIO] class.
+///
+/// You'll need to provide a Google Analytics tracking ID, the application name,
+/// and the application version.
 library usage_io;
 
-import 'src/usage_impl.dart';
-import 'src/usage_impl_io.dart';
-
+export 'src/usage_impl_io.dart' show AnalyticsIO;
 export 'usage.dart';
-
-/**
- * An interface to a Google Analytics session, suitable for use in command-line
- * applications.
- *
- * [analyticsUrl] is an optional replacement for the default Google Analytics
- * URL (`https://www.google-analytics.com/collect`).
- */
-class AnalyticsIO extends AnalyticsImpl {
-  AnalyticsIO(String trackingId, String applicationName, String applicationVersion, {
-    String analyticsUrl
-  }) : super(
-    trackingId,
-    new IOPersistentProperties(applicationName),
-    new IOPostHandler(),
-    applicationName: applicationName,
-    applicationVersion: applicationVersion,
-    analyticsUrl: analyticsUrl
-  );
-}
diff --git a/packages/usage/lib/uuid/uuid.dart b/packages/usage/lib/uuid/uuid.dart
new file mode 100644
index 0000000..eaafeb2
--- /dev/null
+++ b/packages/usage/lib/uuid/uuid.dart
@@ -0,0 +1,42 @@
+// 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.
+
+/// A UUID generator library.
+library uuid;
+
+import 'dart:math' show Random;
+
+/// A UUID generator.
+///
+/// This will generate unique IDs in the format:
+///
+///     f47ac10b-58cc-4372-a567-0e02b2c3d479
+///
+/// The generated uuids are 128 bit numbers encoded in a specific string format.
+/// For more information, see
+/// [en.wikipedia.org/wiki/Universally_unique_identifier](http://en.wikipedia.org/wiki/Universally_unique_identifier).
+class Uuid {
+  final Random _random = new Random();
+
+  /// Generate a version 4 (random) uuid. This is a uuid scheme that only uses
+  /// random numbers as the source of the generated uuid.
+  String generateV4() {
+    // Generate xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx / 8-4-4-4-12.
+    int special = 8 + _random.nextInt(4);
+
+    return '${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}-'
+        '${_bitsDigits(16, 4)}-'
+        '4${_bitsDigits(12, 3)}-'
+        '${_printDigits(special,  1)}${_bitsDigits(12, 3)}-'
+        '${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}';
+  }
+
+  String _bitsDigits(int bitCount, int digitCount) =>
+      _printDigits(_generateBits(bitCount), digitCount);
+
+  int _generateBits(int bitCount) => _random.nextInt(1 << bitCount);
+
+  String _printDigits(int value, int count) =>
+      value.toRadixString(16).padLeft(count, '0');
+}
diff --git a/packages/usage/pubspec.yaml b/packages/usage/pubspec.yaml
index 17aa638..564ba2e 100644
--- a/packages/usage/pubspec.yaml
+++ b/packages/usage/pubspec.yaml
@@ -3,13 +3,16 @@
 # BSD-style license that can be found in the LICENSE file.
 
 name: usage
-version: 1.2.0
-description: A Google Analytics wrapper for both command-line and web apps.
+version: 3.3.0
+description: A Google Analytics wrapper for both command-line, web, and Flutter apps.
 homepage: https://github.com/dart-lang/usage
 author: Dart Team <misc@dartlang.org>
 
 environment:
-  sdk: '>=1.0.0 <2.0.0'
+  sdk: '>=1.24.0 <2.0.0'
+
+dependencies:
+  path: ^1.4.0
 
 dev_dependencies:
   browser: ^0.10.0
diff --git a/packages/usage/readme.md b/packages/usage/readme.md
index 5a86ed7..931f91f 100644
--- a/packages/usage/readme.md
+++ b/packages/usage/readme.md
@@ -1,8 +1,8 @@
 # usage
 
-`usage` is a wrapper around Google Analytics for both command-line apps and web
-apps.
+`usage` is a wrapper around Google Analytics for command-line, web, and Flutter apps.
 
+[![pub package](https://img.shields.io/pub/v/usage.svg)](https://pub.dartlang.org/packages/usage)
 [![Build Status](https://travis-ci.org/dart-lang/usage.svg)](https://travis-ci.org/dart-lang/usage)
 [![Coverage Status](https://img.shields.io/coveralls/dart-lang/usage.svg)](https://coveralls.io/r/dart-lang/usage?branch=master)
 
@@ -12,7 +12,26 @@
 instantiate the `AnalyticsHtml` class.
 
 When you are creating a new property at [google analytics](https://www.google.com/analytics/)
-make sure to select not the website option, but the **mobile app** option.
+make sure to select the **mobile app** option, not the website option.
+
+## For Flutter apps
+
+Flutter applications can use the `AnalyticsIO` version of this library. They will need
+to specify the documents directory in the constructor in order to tell the library where
+to save the analytics preferences:
+
+```dart
+import 'package:flutter/services.dart';
+import 'package:usage/usage_io.dart';
+
+void main() {
+  final String UA = ...;
+
+  Analytics ga = new AnalyticsIO(UA, 'ga_test', '3.0',
+    documentsDirectory: PathProvider.getApplicationDocumentsDirectory());
+  ...
+}
+```
 
 ## For command-line apps
 
@@ -33,9 +52,15 @@
 something like:
 
 ```dart
-analytics.waitForLastPing(timeout: new Duration(milliseconds: 500)).then((_) {
-  exit(0);
-});
+await analytics.waitForLastPing(timeout: new Duration(milliseconds: 200));
+analytics.close();
+```
+
+or:
+
+```dart
+await analytics.waitForLastPing(timeout: new Duration(milliseconds: 200));
+exit(0);
 ```
 
 ## Using the API
@@ -51,7 +76,7 @@
 ```dart
 final String UA = ...;
 
-Analytics ga = new AnalyticsIO(UA, 'ga_test', '1.0');
+Analytics ga = new AnalyticsIO(UA, 'ga_test', '3.0');
 ga.optIn = true;
 
 ga.sendScreenView('home');
@@ -64,21 +89,17 @@
 
 ## When do we send analytics data?
 
-We use an opt-in method for sending analytics information. There are essentially
-three states for when we send information:
+You can use this library in an opt-in manner or an opt-out one. It defaults to
+opt-out - data will be sent to Google Analytics unless the user explicitly
+opts-out. The mode can be adjusted by changing the value of the
+`Analytics.analyticsOpt` field.
 
-*Sending screen views* If the user has not opted in, the library will only send
-information about screen views. This allows tools to do things like version
-checks, but does not send any additional information.
+*Opt-out* In opt-out mode, if the user does not explicitly opt-out of collecting
+analytics (`Analytics.enabled = false`), the usage library will send usage data.
 
-*Opt-in* If the user opts-in to analytics collection the library sends all
-requested analytics info. This includes screen views, events, timing
-information, and exceptions.
-
-*Opt-ing out* In order to not send analytics information, either do not call the
-analytics methods, or create and use the `AnalyticsMock` class. This provides
-an instance you can use in place of a real analytics object but each analytics
-method is a no-op.
+*Opt-in* In opt-in mode, no data will be sent until the user explicitly opt-in
+to collection (`Analytics.enabled = true`). This includes screen views, events,
+timing information, and exceptions.
 
 ## Other info
 
@@ -95,6 +116,10 @@
 For more information, please see the Google Analytics Measurement Protocol
 [Policy](https://developers.google.com/analytics/devguides/collection/protocol/policy).
 
+## Contributing
+
+Tests can be run using `pub run test`.
+
 ## Issues and bugs
 
 Please file reports on the
diff --git a/packages/usage/test/all.dart b/packages/usage/test/all.dart
index 393640a..9f93051 100644
--- a/packages/usage/test/all.dart
+++ b/packages/usage/test/all.dart
@@ -5,15 +5,15 @@
 library usage.all_test;
 
 import 'hit_types_test.dart' as hit_types_test;
-import 'usage_test.dart' as usage_test;
-import 'usage_impl_test.dart' as usage_impl_test;
 import 'usage_impl_io_test.dart' as usage_impl_io_test;
+import 'usage_impl_test.dart' as usage_impl_test;
+import 'usage_test.dart' as usage_test;
 import 'uuid_test.dart' as uuid_test;
 
 void main() {
   hit_types_test.defineTests();
-  usage_test.defineTests();
-  usage_impl_test.defineTests();
   usage_impl_io_test.defineTests();
+  usage_impl_test.defineTests();
+  usage_test.defineTests();
   uuid_test.defineTests();
 }
diff --git a/packages/usage/test/hit_types_test.dart b/packages/usage/test/hit_types_test.dart
index 4bfb608..e8bf241 100644
--- a/packages/usage/test/hit_types_test.dart
+++ b/packages/usage/test/hit_types_test.dart
@@ -21,6 +21,13 @@
       expect(mock.mockProperties['clientId'], isNotNull);
       expect(mock.mockPostHandler.sentValues, isNot(isEmpty));
     });
+    test('with parameters', () {
+      AnalyticsImplMock mock = createMock();
+      mock.sendScreenView('withParams', parameters: {'cd1': 'foo'});
+      expect(mock.mockProperties['clientId'], isNotNull);
+      expect(mock.mockPostHandler.sentValues, isNot(isEmpty));
+      has(mock.last, 'cd1');
+    });
   });
 
   group('event', () {
@@ -33,6 +40,16 @@
       has(mock.last, 'ea');
     });
 
+    test('with parameters', () {
+      AnalyticsImplMock mock = createMock();
+      mock.sendEvent('withParams', 'save', parameters: {'cd1': 'foo'});
+      expect(mock.mockPostHandler.sentValues, isNot(isEmpty));
+      was(mock.last, 'event');
+      has(mock.last, 'ec');
+      has(mock.last, 'ea');
+      has(mock.last, 'cd1');
+    });
+
     test('optional args', () {
       AnalyticsImplMock mock = createMock();
       mock.sendEvent('files', 'save', label: 'File Save', value: 23);
@@ -78,26 +95,25 @@
       has(mock.last, 'utl');
     });
 
-    test('timer', () {
+    test('timer', () async {
       AnalyticsImplMock mock = createMock();
       AnalyticsTimer timer =
           mock.startTimer('compile', category: 'Build', label: 'Compile');
 
-      return new Future.delayed(new Duration(milliseconds: 20), () {
-        return timer.finish().then((_) {
-          expect(mock.mockPostHandler.sentValues, isNot(isEmpty));
-          was(mock.last, 'timing');
-          has(mock.last, 'utv');
-          has(mock.last, 'utt');
-          has(mock.last, 'utc');
-          has(mock.last, 'utl');
-          int time = timer.currentElapsedMillis;
-          expect(time, greaterThan(10));
-          return new Future.delayed(new Duration(milliseconds: 10), () {
-            expect(timer.currentElapsedMillis, time);
-          });
-        });
-      });
+      await new Future.delayed(new Duration(milliseconds: 20));
+
+      await timer.finish();
+      expect(mock.mockPostHandler.sentValues, isNot(isEmpty));
+      was(mock.last, 'timing');
+      has(mock.last, 'utv');
+      has(mock.last, 'utt');
+      has(mock.last, 'utc');
+      has(mock.last, 'utl');
+      int time = timer.currentElapsedMillis;
+      expect(time, greaterThan(10));
+
+      await new Future.delayed(new Duration(milliseconds: 10));
+      expect(timer.currentElapsedMillis, time);
     });
   });
 
@@ -124,12 +140,5 @@
       mock.sendException('foo bar (file:///Users/foobar/tmp/error.dart:3:13)');
       expect(mock.last['exd'], 'foo bar (');
     });
-
-    test('long description trimmed', () {
-      String str = '0123456789abcdefghijklmnopqrstuvwxyz';
-      AnalyticsImplMock mock = createMock();
-      mock.sendException(str + str + str + str + str);
-      expect(mock.last['exd'].length, 100);
-    });
   });
 }
diff --git a/packages/usage/test/src/common.dart b/packages/usage/test/src/common.dart
index 238ce6d..7799394 100644
--- a/packages/usage/test/src/common.dart
+++ b/packages/usage/test/src/common.dart
@@ -9,22 +9,20 @@
 import 'package:test/test.dart';
 import 'package:usage/src/usage_impl.dart';
 
-AnalyticsImplMock createMock({bool setOptIn: true}) =>
-    new AnalyticsImplMock('UA-0', setOptIn: setOptIn);
+AnalyticsImplMock createMock({Map<String, dynamic> props}) =>
+    new AnalyticsImplMock('UA-0', props: props);
 
-void was(Map m, String type) => expect(m['t'], type);
-void has(Map m, String key) => expect(m[key], isNotNull);
-void hasnt(Map m, String key) => expect(m[key], isNull);
+was(Map m, String type) => expect(m['t'], type);
+has(Map m, String key) => expect(m[key], isNotNull);
+hasnt(Map m, String key) => expect(m[key], isNull);
 
 class AnalyticsImplMock extends AnalyticsImpl {
   MockProperties get mockProperties => properties;
   MockPostHandler get mockPostHandler => postHandler;
 
-  AnalyticsImplMock(String trackingId, {bool setOptIn: true}) :
-      super(trackingId, new MockProperties(), new MockPostHandler(),
-      applicationName: 'Test App', applicationVersion: '0.1') {
-    if (setOptIn) optIn = true;
-  }
+  AnalyticsImplMock(String trackingId, {Map<String, dynamic> props})
+      : super(trackingId, new MockProperties(props), new MockPostHandler(),
+            applicationName: 'Test App', applicationVersion: '0.1');
 
   Map<String, dynamic> get last => mockPostHandler.last;
 }
@@ -32,18 +30,26 @@
 class MockProperties extends PersistentProperties {
   Map<String, dynamic> props = {};
 
-  MockProperties() : super('mock');
+  MockProperties([Map<String, dynamic> props]) : super('mock') {
+    if (props != null) this.props.addAll(props);
+  }
 
-  dynamic operator[](String key) => props[key];
+  @override
+  dynamic operator [](String key) => props[key];
 
-  void operator[]=(String key, dynamic value) {
+  @override
+  void operator []=(String key, dynamic value) {
     props[key] = value;
   }
+
+  @override
+  void syncSettings() {}
 }
 
 class MockPostHandler extends PostHandler {
   List<Map<String, dynamic>> sentValues = [];
 
+  @override
   Future sendPost(String url, Map<String, dynamic> parameters) {
     sentValues.add(parameters);
 
@@ -51,4 +57,7 @@
   }
 
   Map<String, dynamic> get last => sentValues.last;
+
+  @override
+  void close() {}
 }
diff --git a/packages/usage/test/usage_impl_io_test.dart b/packages/usage/test/usage_impl_io_test.dart
index e41f241..8889077 100644
--- a/packages/usage/test/usage_impl_io_test.dart
+++ b/packages/usage/test/usage_impl_io_test.dart
@@ -2,6 +2,7 @@
 // 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.
 
+@TestOn('!browser')
 library usage.usage_impl_io_test;
 
 import 'dart:async';
@@ -14,13 +15,12 @@
 
 void defineTests() {
   group('IOPostHandler', () {
-    test('sendPost', () {
+    test('sendPost', () async {
       var httpClient = new MockHttpClient();
       IOPostHandler postHandler = new IOPostHandler(mockClient: httpClient);
       Map<String, dynamic> args = {'utv': 'varName', 'utt': 123};
-      return postHandler.sendPost('http://www.google.com', args).then((_) {
-        expect(httpClient.sendCount, 1);
-      });
+      await postHandler.sendPost('http://www.google.com', args);
+      expect(httpClient.sendCount, 1);
     });
   });
 
@@ -39,38 +39,65 @@
       expect(props['foo'], null);
     });
   });
+
+  group('usage_impl_io', () {
+    test('getDartVersion', () {
+      expect(getDartVersion(), isNotNull);
+    });
+
+    test('getPlatformLocale', () {
+      expect(getPlatformLocale(), isNotNull);
+    });
+  });
 }
 
 class MockHttpClient implements HttpClient {
+  @override
   String userAgent;
   int sendCount = 0;
   int writeCount = 0;
   bool closed = false;
+
+  @override
   Future<HttpClientRequest> postUrl(Uri url) {
     return new Future.value(new MockHttpClientRequest(this));
   }
-  noSuchMethod(Invocation invocation) { }
+
+  @override
+  noSuchMethod(Invocation invocation) {}
 }
 
 class MockHttpClientRequest implements HttpClientRequest {
   final MockHttpClient client;
+
   MockHttpClientRequest(this.client);
+
+  @override
   void write(Object obj) {
     client.writeCount++;
   }
+
+  @override
   Future<HttpClientResponse> close() {
     client.closed = true;
     return new Future.value(new MockHttpClientResponse(client));
   }
-  noSuchMethod(Invocation invocation) { }
+
+  @override
+  noSuchMethod(Invocation invocation) {}
 }
 
 class MockHttpClientResponse implements HttpClientResponse {
   final MockHttpClient client;
+
   MockHttpClientResponse(this.client);
-  Future drain([var futureValue]) {
+
+  @override
+  Future/*<E>*/ drain/*<E>*/([/*=E*/ futureValue]) {
     client.sendCount++;
     return new Future.value();
   }
-  noSuchMethod(Invocation invocation) { }
+
+  @override
+  noSuchMethod(Invocation invocation) {}
 }
diff --git a/packages/usage/test/usage_impl_test.dart b/packages/usage/test/usage_impl_test.dart
index b8c0f05..a4ca3ac 100644
--- a/packages/usage/test/usage_impl_test.dart
+++ b/packages/usage/test/usage_impl_test.dart
@@ -28,19 +28,34 @@
   });
 
   group('AnalyticsImpl', () {
+    test('trackingId', () {
+      AnalyticsImplMock mock = createMock();
+      expect(mock.trackingId, isNotNull);
+    });
+
+    test('applicationName', () {
+      AnalyticsImplMock mock = createMock();
+      expect(mock.applicationName, isNotNull);
+    });
+
+    test('applicationVersion', () {
+      AnalyticsImplMock mock = createMock();
+      expect(mock.applicationVersion, isNotNull);
+    });
+
     test('respects disabled', () {
       AnalyticsImplMock mock = createMock();
-      mock.optIn = false;
+      mock.enabled = false;
       mock.sendException('FooBar exception');
-      expect(mock.optIn, false);
+      expect(mock.enabled, false);
       expect(mock.mockPostHandler.sentValues, isEmpty);
     });
 
-    test('hasSetOptIn', () {
-      AnalyticsImplMock mock = createMock(setOptIn: false);
-      expect(mock.hasSetOptIn, false);
-      mock.optIn = false;
-      expect(mock.hasSetOptIn, true);
+    test('firstRun', () {
+      AnalyticsImplMock mock = createMock();
+      expect(mock.firstRun, true);
+      mock = createMock(props: {'firstRun': false});
+      expect(mock.firstRun, false);
     });
 
     test('setSessionValue', () {
@@ -62,6 +77,28 @@
       mock.sendScreenView('baz');
       return mock.waitForLastPing(timeout: new Duration(milliseconds: 100));
     });
+
+    group('clientId', () {
+      test('is available immediately', () {
+        AnalyticsImplMock mock = createMock();
+        expect(mock.clientId, isNotEmpty);
+      });
+
+      test('is memoized', () {
+        AnalyticsImplMock mock = createMock();
+        final value1 = mock.clientId;
+        final value2 = mock.clientId;
+        expect(value1, isNotEmpty);
+        expect(value1, value2);
+      });
+
+      test('is stored in properties', () {
+        AnalyticsImplMock mock = createMock();
+        expect(mock.properties['clientId'], isNull);
+        final value = mock.clientId;
+        expect(mock.properties['clientId'], value);
+      });
+    });
   });
 
   group('postEncode', () {
diff --git a/packages/usage/test/usage_test.dart b/packages/usage/test/usage_test.dart
index bdbd6f1..37500a9 100644
--- a/packages/usage/test/usage_test.dart
+++ b/packages/usage/test/usage_test.dart
@@ -14,7 +14,10 @@
     test('simple', () {
       AnalyticsMock mock = new AnalyticsMock();
       mock.sendScreenView('main');
+      mock.sendScreenView('withParameters', parameters: {'cd1': 'custom'});
       mock.sendEvent('files', 'save');
+      mock.sendEvent('eventWithParameters', 'save',
+          parameters: {'cd1': 'custom'});
       mock.sendSocial('g+', 'plus', 'userid');
       mock.sendTiming('compile', 123);
       mock.startTimer('compile').finish();
@@ -26,38 +29,38 @@
 
   group('sanitizeStacktrace', () {
     test('replace file', () {
-      expect(sanitizeStacktrace(
-          '(file:///Users/foo/tmp/error.dart:3:13)',
-          shorten: false),
+      expect(
+          sanitizeStacktrace('(file:///Users/foo/tmp/error.dart:3:13)',
+              shorten: false),
           '(error.dart:3:13)');
     });
 
     test('replace files', () {
-      expect(sanitizeStacktrace(
-          'foo (file:///Users/foo/tmp/error.dart:3:13)\n'
-          'bar (file:///Users/foo/tmp/error.dart:3:13)',
-          shorten: false),
+      expect(
+          sanitizeStacktrace(
+              'foo (file:///Users/foo/tmp/error.dart:3:13)\n'
+              'bar (file:///Users/foo/tmp/error.dart:3:13)',
+              shorten: false),
           'foo (error.dart:3:13)\nbar (error.dart:3:13)');
     });
 
     test('shorten 1', () {
-      expect(sanitizeStacktrace(
-          '(file:///Users/foo/tmp/error.dart:3:13)'),
+      expect(sanitizeStacktrace('(file:///Users/foo/tmp/error.dart:3:13)'),
           '(error.dart:3:13)');
     });
 
     test('shorten 2', () {
-      expect(sanitizeStacktrace(
-          'foo (file:///Users/foo/tmp/error.dart:3:13)\n'
-          'bar (file:///Users/foo/tmp/error.dart:3:13)'),
-          'foo (error.dart:3:13) bar (error.dart:3:13)');
+      expect(
+          sanitizeStacktrace('foo (file:///Users/foo/tmp/error.dart:3:13)\n'
+              'bar (file:///Users/foo/tmp/error.dart:3:13)'),
+          'foo (error.dart:3:13)\nbar (error.dart:3:13)');
     });
 
     test('shorten 3', () {
-      expect(sanitizeStacktrace(
-          'foo (package:foo/foo.dart:3:13)\n'
-          'bar (dart:async/schedule_microtask.dart:41)'),
-          'foo (foo/foo.dart:3:13) bar (async/schedule_microtask.dart:41)');
+      expect(
+          sanitizeStacktrace('foo (package:foo/foo.dart:3:13)\n'
+              'bar (dart:async/schedule_microtask.dart:41)'),
+          'foo (package:foo/foo.dart:3:13)\nbar (dart:async/schedule_microtask.dart:41)');
     });
   });
 }
diff --git a/packages/usage/test/uuid_test.dart b/packages/usage/test/uuid_test.dart
index 335d878..98dbab8 100644
--- a/packages/usage/test/uuid_test.dart
+++ b/packages/usage/test/uuid_test.dart
@@ -5,7 +5,7 @@
 library usage.uuid_test;
 
 import 'package:test/test.dart';
-import 'package:usage/src/uuid.dart';
+import 'package:usage/uuid/uuid.dart';
 
 main() => defineTests();
 
diff --git a/packages/usage/test/web_test.dart b/packages/usage/test/web_test.dart
index c2aaa9a..8d91ae9 100644
--- a/packages/usage/test/web_test.dart
+++ b/packages/usage/test/web_test.dart
@@ -6,6 +6,7 @@
 library usage.web_test;
 
 import 'dart:async';
+import 'dart:html';
 
 import 'package:test/test.dart';
 import 'package:usage/src/usage_impl_html.dart';
@@ -22,32 +23,34 @@
   usage_impl_test.defineTests();
   uuid_test.defineTests();
 
-  // Define some web specfic tests.
+  // Define some web specific tests.
   defineWebTests();
 }
 
 void defineWebTests() {
   group('HtmlPostHandler', () {
-    test('sendPost', () {
+    test('sendPost', () async {
       MockRequestor client = new MockRequestor();
-      HtmlPostHandler postHandler = new HtmlPostHandler(
-          mockRequestor: client.request);
+      HtmlPostHandler postHandler =
+          new HtmlPostHandler(mockRequestor: client.request);
       Map<String, dynamic> args = {'utv': 'varName', 'utt': 123};
-      return postHandler.sendPost('http://www.google.com', args).then((_) {
-        expect(client.sendCount, 1);
-      });
+
+      await postHandler.sendPost('http://www.google.com', args);
+      expect(client.sendCount, 1);
     });
   });
 
   group('HtmlPersistentProperties', () {
     test('add', () {
-      HtmlPersistentProperties props = new HtmlPersistentProperties('foo_props');
+      HtmlPersistentProperties props =
+          new HtmlPersistentProperties('foo_props');
       props['foo'] = 'bar';
       expect(props['foo'], 'bar');
     });
 
     test('remove', () {
-      HtmlPersistentProperties props = new HtmlPersistentProperties('foo_props');
+      HtmlPersistentProperties props =
+          new HtmlPersistentProperties('foo_props');
       props['foo'] = 'bar';
       expect(props['foo'], 'bar');
       props['foo'] = null;
@@ -59,7 +62,7 @@
 class MockRequestor {
   int sendCount = 0;
 
-  Future request(String url, {String method, String sendData}) {
+  Future<HttpRequest> request(String url, {String method, sendData}) {
     expect(url, isNotEmpty);
     expect(method, isNotEmpty);
     expect(sendData, isNotEmpty);
diff --git a/packages/usage/tool/drone.sh b/packages/usage/tool/drone.sh
deleted file mode 100755
index ff46f4f..0000000
--- a/packages/usage/tool/drone.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-
-# 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.
-
-# Fast fail the script on failures.
-set -e
-
-# Display installed versions.
-dart --version
-
-# Get our packages.
-pub get
-
-# Verify that the libraries are error free.
-dartanalyzer --fatal-warnings \
-  lib/usage.dart \
-  lib/usage_html.dart \
-  lib/usage_io.dart \
-  test/all.dart
-
-# Run the tests.
-dart test/all.dart
-
-# Measure the size of the compiled JS, for the dart:html version of the library.
-dart tool/grind.dart build
diff --git a/packages/usage/tool/grind.dart b/packages/usage/tool/grind.dart
index f1d8121..f93261b 100644
--- a/packages/usage/tool/grind.dart
+++ b/packages/usage/tool/grind.dart
@@ -12,31 +12,24 @@
 
 main(List<String> args) => grind(args);
 
-@Task('Do any necessary build set up')
-void init() {
-  // Verify we're running in the project root.
-  if (!getDir('lib').existsSync() || !getFile('pubspec.yaml').existsSync()) {
-    context.fail('This script must be run from the project root.');
-  }
-
-  _buildExampleDir.createSync(recursive: true);
-}
+@Task()
+void init() => _buildExampleDir.createSync(recursive: true);
 
 @Task()
 @Depends(init)
 void build() {
   // Compile `test/web_test.dart` to the `build/test` dir; measure its size.
   File srcFile = new File('example/example.dart');
-  Dart2js.compile(srcFile, outDir: _buildExampleDir, minify: true);
+  Dart2js.compile(srcFile,
+      outDir: _buildExampleDir,
+      minify: true,
+      extraArgs: ['--conditional-directives']);
   File outFile = joinFile(_buildExampleDir, ['example.dart.js']);
 
-  context.log('${outFile.path} compiled to ${_printSize(outFile)}');
+  log('${outFile.path} compiled to ${_printSize(outFile)}');
 }
 
-@Task('Delete all generated artifacts')
-void clean() {
-  // Delete the build/ dir.
-  delete(buildDir);
-}
+@Task()
+void clean() => delete(buildDir);
 
 String _printSize(File file) => '${(file.lengthSync() + 1023) ~/ 1024}k';
diff --git a/packages/usage/tool/travis.sh b/packages/usage/tool/travis.sh
index 5b6e2dd..a3f3082 100755
--- a/packages/usage/tool/travis.sh
+++ b/packages/usage/tool/travis.sh
@@ -9,22 +9,14 @@
 
 # Verify that the libraries are error free.
 dartanalyzer --fatal-warnings \
+  example/example.dart \
+  example/ga.dart \
   lib/usage.dart \
-  lib/usage_html.dart \
-  lib/usage_io.dart \
   test/all.dart
 
 # Run the tests.
 dart -c test/all.dart
 
-# Run the UI/web tests as well.
-#pub build test
-#pub run grinder:test build/test/web.html
-
-# Verify against DDC.
-pub global activate dev_compiler
-pub global run dev_compiler lib/usage_html.dart
-
 # Measure the size of the compiled JS, for the dart:html version of the library.
 dart tool/grind.dart build
 
diff --git a/pubspec.yaml b/pubspec.yaml
index f47445c..84c91cf 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,11 +1,10 @@
 name: observatory
-transformers:
-- dart_to_js_script_rewriter
-- $dart2js:
-    suppressWarnings: false
-    commandLineOptions: [--show-package-warnings]
 dependencies:
-  args: ^0.13.2
-  charted: ^0.4.8
-  unittest: < 0.12.0
-  usage: ^1.2.0
+  charted: any
+  logging: any
+  stack_trace: any
+  unittest: any
+  usage: any
+
+  # charted neglects to declare this dependency
+  collection: any
diff --git a/roll.py b/roll.py
index 11c313c..7fd2a40 100755
--- a/roll.py
+++ b/roll.py
@@ -90,29 +90,10 @@
     f.write('\n'.join(yaml))
 
 def main():
-  parser = argparse.ArgumentParser(
-      description='Updating snapshot of Observatory dependencies')
-  parser.add_argument(
-      '--dart-sdk-src',
-      action='store',
-      metavar='dart_sdk_src',
-      help='Path to dart/sdk',
-      default='~/workspace/dart/sdk')
-  args = parser.parse_args()
-  args.dart_sdk_src = os.path.abspath(os.path.expanduser(args.dart_sdk_src))
-  observatory_dir = os.path.join(args.dart_sdk_src, 'runtime', 'observatory')
-
   if not check_for_pubspec_yaml(SCRIPT_DIR):
     print('Error could not find pubspec.yaml next to roll.py')
     return 1
 
-  if not check_for_pubspec_yaml(observatory_dir):
-    print('Error could not find Observatory source.')
-    return 1
-
-  yaml_src = os.path.abspath(os.path.join(SCRIPT_DIR, 'pubspec.yaml'))
-  yaml_dst = os.path.abspath(os.path.join(observatory_dir, 'pubspec.yaml'))
-
   packages_dst = os.path.abspath(os.path.join(SCRIPT_DIR, 'packages'))
 
   temp_dir = tempfile.mkdtemp();
@@ -122,7 +103,6 @@
     packages_src = os.path.join(temp_dir, '.packages')
     run_pub_get(temp_dir)
     update_packages(packages_src, packages_dst)
-    rewrite_pubspec_yaml(packages_src, yaml_src, yaml_dst)
   finally:
     shutil.rmtree(temp_dir)