Version 2.10.0-96.0.dev

Merge commit '5b956bd8a0eb2012f55d3e98fc0d3d8cc08f4dca' into 'dev'
diff --git a/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart b/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
index 27e4114..09dd636 100644
--- a/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
+++ b/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
@@ -37,8 +37,10 @@
 import 'package:analysis_server/src/services/correction/dart/remove_this_expression.dart';
 import 'package:analysis_server/src/services/correction/dart/remove_type_annotation.dart';
 import 'package:analysis_server/src/services/correction/dart/remove_unnecessary_new.dart';
+import 'package:analysis_server/src/services/correction/dart/rename_to_camel_case.dart';
 import 'package:analysis_server/src/services/correction/dart/replace_cascade_with_dot.dart';
 import 'package:analysis_server/src/services/correction/dart/replace_colon_with_equals.dart';
+import 'package:analysis_server/src/services/correction/dart/replace_final_with_const.dart';
 import 'package:analysis_server/src/services/correction/dart/replace_null_with_closure.dart';
 import 'package:analysis_server/src/services/correction/dart/replace_with_conditional_assignment.dart';
 import 'package:analysis_server/src/services/correction/dart/replace_with_is_empty.dart';
@@ -80,11 +82,13 @@
     LintNames.empty_statements: RemoveEmptyStatement.newInstance,
     LintNames.hash_and_equals: CreateMethod.equalsOrHashCode,
     LintNames.no_duplicate_case_values: RemoveDuplicateCase.newInstance,
+    LintNames.non_constant_identifier_names: RenameToCamelCase.newInstance,
     LintNames.null_closures: ReplaceNullWithClosure.newInstance,
     LintNames.omit_local_variable_types: ReplaceWithVar.newInstance,
     LintNames.prefer_adjacent_string_concatenation: RemoveOperator.newInstance,
     LintNames.prefer_conditional_assignment:
         ReplaceWithConditionalAssignment.newInstance,
+    LintNames.prefer_const_declarations: ReplaceFinalWithConst.newInstance,
     LintNames.prefer_contains: ConvertToContains.newInstance,
     LintNames.prefer_equal_for_default_values:
         ReplaceColonWithEquals.newInstance,
diff --git a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_parser.dart b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_parser.dart
index f5e89d2..19d4808 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_parser.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_parser.dart
@@ -2,12 +2,15 @@
 // 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:analysis_server/src/services/correction/fix/data_driven/add_type_parameter.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/change.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/element_descriptor.dart';
+import 'package:analysis_server/src/services/correction/fix/data_driven/parameter_reference.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/rename.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/transform.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/transform_set.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/transform_set_error_code.dart';
+import 'package:analysis_server/src/services/correction/fix/data_driven/value_extractor.dart';
 import 'package:analysis_server/src/utilities/extensions/yaml.dart';
 import 'package:analyzer/error/listener.dart';
 import 'package:yaml/yaml.dart';
@@ -15,51 +18,31 @@
 /// A parser used to read a transform set from a file.
 class TransformSetParser {
   static const String _changesKey = 'changes';
-
   static const String _classKey = 'class';
-
   static const String _constructorKey = 'constructor';
-
   static const String _elementKey = 'element';
-
   static const String _enumConstantKey = 'constant';
-
   static const String _enumKey = 'enum';
-
   static const String _extensionKey = 'extension';
-
   static const String _fieldKey = 'field';
-
   static const String _functionKey = 'function';
-
   static const String _getterKey = 'getter';
-
   static const String _inClassKey = 'inClass';
-
   static const String _inEnumKey = 'inEnum';
-
   static const String _inExtensionKey = 'inExtension';
-
+  static const String _indexKey = 'index';
   static const String _inMixinKey = 'inMixin';
-
   static const String _kindKey = 'kind';
-
   static const String _methodKey = 'method';
-
   static const String _mixinKey = 'mixin';
-
+  static const String _nameKey = 'name';
   static const String _newNameKey = 'newName';
-
   static const String _setterKey = 'setter';
-
   static const String _titleKey = 'title';
-
   static const String _transformsKey = 'transforms';
-
   static const String _typedefKey = 'typedef';
-
   static const String _urisKey = 'uris';
-
+  static const String _valueKey = 'value';
   static const String _versionKey = 'version';
 
   /// A table mapping top-level keys for member elements to the list of keys for
@@ -73,6 +56,8 @@
     _setterKey: [_inClassKey, _inExtensionKey, _inMixinKey],
   };
 
+  static const String _addTypeParameterKind = 'addTypeParameter';
+  static const String _argumentKind = 'argument';
   static const String _renameKind = 'rename';
 
   static const int currentVersion = 1;
@@ -160,13 +145,63 @@
     return foundKeys[0];
   }
 
+  /// Translate the [node] into an add-type-parameter change. Return the
+  /// resulting change, or `null` if the [node] does not represent a valid
+  /// add-type-parameter change.
+  Change _translateAddTypeParameterChange(YamlMap node) {
+    _reportUnsupportedKeys(
+        node, const {_indexKey, _kindKey, _nameKey, _valueKey});
+    var index = _translateInteger(node.valueAt(_indexKey));
+    if (index == null) {
+      return null;
+    }
+    var name = _translateString(node.valueAt(_nameKey));
+    if (name == null) {
+      return null;
+    }
+    var value = _translateValueExtractor(node.valueAt(_valueKey));
+    if (value == null) {
+      return null;
+    }
+    return AddTypeParameter(index: index, name: name, value: value);
+  }
+
+  /// Translate the [node] into a value extractor. Return the resulting
+  /// extractor, or `null` if the [node] does not represent a valid value
+  /// extractor.
+  ValueExtractor _translateArgumentExtractor(YamlMap node) {
+    var indexNode = node.valueAt(_indexKey);
+    if (indexNode != null) {
+      _reportUnsupportedKeys(node, const {_indexKey, _kindKey});
+      var index = _translateInteger(indexNode);
+      if (index == null) {
+        // The error has already been reported.
+        return null;
+      }
+      return ArgumentExtractor(PositionalParameterReference(index));
+    }
+    var nameNode = node.valueAt(_nameKey);
+    if (nameNode != null) {
+      _reportUnsupportedKeys(node, const {_nameKey, _kindKey});
+      var name = _translateString(nameNode);
+      if (name == null) {
+        // The error has already been reported.
+        return null;
+      }
+      return ArgumentExtractor(NamedParameterReference(name));
+    }
+    // TODO(brianwilkerson) Report the missing YAML.
+    return null;
+  }
+
   /// Translate the [node] into a change. Return the resulting change, or `null`
   /// if the [node] does not represent a valid change.
   Change _translateChange(YamlNode node) {
     if (node is YamlMap) {
       var kind = _translateString(node.valueAt(_kindKey));
-      // TODO(brianwilkerson) Implement additional change kinds.
-      if (kind == _renameKind) {
+      if (kind == _addTypeParameterKind) {
+        return _translateAddTypeParameterChange(node);
+      } else if (kind == _renameKind) {
         return _translateRenameChange(node);
       }
       // TODO(brianwilkerson) Report the invalid change kind.
@@ -365,4 +400,24 @@
       return null;
     }
   }
+
+  /// Translate the [node] into a value extractor. Return the resulting
+  /// extractor, or `null` if the [node] does not represent a valid value
+  /// extractor.
+  ValueExtractor _translateValueExtractor(YamlNode node) {
+    if (node is YamlMap) {
+      var kind = _translateString(node.valueAt(_kindKey));
+      if (kind == _argumentKind) {
+        return _translateArgumentExtractor(node);
+      }
+      // TODO(brianwilkerson) Report the invalid extractor kind.
+      return null;
+    } else if (node == null) {
+      // TODO(brianwilkerson) Report the missing YAML.
+      return null;
+    } else {
+      // TODO(brianwilkerson) Report the invalid YAML.
+      return null;
+    }
+  }
 }
diff --git a/pkg/analysis_server/test/src/services/correction/fix/bulk/rename_to_camel_case_test.dart b/pkg/analysis_server/test/src/services/correction/fix/bulk/rename_to_camel_case_test.dart
new file mode 100644
index 0000000..8e717a0
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/bulk/rename_to_camel_case_test.dart
@@ -0,0 +1,45 @@
+// Copyright (c) 2020, 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:analysis_server/src/services/linter/lint_names.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'bulk_fix_processor.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(RenameToCamelCaseTest);
+  });
+}
+
+@reflectiveTest
+class RenameToCamelCaseTest extends BulkFixProcessorTest {
+  @override
+  String get lintCode => LintNames.non_constant_identifier_names;
+
+  Future<void> test_singleFile() async {
+    await resolveTestUnit('''
+main() {
+  int my_integer_variable = 42;
+  int foo;
+  print(my_integer_variable);
+  print(foo);
+  [0, 1, 2].forEach((my_integer_variable) {
+    print(my_integer_variable);
+  });
+}
+''');
+    await assertHasFix('''
+main() {
+  int myIntegerVariable = 42;
+  int foo;
+  print(myIntegerVariable);
+  print(foo);
+  [0, 1, 2].forEach((myIntegerVariable) {
+    print(myIntegerVariable);
+  });
+}
+''');
+  }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/bulk/replace_final_with_const_test.dart b/pkg/analysis_server/test/src/services/correction/fix/bulk/replace_final_with_const_test.dart
new file mode 100644
index 0000000..1eefe84
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/bulk/replace_final_with_const_test.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2020, 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:analysis_server/src/services/linter/lint_names.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'bulk_fix_processor.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(ReplaceFinalWithConstTest);
+  });
+}
+
+@reflectiveTest
+class ReplaceFinalWithConstTest extends BulkFixProcessorTest {
+  @override
+  String get lintCode => LintNames.prefer_const_declarations;
+
+  Future<void> test_singleFile() async {
+    await resolveTestUnit('''
+final int a = 1;
+final b = 1;
+''');
+    await assertHasFix('''
+const int a = 1;
+const b = 1;
+''');
+  }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/bulk/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/bulk/test_all.dart
index 36033cf..169e7b5 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/bulk/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/bulk/test_all.dart
@@ -39,7 +39,9 @@
 import 'remove_type_annotation_test.dart' as remove_type_annotation;
 import 'remove_unnecessary_const_test.dart' as remove_unnecessary_const;
 import 'remove_unnecessary_new_test.dart' as remove_unnecessary_new;
+import 'rename_to_camel_case_test.dart' as rename_to_camel_case;
 import 'replace_colon_with_equals_test.dart' as replace_colon_with_equals;
+import 'replace_final_with_const_test.dart' as replace_final_with_const;
 import 'replace_null_with_closure_test.dart' as replace_null_with_closure;
 import 'replace_with_conditional_assignment_test.dart'
     as replace_with_conditional_assignment;
@@ -83,8 +85,10 @@
     remove_type_annotation.main();
     remove_unnecessary_const.main();
     remove_unnecessary_new.main();
+    rename_to_camel_case.main();
     replace_with_conditional_assignment.main();
     replace_colon_with_equals.main();
+    replace_final_with_const.main();
     replace_null_with_closure.main();
     replace_with_is_empty.main();
     replace_with_tear_off.main();
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/end_to_end_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/end_to_end_test.dart
index 58dd53a..1f0eb58 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/end_to_end_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/end_to_end_test.dart
@@ -14,6 +14,45 @@
 
 @reflectiveTest
 class EndToEndTest extends DataDrivenFixProcessorTest {
+  Future<void> test_addTypeParameter() async {
+    setPackageContent('''
+class C {
+  void m<S, T>(Type t) {}
+}
+''');
+    addPackageDataFile('''
+version: 1
+transforms:
+- title: 'Add type argument'
+  element:
+    uris:
+      - '$importUri'
+    method: 'm'
+    inClass: 'C'
+  changes:
+    - kind: 'addTypeParameter'
+      index: 1
+      name: 'T'
+      value:
+        kind: 'argument'
+        index: 0
+''');
+    await resolveTestUnit('''
+import '$importUri';
+
+void f(C c) {
+  c.m<int>(String);
+}
+''');
+    await assertHasFix('''
+import '$importUri';
+
+void f(C c) {
+  c.m<int, String>(String);
+}
+''');
+  }
+
   Future<void> test_rename() async {
     setPackageContent('''
 class New {}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/transform_set_parser_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/transform_set_parser_test.dart
index f36fef5..b60500a 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/transform_set_parser_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/transform_set_parser_test.dart
@@ -2,8 +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.
 
+import 'package:analysis_server/src/services/correction/fix/data_driven/add_type_parameter.dart';
+import 'package:analysis_server/src/services/correction/fix/data_driven/parameter_reference.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/rename.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/transform_set_error_code.dart';
+import 'package:analysis_server/src/services/correction/fix/data_driven/value_extractor.dart';
 import 'package:matcher/matcher.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -18,6 +21,66 @@
 
 @reflectiveTest
 class TransformSetParserTest extends AbstractTransformSetParserTest {
+  void test_addTypeParameter_fromNamedArgument() {
+    parse('''
+version: 1
+transforms:
+- title: 'Add'
+  element:
+    uris:
+      - 'test.dart'
+    class: 'A'
+  changes:
+    - kind: 'addTypeParameter'
+      index: 0
+      name: 'T'
+      value:
+        kind: 'argument'
+        name: 'p'
+''');
+    var transforms = result.transformsFor('A', ['test.dart']);
+    expect(transforms, hasLength(1));
+    var transform = transforms[0];
+    expect(transform.title, 'Add');
+    expect(transform.changes, hasLength(1));
+    var change = transform.changes[0] as AddTypeParameter;
+    expect(change.index, 0);
+    expect(change.name, 'T');
+    var value = change.value as ArgumentExtractor;
+    var parameter = value.parameter as NamedParameterReference;
+    expect(parameter.name, 'p');
+  }
+
+  void test_addTypeParameter_fromPositionalArgument() {
+    parse('''
+version: 1
+transforms:
+- title: 'Add'
+  element:
+    uris:
+      - 'test.dart'
+    class: 'A'
+  changes:
+    - kind: 'addTypeParameter'
+      index: 0
+      name: 'T'
+      value:
+        kind: 'argument'
+        index: 2
+''');
+    var transforms = result.transformsFor('A', ['test.dart']);
+    expect(transforms, hasLength(1));
+    var transform = transforms[0];
+    expect(transform.title, 'Add');
+    expect(transform.changes, hasLength(1));
+    var change = transform.changes[0] as AddTypeParameter;
+    expect(change.index, 0);
+    expect(change.name, 'T');
+    var value = change.value as ArgumentExtractor;
+    var parameter = value.parameter as PositionalParameterReference;
+    expect(parameter.index, 2);
+  }
+
   void test_element_getter_inMixin() {
     parse('''
 version: 1
diff --git a/pkg/dartdev/lib/dartdev.dart b/pkg/dartdev/lib/dartdev.dart
index 4cc2964..a258612 100644
--- a/pkg/dartdev/lib/dartdev.dart
+++ b/pkg/dartdev/lib/dartdev.dart
@@ -20,7 +20,7 @@
 import 'src/commands/fix.dart';
 import 'src/commands/pub.dart';
 import 'src/commands/run.dart';
-import 'src/commands/test.dart' hide Runtime;
+import 'src/commands/test.dart';
 import 'src/core.dart';
 import 'src/experiments.dart';
 import 'src/sdk.dart';
diff --git a/pkg/dartdev/lib/src/commands/test.dart b/pkg/dartdev/lib/src/commands/test.dart
index f655ed4..dcb5a7f 100644
--- a/pkg/dartdev/lib/src/commands/test.dart
+++ b/pkg/dartdev/lib/src/commands/test.dart
@@ -2,13 +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.
 
-// ignore_for_file: avoid_redundant_argument_values
-// ignore_for_file: lines_longer_than_80_chars
-// ignore_for_file: prefer_initializing_formals
-
 import 'dart:async';
-import 'dart:io';
-import 'dart:math' as math;
 
 import 'package:args/args.dart';
 
@@ -17,35 +11,51 @@
 import '../sdk.dart';
 import '../vm_interop_handler.dart';
 
+/// Implement `dart test`.
+///
+/// This command largely delegates to `pub run test`.
 class TestCommand extends DartdevCommand<int> {
-  TestCommand() : super('test', 'Run tests in this package.') {
-    generateParser(argParser);
-  }
+  TestCommand() : super('test', 'Run tests in this package.');
+
+  @override
+  final ArgParser argParser = ArgParser.allowAnything();
 
   @override
   void printUsage() {
-    if (!Sdk.checkArtifactExists(sdk.pubSnapshot)) {
-      return;
-    }
-    super.printUsage();
+    _runImpl(['-h']);
   }
 
   @override
   FutureOr<int> run() async {
+    return _runImpl(argResults.arguments.toList());
+  }
+
+  int _runImpl(List<String> testArgs) {
     if (!Sdk.checkArtifactExists(sdk.pubSnapshot)) {
       return 255;
     }
-    // "Could not find package "test". Did you forget to add a dependency?"
-    if (project.hasPackageConfigFile) {
-      if ((project.packageConfig != null) &&
-          !project.packageConfig.hasDependency('test')) {
-        _printPackageTestInstructions();
-        return 65;
-      }
+
+    final pubSnapshot = sdk.pubSnapshot;
+
+    bool isHelpCommand = testArgs.contains('--help') || testArgs.contains('-h');
+
+    // Check for no pubspec.yaml file.
+    if (!project.hasPubspecFile) {
+      _printNoPubspecMessage(isHelpCommand);
+      return 65;
     }
 
-    final command = sdk.pubSnapshot;
-    final testArgs = argResults.arguments.toList();
+    // Handle the case of no .dart_tool/package_config.json file.
+    if (!project.hasPackageConfigFile) {
+      _printRunPubGetInstructions(isHelpCommand);
+      return 65;
+    }
+
+    // "Could not find package "test". Did you forget to add a dependency?"
+    if (!project.packageConfig.hasDependency('test')) {
+      _printMissingDepInstructions(isHelpCommand);
+      return 65;
+    }
 
     final args = [
       'run',
@@ -55,327 +65,59 @@
       ...testArgs,
     ];
 
-    log.trace('$command ${args.join(' ')}');
-    VmInteropHandler.run(command, args);
+    log.trace('$pubSnapshot ${args.join(' ')}');
+    VmInteropHandler.run(pubSnapshot, args);
     return 0;
   }
 
-  void _printPackageTestInstructions() {
-    log.stdout('');
+  void _printNoPubspecMessage(bool wasHelpCommand) {
+    log.stdout('''
+No pubspec.yaml file found; please run this command from the root of your project.
+''');
 
+    if (wasHelpCommand) {
+      log.stdout(_terseHelp);
+      log.stdout('');
+    }
+
+    log.stdout(_usageHelp);
+  }
+
+  void _printRunPubGetInstructions(bool wasHelpCommand) {
+    log.stdout('''
+No .dart_tool/package_config.json file found, please run 'dart pub get'.
+''');
+
+    if (wasHelpCommand) {
+      log.stdout(_terseHelp);
+      log.stdout('');
+    }
+
+    log.stdout(_usageHelp);
+  }
+
+  void _printMissingDepInstructions(bool wasHelpCommand) {
     final ansi = log.ansi;
 
     log.stdout('''
-In order to run tests, you need to add a dependency on package:test in your
-pubspec.yaml file:
+No dependency on package:test found. In order to run tests, you need to add a dependency
+on package:test in your pubspec.yaml file:
 
 ${ansi.emphasized('dev_dependencies:\n  test: ^1.0.0')}
 
-See https://pub.dev/packages/test#-installing-tab- for more information on
-adding package:test, and https://dart.dev/guides/testing for general
-information on testing.''');
-  }
+See https://pub.dev/packages/test/install for more information on adding package:test,
+and https://dart.dev/guides/testing for general information on testing.
+''');
 
-  /// This content has been copied from and kept in sync with
-  /// https://github.com/dart-lang/test, by having a copy in dartdev itself,
-  /// help is faster and more robust, see
-  /// https://github.com/dart-lang/sdk/issues/42014.
-  void generateParser(ArgParser parser) {
-    // Set in test/pkgs/test_core/lib/src/runner/configuration/values.dart:
-    final defaultConcurrency = math.max(1, Platform.numberOfProcessors ~/ 2);
+    if (wasHelpCommand) {
+      log.stdout(_terseHelp);
+      log.stdout('');
+    }
 
-    /// The parser used to parse the command-line arguments.
-    var allRuntimes = Runtime.builtIn.toList()..remove(Runtime.vm);
-    if (!Platform.isMacOS) allRuntimes.remove(Runtime.safari);
-    if (!Platform.isWindows) allRuntimes.remove(Runtime.internetExplorer);
-
-//    parser.addFlag('help',
-//        abbr: 'h', negatable: false, help: 'Shows this usage information.');
-    parser.addFlag('version',
-        negatable: false, help: "Shows the package's version.");
-
-    // Note that defaultsTo declarations here are only for documentation
-    // purposes.
-    // We pass null instead of the default so that it merges properly with the
-    // config file.
-
-    parser.addSeparator('======== Selecting Tests');
-    parser.addMultiOption('name',
-        abbr: 'n',
-        help: 'A substring of the name of the test to run.\n'
-            'Regular expression syntax is supported.\n'
-            'If passed multiple times, tests must match all substrings.',
-        splitCommas: false);
-    parser.addMultiOption('plain-name',
-        abbr: 'N',
-        help: 'A plain-text substring of the name of the test to run.\n'
-            'If passed multiple times, tests must match all substrings.',
-        splitCommas: false);
-    parser.addMultiOption('tags',
-        abbr: 't',
-        help: 'Run only tests with all of the specified tags.\n'
-            'Supports boolean selector syntax.');
-    parser.addMultiOption('tag', hide: true);
-    parser.addMultiOption('exclude-tags',
-        abbr: 'x',
-        help: "Don't run tests with any of the specified tags.\n"
-            'Supports boolean selector syntax.');
-    parser.addMultiOption('exclude-tag', hide: true);
-    parser.addFlag('run-skipped',
-        help: 'Run skipped tests instead of skipping them.');
-
-    parser.addSeparator('======== Running Tests');
-
-    // The UI term "platform" corresponds with the implementation term "runtime".
-    // The [Runtime] class used to be called [TestPlatform], but it was changed to
-    // avoid conflicting with [SuitePlatform]. We decided not to also change the
-    // UI to avoid a painful migration.
-    parser.addMultiOption('platform',
-        abbr: 'p',
-        help: 'The platform(s) on which to run the tests.\n'
-            '[vm (default), '
-            '${allRuntimes.map((runtime) => runtime.identifier).join(", ")}]');
-    parser.addMultiOption('preset',
-        abbr: 'P', help: 'The configuration preset(s) to use.');
-    parser.addOption('concurrency',
-        abbr: 'j',
-        help: 'The number of concurrent test suites run.',
-        defaultsTo: defaultConcurrency.toString(),
-        valueHelp: 'threads');
-    parser.addOption('total-shards',
-        help: 'The total number of invocations of the test runner being run.');
-    parser.addOption('shard-index',
-        help: 'The index of this test runner invocation (of --total-shards).');
-    parser.addOption('pub-serve',
-        help: 'The port of a pub serve instance serving "test/".',
-        valueHelp: 'port');
-    parser.addOption('timeout',
-        help: 'The default test timeout. For example: 15s, 2x, none',
-        defaultsTo: '30s');
-    parser.addFlag('pause-after-load',
-        help: 'Pauses for debugging before any tests execute.\n'
-            'Implies --concurrency=1, --debug, and --timeout=none.\n'
-            'Currently only supported for browser tests.',
-        negatable: false);
-    parser.addFlag('debug',
-        help: 'Runs the VM and Chrome tests in debug mode.', negatable: false);
-    parser.addOption('coverage',
-        help: 'Gathers coverage and outputs it to the specified directory.\n'
-            'Implies --debug.',
-        valueHelp: 'directory');
-    parser.addFlag('chain-stack-traces',
-        help: 'Chained stack traces to provide greater exception details\n'
-            'especially for asynchronous code. It may be useful to disable\n'
-            'to provide improved test performance but at the cost of\n'
-            'debuggability.',
-        defaultsTo: true);
-    parser.addFlag('no-retry',
-        help: "Don't re-run tests that have retry set.",
-        defaultsTo: false,
-        negatable: false);
-    parser.addOption('test-randomize-ordering-seed',
-        help: 'The seed to randomize the execution order of test cases.\n'
-            'Must be a 32bit unsigned integer or "random".\n'
-            'If "random", pick a random seed to use.\n'
-            'If not passed, do not randomize test case execution order.');
-
-    var defaultReporter = 'compact';
-    var reporterDescriptions = <String, String>{
-      'compact': 'A single line, updated continuously.',
-      'expanded': 'A separate line for each update.',
-      'json': 'A machine-readable format (see https://goo.gl/gBsV1a).'
-    };
-
-    parser.addSeparator('======== Output');
-    parser.addOption('reporter',
-        abbr: 'r',
-        help: 'The runner used to print test results.',
-        defaultsTo: defaultReporter,
-        allowed: reporterDescriptions.keys.toList(),
-        allowedHelp: reporterDescriptions);
-    parser.addOption('file-reporter',
-        help: 'The reporter used to write test results to a file.\n'
-            'Should be in the form <reporter>:<filepath>, '
-            'e.g. "json:reports/tests.json"');
-    parser.addFlag('verbose-trace',
-        negatable: false,
-        help: 'Whether to emit stack traces with core library frames.');
-    parser.addFlag('js-trace',
-        negatable: false,
-        help: 'Whether to emit raw JavaScript stack traces for browser tests.');
-    parser.addFlag('color',
-        help: 'Whether to use terminal colors.\n(auto-detected by default)');
-
-    /// The following options are used only by the internal Google test runner.
-    /// They're hidden and not supported as stable API surface outside Google.
-    parser.addOption('configuration',
-        help: 'The path to the configuration file.', hide: true);
-    parser.addOption('dart2js-path',
-        help: 'The path to the dart2js executable.', hide: true);
-    parser.addMultiOption('dart2js-args',
-        help: 'Extra arguments to pass to dart2js.', hide: true);
-
-    // If we're running test/dir/my_test.dart, we'll look for
-    // test/dir/my_test.dart.html in the precompiled directory.
-    parser.addOption('precompiled',
-        help: 'The path to a mirror of the package directory containing HTML '
-            'that points to precompiled JS.',
-        hide: true);
+    log.stdout(_usageHelp);
   }
 }
 
-/// An enum of all Dart runtimes supported by the test runner.
-class Runtime {
-  // When adding new runtimes, be sure to update the baseline and derived
-  // variable tests in test/backend/platform_selector/evaluate_test.
+const String _terseHelp = 'Run tests in this package.';
 
-  /// The command-line Dart VM.
-  static const Runtime vm = Runtime('VM', 'vm', isDartVM: true);
-
-  /// Google Chrome.
-  static const Runtime chrome =
-      Runtime('Chrome', 'chrome', isBrowser: true, isJS: true, isBlink: true);
-
-  /// PhantomJS.
-  static const Runtime phantomJS = Runtime('PhantomJS', 'phantomjs',
-      isBrowser: true, isJS: true, isBlink: true, isHeadless: true);
-
-  /// Mozilla Firefox.
-  static const Runtime firefox =
-      Runtime('Firefox', 'firefox', isBrowser: true, isJS: true);
-
-  /// Apple Safari.
-  static const Runtime safari =
-      Runtime('Safari', 'safari', isBrowser: true, isJS: true);
-
-  /// Microsoft Internet Explorer.
-  static const Runtime internetExplorer =
-      Runtime('Internet Explorer', 'ie', isBrowser: true, isJS: true);
-
-  /// The command-line Node.js VM.
-  static const Runtime nodeJS = Runtime('Node.js', 'node', isJS: true);
-
-  /// The platforms that are supported by the test runner by default.
-  static const List<Runtime> builtIn = [
-    Runtime.vm,
-    Runtime.chrome,
-    Runtime.phantomJS,
-    Runtime.firefox,
-    Runtime.safari,
-    Runtime.internetExplorer,
-    Runtime.nodeJS
-  ];
-
-  /// The human-friendly name of the platform.
-  final String name;
-
-  /// The identifier used to look up the platform.
-  final String identifier;
-
-  /// The parent platform that this is based on, or `null` if there is no
-  /// parent.
-  final Runtime parent;
-
-  /// Returns whether this is a child of another platform.
-  bool get isChild => parent != null;
-
-  /// Whether this platform runs the Dart VM in any capacity.
-  final bool isDartVM;
-
-  /// Whether this platform is a browser.
-  final bool isBrowser;
-
-  /// Whether this platform runs Dart compiled to JavaScript.
-  final bool isJS;
-
-  /// Whether this platform uses the Blink rendering engine.
-  final bool isBlink;
-
-  /// Whether this platform has no visible window.
-  final bool isHeadless;
-
-  /// Returns the platform this is based on, or [this] if it's not based on
-  /// anything.
-  ///
-  /// That is, returns [parent] if it's non-`null` or [this] if it's `null`.
-  Runtime get root => parent ?? this;
-
-  const Runtime(this.name, this.identifier,
-      {this.isDartVM = false,
-      this.isBrowser = false,
-      this.isJS = false,
-      this.isBlink = false,
-      this.isHeadless = false})
-      : parent = null;
-
-  Runtime._child(this.name, this.identifier, Runtime parent)
-      : isDartVM = parent.isDartVM,
-        isBrowser = parent.isBrowser,
-        isJS = parent.isJS,
-        isBlink = parent.isBlink,
-        isHeadless = parent.isHeadless,
-        parent = parent;
-
-  /// Converts a JSON-safe representation generated by [serialize] back into a
-  /// [Runtime].
-  factory Runtime.deserialize(Object serialized) {
-    if (serialized is String) {
-      return builtIn
-          .firstWhere((platform) => platform.identifier == serialized);
-    }
-
-    var map = serialized as Map;
-    var parent = map['parent'];
-    if (parent != null) {
-      // Note that the returned platform's [parent] won't necessarily be `==` to
-      // a separately-deserialized parent platform. This should be fine, though,
-      // since we only deserialize platforms in the remote execution context
-      // where they're only used to evaluate platform selectors.
-      return Runtime._child(map['name'] as String, map['identifier'] as String,
-          Runtime.deserialize(parent as Object));
-    }
-
-    return Runtime(map['name'] as String, map['identifier'] as String,
-        isDartVM: map['isDartVM'] as bool,
-        isBrowser: map['isBrowser'] as bool,
-        isJS: map['isJS'] as bool,
-        isBlink: map['isBlink'] as bool,
-        isHeadless: map['isHeadless'] as bool);
-  }
-
-  /// Converts [this] into a JSON-safe object that can be converted back to a
-  /// [Runtime] using [Runtime.deserialize].
-  Object serialize() {
-    if (builtIn.contains(this)) return identifier;
-
-    if (parent != null) {
-      return {
-        'name': name,
-        'identifier': identifier,
-        'parent': parent.serialize()
-      };
-    }
-
-    return {
-      'name': name,
-      'identifier': identifier,
-      'isDartVM': isDartVM,
-      'isBrowser': isBrowser,
-      'isJS': isJS,
-      'isBlink': isBlink,
-      'isHeadless': isHeadless
-    };
-  }
-
-  /// Returns a child of [this] that counts as both this platform's identifier
-  /// and the new [identifier].
-  ///
-  /// This may not be called on a platform that's already a child.
-  Runtime extend(String name, String identifier) {
-    if (parent == null) return Runtime._child(name, identifier, this);
-    throw StateError('A child platform may not be extended.');
-  }
-
-  @override
-  String toString() => name;
-}
+const String _usageHelp = 'Usage: dart test [files or directories...]';
diff --git a/pkg/dartdev/lib/src/core.dart b/pkg/dartdev/lib/src/core.dart
index 9a05bee..cc3a505 100644
--- a/pkg/dartdev/lib/src/core.dart
+++ b/pkg/dartdev/lib/src/core.dart
@@ -55,7 +55,7 @@
 
   /// Return whether any Dart experiments were specified by the user.
   bool get wereExperimentsSpecified =>
-      globalResults.wasParsed(experimentFlagName);
+      globalResults?.wasParsed(experimentFlagName) ?? false;
 
   /// Return the list of Dart experiment flags specified by the user.
   List<String> get specifiedExperiments => globalResults[experimentFlagName];
@@ -121,6 +121,9 @@
 
   Project.fromDirectory(this.dir);
 
+  bool get hasPubspecFile =>
+      FileSystemEntity.isFileSync(path.join(dir.path, 'pubspec.yaml'));
+
   bool get hasPackageConfigFile => packageConfig != null;
 
   PackageConfig get packageConfig {
diff --git a/pkg/dartdev/test/commands/test_test.dart b/pkg/dartdev/test/commands/test_test.dart
index 304f598..ef2ae06 100644
--- a/pkg/dartdev/test/commands/test_test.dart
+++ b/pkg/dartdev/test/commands/test_test.dart
@@ -2,6 +2,9 @@
 // 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:io';
+
+import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
 
 import '../utils.dart';
@@ -17,32 +20,69 @@
 
   test('--help', () {
     p = project();
-    var result = p.runSync('test', ['--help']);
+
+    var result = p.runSync('pub', ['get']);
     expect(result.exitCode, 0);
+
+    result = p.runSync('test', ['--help']);
+
+    expect(result.exitCode, 0);
+    expect(result.stdout, contains(' tests in this package'));
     expect(result.stderr, isEmpty);
-    expect(result.stdout, contains('Run tests in this package.'));
-    expect(result.stdout, contains('Usage: dart test [arguments]'));
-    expect(result.stdout, contains('======== Selecting Tests'));
   });
 
-  test('no dependency', () {
+  test('dart help test', () {
+    p = project();
+
+    var result = p.runSync('pub', ['get']);
+    expect(result.exitCode, 0);
+
+    result = p.runSync('help', ['test']);
+
+    expect(result.exitCode, 0);
+    expect(result.stdout, contains(' tests in this package'));
+    expect(result.stderr, isEmpty);
+  });
+
+  test('no pubspec.yaml', () {
+    p = project();
+    var pubspec = File(path.join(p.dirPath, 'pubspec.yaml'));
+    pubspec.deleteSync();
+
+    var result = p.runSync('help', ['test']);
+
+    expect(result.exitCode, 0);
+    expect(result.stdout, contains('No pubspec.yaml file found'));
+    expect(result.stderr, isEmpty);
+  });
+
+  test('no .dart_tool/package_config.json', () {
+    p = project();
+
+    var result = p.runSync('help', ['test']);
+
+    expect(result.exitCode, 0);
+    expect(result.stdout,
+        contains('No .dart_tool/package_config.json file found'));
+    expect(result.stderr, isEmpty);
+  });
+
+  test('no package:test dependency', () {
     p = project(mainSrc: 'int get foo => 1;\n');
     p.file('pubspec.yaml', 'name: ${p.name}\n');
 
-    var result = p.runSync('pub', ['get', '--offline']);
+    var result = p.runSync('pub', ['get']);
     expect(result.exitCode, 0);
 
     result = p.runSync('test', []);
     expect(result.exitCode, 65);
     expect(
       result.stdout,
-      contains(
-        'In order to run tests, you need to add a dependency on package:test',
-      ),
+      contains('In order to run tests, you need to add a dependency'),
     );
-  }, skip: 'https://github.com/dart-lang/sdk/issues/40854');
+  });
 
-  test('has dependency', () {
+  test('has package:test dependency', () {
     p = project(mainSrc: 'int get foo => 1;\n');
     p.file('test/foo_test.dart', '''
 import 'package:test/test.dart';
@@ -54,14 +94,14 @@
 }
 ''');
 
-    var result = p.runSync('pub', ['get', '--offline']);
+    var result = p.runSync('pub', ['get']);
     expect(result.exitCode, 0);
 
     result = p.runSync('test', ['--no-color', '--reporter', 'expanded']);
     expect(result.exitCode, 0);
     expect(result.stdout, contains('All tests passed!'));
     expect(result.stderr, isEmpty);
-  }, skip: 'https://github.com/dart-lang/sdk/issues/40854');
+  });
 
   test('--enable-experiment', () {
     p = project(mainSrc: 'int get foo => 1;\n');
@@ -77,11 +117,11 @@
 }
 ''');
 
-    var result = p.runSync('pub', ['get', '--offline']);
+    var result = p.runSync('pub', ['get']);
     expect(result.exitCode, 0);
 
     result = p.runSync('--enable-experiment=non-nullable',
         ['test', '--no-color', '--reporter', 'expanded']);
     expect(result.exitCode, 1);
-  }, skip: 'https://github.com/dart-lang/sdk/issues/40854');
+  });
 }
diff --git a/pkg/dartdev/test/core_test.dart b/pkg/dartdev/test/core_test.dart
index 6384d9b..bc10cb9 100644
--- a/pkg/dartdev/test/core_test.dart
+++ b/pkg/dartdev/test/core_test.dart
@@ -3,8 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:convert';
+import 'dart:io';
 
 import 'package:dartdev/src/core.dart';
+import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
 
 import 'utils.dart';
@@ -32,10 +34,19 @@
 
   tearDown(() => p?.dispose());
 
-  test('hasPackageConfigFile negative', () {
+  test('hasPubspecFile positive', () {
     p = project();
     Project coreProj = Project.fromDirectory(p.dir);
-    expect(coreProj.hasPackageConfigFile, isFalse);
+    expect(coreProj.hasPubspecFile, isTrue);
+  });
+
+  test('hasPubspecFile negative', () {
+    p = project();
+    var pubspec = File(path.join(p.dirPath, 'pubspec.yaml'));
+    pubspec.deleteSync();
+
+    Project coreProj = Project.fromDirectory(p.dir);
+    expect(coreProj.hasPubspecFile, isFalse);
   });
 
   test('hasPackageConfigFile positive', () {
@@ -46,6 +57,12 @@
     expect(coreProj.packageConfig, isNotNull);
     expect(coreProj.packageConfig.packages, isNotEmpty);
   });
+
+  test('hasPackageConfigFile negative', () {
+    p = project();
+    Project coreProj = Project.fromDirectory(p.dir);
+    expect(coreProj.hasPackageConfigFile, isFalse);
+  });
 }
 
 const String _packageData = '''{
diff --git a/pkg/dds/CHANGELOG.md b/pkg/dds/CHANGELOG.md
index 52b7282..96848ba 100644
--- a/pkg/dds/CHANGELOG.md
+++ b/pkg/dds/CHANGELOG.md
@@ -1,5 +1,6 @@
-# 1.3.2-dev
+# 1.3.2
 
+- Add IPv6 hosting support.
 - Fix handling of requests that are outstanding when a client channel is closed.
 
 # 1.3.1
diff --git a/pkg/dds/lib/dds.dart b/pkg/dds/lib/dds.dart
index f9c057c..aeb39a9 100644
--- a/pkg/dds/lib/dds.dart
+++ b/pkg/dds/lib/dds.dart
@@ -51,7 +51,8 @@
   /// development service will communicate with.
   ///
   /// If provided, [serviceUri] will determine the address and port of the
-  /// spawned Dart Development Service.
+  /// spawned Dart Development Service. The format of [serviceUri] must be
+  /// consistent with the protocol determined by [ipv6].
   ///
   /// [enableAuthCodes] controls whether or not an authentication code must
   /// be provided by clients when communicating with this instance of
@@ -59,10 +60,14 @@
   /// encoded string provided as the first element of the DDS path and is meant
   /// to make it more difficult for unintended clients to connect to this
   /// service. Authentication codes are enabled by default.
+  ///
+  /// [ipv6] controls whether or not DDS is served via IPv6. IPv4 is enabled by
+  /// default.
   static Future<DartDevelopmentService> startDartDevelopmentService(
     Uri remoteVmServiceUri, {
     Uri serviceUri,
     bool enableAuthCodes = true,
+    bool ipv6 = false,
   }) async {
     if (remoteVmServiceUri == null) {
       throw ArgumentError.notNull('remoteVmServiceUri');
@@ -72,15 +77,33 @@
         'remoteVmServiceUri must have an HTTP scheme. Actual: ${remoteVmServiceUri.scheme}',
       );
     }
-    if (serviceUri != null && serviceUri.scheme != 'http') {
-      throw ArgumentError(
-        'serviceUri must have an HTTP scheme. Actual: ${serviceUri.scheme}',
+    if (serviceUri != null) {
+      if (serviceUri.scheme != 'http') {
+        throw ArgumentError(
+          'serviceUri must have an HTTP scheme. Actual: ${serviceUri.scheme}',
+        );
+      }
+
+      // If provided an address to bind to, ensure it uses a protocol consistent
+      // with that used to spawn DDS.
+      final addresses = await InternetAddress.lookup(serviceUri.host);
+      final address = addresses.firstWhere(
+        (a) => (a.type ==
+            (ipv6 ? InternetAddressType.IPv6 : InternetAddressType.IPv4)),
+        orElse: () => null,
       );
+      if (address == null) {
+        throw ArgumentError(
+          "serviceUri '$serviceUri' is not an IPv${ipv6 ? "6" : "4"} address.",
+        );
+      }
     }
+
     final service = _DartDevelopmentService(
       remoteVmServiceUri,
       serviceUri,
       enableAuthCodes,
+      ipv6,
     );
     await service.startService();
     return service;
diff --git a/pkg/dds/lib/src/dds_impl.dart b/pkg/dds/lib/src/dds_impl.dart
index 55e9e64..192e9ad5 100644
--- a/pkg/dds/lib/src/dds_impl.dart
+++ b/pkg/dds/lib/src/dds_impl.dart
@@ -6,10 +6,7 @@
 
 class _DartDevelopmentService implements DartDevelopmentService {
   _DartDevelopmentService(
-    this._remoteVmServiceUri,
-    this._uri,
-    this._authCodesEnabled,
-  ) {
+      this._remoteVmServiceUri, this._uri, this._authCodesEnabled, this._ipv6) {
     _clientManager = _ClientManager(this);
     _expressionEvaluator = _ExpressionEvaluator(this);
     _isolateManager = _IsolateManager(this);
@@ -38,8 +35,9 @@
 
   Future<void> _startDDSServer() async {
     // No provided address, bind to an available port on localhost.
-    // TODO(bkonyi): handle case where there's no IPv4 loopback.
-    final host = uri?.host ?? InternetAddress.loopbackIPv4.host;
+    final host = uri?.host ??
+        (_ipv6 ? InternetAddress.loopbackIPv6 : InternetAddress.loopbackIPv4)
+            .host;
     final port = uri?.port ?? 0;
 
     // Start the DDS server.
@@ -226,6 +224,8 @@
   Uri get wsUri => _toWebSocket(_uri);
   Uri _uri;
 
+  final bool _ipv6;
+
   bool get isRunning => _uri != null;
 
   Future<void> get done => _done.future;
diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml
index e1d0d3d..447170c 100644
--- a/pkg/dds/pubspec.yaml
+++ b/pkg/dds/pubspec.yaml
@@ -3,7 +3,7 @@
   A library used to spawn the Dart Developer Service, used to communicate with
   a Dart VM Service instance.
 
-version: 1.3.2-dev
+version: 1.3.2
 
 homepage: https://github.com/dart-lang/sdk/tree/master/pkg/dds
 
diff --git a/pkg/dds/test/smoke_test.dart b/pkg/dds/test/smoke_test.dart
index 0da26db..0a7347c 100644
--- a/pkg/dds/test/smoke_test.dart
+++ b/pkg/dds/test/smoke_test.dart
@@ -27,16 +27,26 @@
       process = null;
     });
 
-    void createSmokeTest(bool useAuthCodes) {
+    void createSmokeTest(bool useAuthCodes, bool ipv6) {
+      final protocol = ipv6 ? 'IPv6' : 'IPv4';
       test(
-        'Smoke Test with ${useAuthCodes ? "" : "no "} authentication codes',
+        'Smoke Test with ${useAuthCodes ? "" : "no"} authentication codes '
+        'with $protocol',
         () async {
           dds = await DartDevelopmentService.startDartDevelopmentService(
             remoteVmServiceUri,
             enableAuthCodes: useAuthCodes,
+            ipv6: ipv6,
           );
           expect(dds.isRunning, true);
 
+          try {
+            Uri.parseIPv6Address(dds.uri.host);
+            expect(ipv6, true);
+          } on FormatException {
+            expect(ipv6, false);
+          }
+
           // Ensure basic websocket requests are forwarded correctly to the VM service.
           final service = await vmServiceConnectUri(dds.wsUri.toString());
           final version = await service.getVersion();
@@ -69,8 +79,9 @@
       );
     }
 
-    createSmokeTest(true);
-    createSmokeTest(false);
+    createSmokeTest(true, false);
+    createSmokeTest(false, false);
+    createSmokeTest(true, true);
 
     test('startup fails when VM service has existing clients', () async {
       Uri httpToWebSocketUri(Uri httpUri) {
@@ -121,5 +132,23 @@
               serviceUri: Uri.parse('dart-lang://localhost:2345'),
             ),
         throwsA(TypeMatcher<ArgumentError>()));
+
+    // Protocol mismatch
+    expect(
+        () async => await DartDevelopmentService.startDartDevelopmentService(
+              Uri.parse('http://localhost:1234'),
+              serviceUri: Uri.parse('http://127.0.0.1:2345'),
+              ipv6: true,
+            ),
+        throwsA(TypeMatcher<ArgumentError>()));
+
+    // Protocol mismatch
+    expect(
+        () async => await DartDevelopmentService.startDartDevelopmentService(
+              Uri.parse('http://localhost:1234'),
+              serviceUri: Uri.parse('http://[::1]:2345'),
+              ipv6: false,
+            ),
+        throwsA(TypeMatcher<ArgumentError>()));
   });
 }
diff --git a/runtime/vm/service_event.cc b/runtime/vm/service_event.cc
index 18e06f9..00d7ba3 100644
--- a/runtime/vm/service_event.cc
+++ b/runtime/vm/service_event.cc
@@ -34,11 +34,18 @@
       bytes_(NULL),
       bytes_length_(0),
       timestamp_(OS::GetCurrentTimeMillis()) {
-  // We should never generate events for the vm or service isolates.
+  // We should never generate events for the vm isolate as it is never reported over the service.
   ASSERT(isolate_ != Dart::vm_isolate());
+
+  // System isolates should never post service events. However, the Isolate service object uses a
+  // service event to represent the current running state of the isolate, so we need to allow for
+  // system isolates to create resume and none events for this purpose. The resume event represents
+  // a running isolate and the none event is returned for an isolate that has not yet been marked as
+  // runnable (see "pauseEvent" in Isolate::PrintJSON).
   ASSERT(isolate == NULL || !Isolate::IsSystemIsolate(isolate) ||
          (Isolate::IsSystemIsolate(isolate) &&
-          event_kind == ServiceEvent::kResume));
+          (event_kind == ServiceEvent::kResume ||
+           event_kind == ServiceEvent::kNone)));
 
   if ((event_kind == ServiceEvent::kPauseStart) ||
       (event_kind == ServiceEvent::kPauseExit)) {
diff --git a/tools/VERSION b/tools/VERSION
index be4759b..59af991 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 10
 PATCH 0
-PRERELEASE 95
+PRERELEASE 96
 PRERELEASE_PATCH 0
\ No newline at end of file