migrate test/descriptor/ and couple more files in test/ to null-safety (#3192)

diff --git a/lib/src/command/global_run.dart b/lib/src/command/global_run.dart
index b8f7f1b..71e0a9d 100644
--- a/lib/src/command/global_run.dart
+++ b/lib/src/command/global_run.dart
@@ -17,8 +17,7 @@
   String get name => 'run';
   @override
   String get description =>
-      'Run an executable from a globally activated package.\n'
-      "NOTE: We are currently optimizing this command's startup time.";
+      'Run an executable from a globally activated package.';
   @override
   String get argumentsDescription => '<package>:<executable> [args...]';
   @override
diff --git a/test/descriptor.dart b/test/descriptor.dart
index 3eee5d1..aa95ca6 100644
--- a/test/descriptor.dart
+++ b/test/descriptor.dart
@@ -2,12 +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.
 
-// @dart=2.10
-
 /// Pub-specific test descriptors.
 import 'dart:convert';
 
-import 'package:meta/meta.dart';
 import 'package:oauth2/oauth2.dart' as oauth2;
 import 'package:path/path.dart' as p;
 import 'package:pub/src/language_version.dart';
@@ -27,11 +24,11 @@
 export 'descriptor/tar.dart';
 
 /// Creates a new [GitRepoDescriptor] with [name] and [contents].
-GitRepoDescriptor git(String name, [Iterable<Descriptor> contents]) =>
+GitRepoDescriptor git(String name, [List<Descriptor>? contents]) =>
     GitRepoDescriptor(name, contents ?? <Descriptor>[]);
 
 /// Creates a new [TarFileDescriptor] with [name] and [contents].
-TarFileDescriptor tar(String name, [Iterable<Descriptor> contents]) =>
+TarFileDescriptor tar(String name, [List<Descriptor>? contents]) =>
     TarFileDescriptor(name, contents ?? <Descriptor>[]);
 
 /// Describes a package that passes all validation.
@@ -74,7 +71,7 @@
         // TODO: Copy-pasting this into all call-sites, or use d.libPubspec
         'environment': {
           'sdk': '>=0.1.2 <1.0.0',
-          ...contents['environment'] as Map ?? {},
+          ...(contents['environment'] ?? {}) as Map,
         },
       }),
     );
@@ -84,8 +81,8 @@
 
 /// Describes a file named `pubspec.yaml` for an application package with the
 /// given [dependencies].
-Descriptor appPubspec([Map dependencies]) {
-  var map = <String, dynamic>{
+Descriptor appPubspec([Map? dependencies]) {
+  var map = <String, Object>{
     'name': 'myapp',
     'environment': {
       'sdk': '>=0.1.2 <1.0.0',
@@ -100,7 +97,7 @@
 /// constraint on that version, otherwise it adds an SDK constraint allowing
 /// the current SDK version.
 Descriptor libPubspec(String name, String version,
-    {Map deps, Map devDeps, String sdk}) {
+    {Map? deps, Map? devDeps, String? sdk}) {
   var map = packageMap(name, version, deps, devDeps);
   if (sdk != null) {
     map['environment'] = {'sdk': sdk};
@@ -112,7 +109,7 @@
 
 /// Describes a directory named `lib` containing a single dart file named
 /// `<name>.dart` that contains a line of Dart code.
-Descriptor libDir(String name, [String code]) {
+Descriptor libDir(String name, [String? code]) {
   // Default to printing the name if no other code was given.
   code ??= name;
   return dir('lib', [file('$name.dart', 'main() => "$code";')]);
@@ -130,8 +127,8 @@
 /// If [repoName] is not given it is assumed to be equal to [packageName].
 Descriptor gitPackageRevisionCacheDir(
   String packageName, {
-  int modifier,
-  String repoName,
+  int? modifier,
+  String? repoName,
 }) {
   repoName = repoName ?? packageName;
   var value = packageName;
@@ -159,7 +156,7 @@
 /// validated since they will often lack the dependencies section that the
 /// real pubspec being compared against has. You usually only need to pass
 /// `true` for this if you plan to call [create] on the resulting descriptor.
-Descriptor cacheDir(Map packages, {int port, bool includePubspecs = false}) {
+Descriptor cacheDir(Map packages, {int? port, bool includePubspecs = false}) {
   var contents = <Descriptor>[];
   packages.forEach((name, versions) {
     if (versions is! List) versions = [versions];
@@ -180,9 +177,9 @@
 ///
 /// If [port] is passed, it's used as the port number of the local hosted server
 /// that this cache represents. It defaults to [globalServer.port].
-Descriptor hostedCache(Iterable<Descriptor> contents, {int port}) {
+Descriptor hostedCache(Iterable<Descriptor> contents, {int? port}) {
   return dir(cachePath, [
-    dir('hosted', [dir('localhost%58${port ?? globalServer.port}', contents)])
+    dir('hosted', [dir('localhost%58${port ?? globalServer?.port}', contents)])
   ]);
 }
 
@@ -190,7 +187,7 @@
 /// credentials. The URL "/token" on [server] will be used as the token
 /// endpoint for refreshing the access token.
 Descriptor credentialsFile(PackageServer server, String accessToken,
-    {String refreshToken, DateTime expiration}) {
+    {String? refreshToken, DateTime? expiration}) {
   return dir(
     configPath,
     [
@@ -208,7 +205,7 @@
 }
 
 Descriptor legacyCredentialsFile(PackageServer server, String accessToken,
-    {String refreshToken, DateTime expiration}) {
+    {String? refreshToken, DateTime? expiration}) {
   return dir(
     cachePath,
     [
@@ -228,8 +225,8 @@
 String _credentialsFileContent(
   PackageServer server,
   String accessToken, {
-  String refreshToken,
-  DateTime expiration,
+  String? refreshToken,
+  DateTime? expiration,
 }) =>
     oauth2.Credentials(
       accessToken,
@@ -245,14 +242,12 @@
 /// Describes the file in the system cache that contains credentials for
 /// third party hosted pub servers.
 Descriptor tokensFile([Map<String, dynamic> contents = const {}]) {
-  return dir(configPath, [
-    file('pub-tokens.json', contents != null ? jsonEncode(contents) : null)
-  ]);
+  return dir(configPath, [file('pub-tokens.json', jsonEncode(contents))]);
 }
 
 /// Describes the application directory, containing only a pubspec specifying
 /// the given [dependencies].
-DirectoryDescriptor appDir([Map dependencies]) =>
+DirectoryDescriptor appDir([Map? dependencies]) =>
     dir(appPath, [appPubspec(dependencies)]);
 
 /// Describes a `.packages` file.
@@ -266,7 +261,7 @@
 /// entries (one per key in [dependencies]), each with a path that contains
 /// either the version string (for a reference to the pub cache) or a
 /// path to a path dependency, relative to the application directory.
-Descriptor packagesFile([Map<String, String> dependencies]) =>
+Descriptor packagesFile(Map<String, String> dependencies) =>
     PackagesFileDescriptor(dependencies);
 
 /// Describes a `.dart_tools/package_config.json` file.
@@ -285,10 +280,10 @@
 /// Create a [PackageConfigEntry] which assumes package with [name] is either
 /// a cached package with given [version] or a path dependency at given [path].
 PackageConfigEntry packageConfigEntry({
-  @required String name,
-  String version,
-  String path,
-  String languageVersion,
+  required String name,
+  String? version,
+  String? path,
+  String? languageVersion,
 }) {
   if (version != null && path != null) {
     throw ArgumentError.value(
@@ -300,7 +295,7 @@
   }
   Uri rootUri;
   if (version != null) {
-    rootUri = p.toUri(globalPackageServer.pathInCache(name, version));
+    rootUri = p.toUri(globalPackageServer!.pathInCache(name, version));
   } else {
     rootUri = p.toUri(p.join('..', path));
   }
diff --git a/test/descriptor/git.dart b/test/descriptor/git.dart
index 56cd59f..edfc7bd 100644
--- a/test/descriptor/git.dart
+++ b/test/descriptor/git.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as path;
@@ -17,7 +15,7 @@
 
   /// Creates the Git repository and commits the contents.
   @override
-  Future create([String parent]) async {
+  Future create([String? parent]) async {
     await super.create(parent);
     await _runGitCommands(parent, [
       ['init'],
@@ -35,7 +33,7 @@
   /// the previous structure to the Git repo.
   ///
   /// [parent] defaults to [sandbox].
-  Future commit([String parent]) async {
+  Future commit([String? parent]) async {
     await super.create(parent);
     await _runGitCommands(parent, [
       ['add', '.'],
@@ -47,7 +45,7 @@
   /// referred to by [ref].
   ///
   /// [parent] defaults to [sandbox].
-  Future<String> revParse(String ref, [String parent]) async {
+  Future<String> revParse(String ref, [String? parent]) async {
     var output = await _runGit(['rev-parse', ref], parent);
     return output[0];
   }
@@ -55,9 +53,9 @@
   /// Runs a Git command in this repository.
   ///
   /// [parent] defaults to [sandbox].
-  Future runGit(List<String> args, [String parent]) => _runGit(args, parent);
+  Future runGit(List<String> args, [String? parent]) => _runGit(args, parent);
 
-  Future<List<String>> _runGit(List<String> args, String parent) {
+  Future<List<String>> _runGit(List<String> args, String? parent) {
     // Explicitly specify the committer information. Git needs this to commit
     // and we don't want to rely on the buildbots having this already set up.
     var environment = {
@@ -72,7 +70,7 @@
         environment: environment);
   }
 
-  Future _runGitCommands(String parent, List<List<String>> commands) async {
+  Future _runGitCommands(String? parent, List<List<String>> commands) async {
     for (var command in commands) {
       await _runGit(command, parent);
     }
diff --git a/test/descriptor/packages.dart b/test/descriptor/packages.dart
index 2a23e40..42ea20a 100644
--- a/test/descriptor/packages.dart
+++ b/test/descriptor/packages.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async' show Future;
 import 'dart:convert' show JsonEncoder, json, utf8;
 import 'dart:io' show File;
@@ -25,7 +23,7 @@
 /// Describes a `.packages` file and its contents.
 class PackagesFileDescriptor extends Descriptor {
   /// A map from package names to either version strings or path to the package.
-  final Map<String, String> _dependencies;
+  final Map<String, String>? _dependencies;
 
   /// Describes a `.packages` file with the given dependencies.
   ///
@@ -34,11 +32,12 @@
   PackagesFileDescriptor([this._dependencies]) : super('.packages');
 
   @override
-  Future create([String parent]) {
+  Future create([String? parent]) {
     var contents = const <int>[];
-    if (_dependencies != null) {
+    var dependencies = _dependencies;
+    if (dependencies != null) {
       var mapping = <String, Uri>{};
-      _dependencies.forEach((package, version) {
+      dependencies.forEach((package, version) {
         String packagePath;
         if (_isSemver(version)) {
           // It's a cache reference.
@@ -58,7 +57,7 @@
   }
 
   @override
-  Future validate([String parent]) async {
+  Future validate([String? parent]) async {
     var fullPath = p.join(parent ?? sandbox, name);
     if (!await File(fullPath).exists()) {
       fail("File not found: '$fullPath'.");
@@ -68,14 +67,16 @@
 
     var map = packages_file.parse(bytes, Uri.parse(_base));
 
-    for (var package in _dependencies.keys) {
+    var dependencies = _dependencies!;
+
+    for (var package in dependencies.keys) {
       if (!map.containsKey(package)) {
         fail('.packages does not contain $package entry');
       }
 
-      var description = _dependencies[package];
+      var description = dependencies[package]!;
       if (_isSemver(description)) {
-        if (!map[package].path.contains(description)) {
+        if (!map[package]!.path.contains(description)) {
           fail('.packages of $package has incorrect version. '
               'Expected $description, found location: ${map[package]}.');
         }
@@ -90,9 +91,9 @@
       }
     }
 
-    if (map.length != _dependencies.length) {
+    if (map.length != dependencies.length) {
       for (var key in map.keys) {
-        if (!_dependencies.containsKey(key)) {
+        if (!dependencies.containsKey(key)) {
           fail('.packages file contains unexpected entry: $key');
         }
       }
@@ -128,7 +129,7 @@
       : super('.dart_tool/package_config.json');
 
   @override
-  Future<void> create([String parent]) async {
+  Future<void> create([String? parent]) async {
     final packageConfigFile = File(p.join(parent ?? sandbox, name));
     await packageConfigFile.parent.create();
     await packageConfigFile.writeAsString(
@@ -137,7 +138,7 @@
   }
 
   @override
-  Future<void> validate([String parent]) async {
+  Future<void> validate([String? parent]) async {
     final packageConfigFile = p.join(parent ?? sandbox, name);
     if (!await File(packageConfigFile).exists()) {
       fail("File not found: '$packageConfigFile'.");
diff --git a/test/descriptor/tar.dart b/test/descriptor/tar.dart
index 1c4f89f..2a3488f 100644
--- a/test/descriptor/tar.dart
+++ b/test/descriptor/tar.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 
@@ -23,7 +21,7 @@
   /// Creates the files and directories within this tar file, then archives
   /// them, compresses them, and saves the result to [parentDir].
   @override
-  Future create([String parent]) {
+  Future create([String? parent]) {
     return withTempDir((tempDir) async {
       await Future.wait(contents.map((entry) => entry.create(tempDir)));
 
@@ -41,7 +39,7 @@
   /// Validates that the `.tar.gz` file at [path] contains the expected
   /// contents.
   @override
-  Future validate([String parent]) {
+  Future validate([String? parent]) {
     throw UnimplementedError('TODO(nweiz): implement this');
   }
 
diff --git a/test/descriptor/yaml.dart b/test/descriptor/yaml.dart
index 086da84..6840041 100644
--- a/test/descriptor/yaml.dart
+++ b/test/descriptor/yaml.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async' show Future;
 import 'dart:convert' show utf8;
 import 'dart:io';
@@ -29,7 +27,7 @@
       Stream.fromIterable([utf8.encode(_contents)]);
 
   @override
-  Future validate([String parent]) async {
+  Future validate([String? parent]) async {
     var fullPath = p.join(parent ?? sandbox, name);
     if (!await File(fullPath).exists()) {
       fail("File not found: '$fullPath'.");
diff --git a/test/descriptor_server.dart b/test/descriptor_server.dart
index a023518..973a49c 100644
--- a/test/descriptor_server.dart
+++ b/test/descriptor_server.dart
@@ -2,10 +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.
 
-// @dart=2.10
-
 import 'dart:async';
 
+import 'package:collection/collection.dart' show IterableExtension;
 import 'package:path/path.dart' as p;
 import 'package:shelf/shelf.dart' as shelf;
 import 'package:shelf/shelf_io.dart' as shelf_io;
@@ -17,26 +16,27 @@
 ///
 /// `null` if there's no global server in use. This can be set to replace the
 /// existing global server.
-DescriptorServer get globalServer => _globalServer;
-set globalServer(DescriptorServer value) {
-  if (_globalServer == null) {
+DescriptorServer? get globalServer => _globalServer;
+set globalServer(DescriptorServer? value) {
+  var server = _globalServer;
+  if (server == null) {
     addTearDown(() {
       _globalServer = null;
     });
   } else {
-    expect(_globalServer.close(), completes);
+    expect(server.close(), completes);
   }
 
   _globalServer = value;
 }
 
-DescriptorServer _globalServer;
+DescriptorServer? _globalServer;
 
 /// Creates a global [DescriptorServer] to serve [contents] as static files.
 ///
 /// This server will exist only for the duration of the pub run. It's accessible
 /// via [server]. Subsequent calls to [serve] replace the previous server.
-Future serve([List<d.Descriptor> contents]) async {
+Future serve([List<d.Descriptor> contents = const []]) async {
   globalServer = (await DescriptorServer.start())..contents.addAll(contents);
 }
 
@@ -75,11 +75,11 @@
   DescriptorServer._(this._server) : _baseDir = d.dir('serve-dir', []) {
     _server.mount((request) async {
       final pathWithInitialSlash = '/${request.url.path}';
-      final key = extraHandlers.keys.firstWhere((pattern) {
+      final key = extraHandlers.keys.firstWhereOrNull((pattern) {
         final match = pattern.matchAsPrefix(pathWithInitialSlash);
         return match != null && match.end == pathWithInitialSlash.length;
-      }, orElse: () => null);
-      if (key != null) return extraHandlers[key](request);
+      });
+      if (key != null) return extraHandlers[key]!(request);
 
       var path = p.posix.fromUri(request.url.path);
       requestedPaths.add(path);
@@ -114,12 +114,12 @@
   var completer = Completer<Stream<T>>();
   var controller = StreamController<T>(sync: true);
 
-  StreamSubscription subscription;
+  late StreamSubscription subscription;
   subscription = stream.listen((value) {
     // We got a value, so the stream is valid.
     if (!completer.isCompleted) completer.complete(controller.stream);
     controller.add(value);
-  }, onError: (error, [StackTrace stackTrace]) {
+  }, onError: (error, [StackTrace? stackTrace]) {
     // If the error came after values, it's OK.
     if (completer.isCompleted) {
       controller.addError(error, stackTrace);
diff --git a/test/package_server.dart b/test/package_server.dart
index ec960ca..12b542a 100644
--- a/test/package_server.dart
+++ b/test/package_server.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
 
@@ -17,17 +15,17 @@
 import 'test_pub.dart';
 
 /// The current global [PackageServer].
-PackageServer get globalPackageServer => _globalPackageServer;
-PackageServer _globalPackageServer;
+PackageServer? get globalPackageServer => _globalPackageServer;
+PackageServer? _globalPackageServer;
 
 /// Creates an HTTP server that replicates the structure of pub.dartlang.org and
 /// makes it the current [globalServer].
 ///
 /// Calls [callback] with a [PackageServerBuilder] that's used to specify
 /// which packages to serve.
-Future servePackages([void Function(PackageServerBuilder) callback]) async {
+Future servePackages([void Function(PackageServerBuilder)? callback]) async {
   _globalPackageServer = await PackageServer.start(callback ?? (_) {});
-  globalServer = _globalPackageServer._inner;
+  globalServer = _globalPackageServer!._inner;
 
   addTearDown(() {
     _globalPackageServer = null;
@@ -44,10 +42,12 @@
 ///
 /// If no server has been set up, an empty server will be started.
 Future serveErrors() async {
-  if (globalPackageServer == null) {
+  var packageServer = globalPackageServer;
+  if (packageServer == null) {
     await serveNoPackages();
+  } else {
+    packageServer.serveErrors();
   }
-  globalPackageServer.serveErrors();
 }
 
 class PackageServer {
@@ -72,7 +72,7 @@
   /// package to serve.
   ///
   /// This is preserved so that additional packages can be added.
-  PackageServerBuilder _builder;
+  late final PackageServerBuilder _builder;
 
   /// The port used for the server.
   int get port => _inner.port;
@@ -197,9 +197,9 @@
   /// If [contents] is passed, it's used as the contents of the package. By
   /// default, a package just contains a dummy lib directory.
   void serve(String name, String version,
-      {Map<String, dynamic> deps,
-      Map<String, dynamic> pubspec,
-      Iterable<d.Descriptor> contents}) {
+      {Map<String, dynamic>? deps,
+      Map<String, dynamic>? pubspec,
+      List<d.Descriptor>? contents}) {
     var pubspecFields = <String, dynamic>{'name': name, 'version': version};
     if (pubspec != null) pubspecFields.addAll(pubspec);
     if (deps != null) pubspecFields['dependencies'] = deps;
@@ -213,8 +213,8 @@
 
   // Mark a package discontinued.
   void discontinue(String name,
-      {bool isDiscontinued = true, String replacementText}) {
-    _packages[name]
+      {bool isDiscontinued = true, String? replacementText}) {
+    _packages[name]!
       ..isDiscontinued = isDiscontinued
       ..discontinuedReplacementText = replacementText;
   }
@@ -225,14 +225,14 @@
   }
 
   void retractPackageVersion(String name, String version) {
-    _packages[name].versions[version].isRetracted = true;
+    _packages[name]!.versions[version]!.isRetracted = true;
   }
 }
 
 class _ServedPackage {
   final versions = <String, _ServedPackageVersion>{};
   bool isDiscontinued = false;
-  String discontinuedReplacementText;
+  String? discontinuedReplacementText;
 }
 
 /// A package that's intended to be served.
diff --git a/test/test_pub.dart b/test/test_pub.dart
index 2d68233..079a0a9 100644
--- a/test/test_pub.dart
+++ b/test/test_pub.dart
@@ -2,15 +2,13 @@
 // 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.
 
-// @dart=2.10
-
 /// Test infrastructure for testing pub.
 ///
 /// Unlike typical unit tests, most pub tests are integration tests that stage
 /// some stuff on the file system, run pub, and then validate the results. This
 /// library provides an API to build tests like that.
-import 'dart:async';
 import 'dart:convert';
+import 'dart:core';
 import 'dart:io';
 import 'dart:isolate';
 import 'dart:math';
@@ -28,7 +26,6 @@
 import 'package:pub/src/io.dart';
 import 'package:pub/src/lock_file.dart';
 import 'package:pub/src/log.dart' as log;
-import 'package:pub/src/sdk.dart';
 import 'package:pub/src/source_registry.dart';
 import 'package:pub/src/system_cache.dart';
 import 'package:pub/src/utils.dart';
@@ -82,7 +79,7 @@
         orElse: () => null) as Map<String, dynamic>;
 
 /// The suffix appended to a built snapshot.
-final versionSuffix = testVersion ?? sdk.version;
+final versionSuffix = testVersion;
 
 /// Enum identifying a pub command that can be run with a well-defined success
 /// output.
@@ -130,14 +127,14 @@
 // TODO(rnystrom): Clean up other tests to call this when possible.
 Future<void> pubCommand(
   RunCommand command, {
-  Iterable<String> args,
+  Iterable<String>? args,
   output,
   error,
   silent,
   warning,
-  int exitCode,
-  Map<String, String> environment,
-  String workingDirectory,
+  int? exitCode,
+  Map<String, String>? environment,
+  String? workingDirectory,
   includeParentEnvironment = true,
 }) async {
   if (error != null && warning != null) {
@@ -167,13 +164,13 @@
 }
 
 Future<void> pubAdd({
-  Iterable<String> args,
+  Iterable<String>? args,
   output,
   error,
   warning,
-  int exitCode,
-  Map<String, String> environment,
-  String workingDirectory,
+  int? exitCode,
+  Map<String, String>? environment,
+  String? workingDirectory,
 }) async =>
     await pubCommand(
       RunCommand.add,
@@ -187,13 +184,13 @@
     );
 
 Future<void> pubGet({
-  Iterable<String> args,
+  Iterable<String>? args,
   output,
   error,
   warning,
-  int exitCode,
-  Map<String, String> environment,
-  String workingDirectory,
+  int? exitCode,
+  Map<String, String>? environment,
+  String? workingDirectory,
   bool includeParentEnvironment = true,
 }) async =>
     await pubCommand(
@@ -209,13 +206,13 @@
     );
 
 Future<void> pubUpgrade(
-        {Iterable<String> args,
+        {Iterable<String>? args,
         output,
         error,
         warning,
-        int exitCode,
-        Map<String, String> environment,
-        String workingDirectory}) async =>
+        int? exitCode,
+        Map<String, String>? environment,
+        String? workingDirectory}) async =>
     await pubCommand(
       RunCommand.upgrade,
       args: args,
@@ -228,13 +225,13 @@
     );
 
 Future<void> pubDowngrade({
-  Iterable<String> args,
+  Iterable<String>? args,
   output,
   error,
   warning,
-  int exitCode,
-  Map<String, String> environment,
-  String workingDirectory,
+  int? exitCode,
+  Map<String, String>? environment,
+  String? workingDirectory,
 }) async =>
     await pubCommand(
       RunCommand.downgrade,
@@ -248,13 +245,13 @@
     );
 
 Future<void> pubRemove({
-  Iterable<String> args,
+  Iterable<String>? args,
   output,
   error,
   warning,
-  int exitCode,
-  Map<String, String> environment,
-  String workingDirectory,
+  int? exitCode,
+  Map<String, String>? environment,
+  String? workingDirectory,
 }) async =>
     await pubCommand(
       RunCommand.remove,
@@ -276,8 +273,8 @@
 /// Returns the `pub run` process.
 Future<PubProcess> pubRun(
     {bool global = false,
-    Iterable<String> args,
-    Map<String, String> environment,
+    required Iterable<String> args,
+    Map<String, String>? environment,
     bool verbose = true}) async {
   var pubArgs = global ? ['global', 'run'] : ['run'];
   pubArgs.addAll(args);
@@ -321,15 +318,15 @@
 /// If [environment] is given, any keys in it will override the environment
 /// variables passed to the spawned process.
 Future<void> runPub(
-    {List<String> args,
+    {List<String>? args,
     output,
     error,
     outputJson,
     silent,
-    int exitCode,
-    String workingDirectory,
-    Map<String, String> environment,
-    List<String> input,
+    int? exitCode,
+    String? workingDirectory,
+    Map<String, String?>? environment,
+    List<String>? input,
     includeParentEnvironment = true}) async {
   exitCode ??= exit_codes.SUCCESS;
   // Cannot pass both output and outputJson.
@@ -375,9 +372,9 @@
 /// Any futures in [args] will be resolved before the process is started.
 Future<PubProcess> startPublish(
   PackageServer server, {
-  List<String> args,
+  List<String>? args,
   String authMethod = 'oauth2',
-  Map<String, String> environment,
+  Map<String, String>? environment,
 }) async {
   var tokenEndpoint = Uri.parse(server.url).resolve('/token').toString();
   args = ['lish', ...?args];
@@ -415,7 +412,7 @@
 String testVersion = '0.1.2+3';
 
 /// Gets the environment variables used to run pub in a test context.
-Map<String, String> getPubTestEnvironment([String tokenEndpoint]) {
+Map<String, String> getPubTestEnvironment([String? tokenEndpoint]) {
   var environment = {
     'CI': 'false', // unless explicitly given tests don't run pub in CI mode
     '_PUB_TESTING': 'true',
@@ -431,8 +428,9 @@
     environment['_PUB_TEST_TOKEN_ENDPOINT'] = tokenEndpoint;
   }
 
-  if (globalServer != null) {
-    environment['PUB_HOSTED_URL'] = 'http://localhost:${globalServer.port}';
+  var server = globalServer;
+  if (server != null) {
+    environment['PUB_HOSTED_URL'] = 'http://localhost:${server.port}';
   }
 
   return environment;
@@ -455,10 +453,10 @@
 /// If [environment] is given, any keys in it will override the environment
 /// variables passed to the spawned process.
 Future<PubProcess> startPub(
-    {Iterable<String> args,
-    String tokenEndpoint,
-    String workingDirectory,
-    Map<String, String> environment,
+    {Iterable<String>? args,
+    String? tokenEndpoint,
+    String? workingDirectory,
+    Map<String, String?>? environment,
     bool verbose = true,
     includeParentEnvironment = true}) async {
   args ??= [];
@@ -484,10 +482,11 @@
 
   final mergedEnvironment = getPubTestEnvironment(tokenEndpoint);
   for (final e in (environment ?? {}).entries) {
-    if (e.value == null) {
+    var value = e.value;
+    if (value == null) {
       mergedEnvironment.remove(e.key);
     } else {
-      mergedEnvironment[e.key] = e.value;
+      mergedEnvironment[e.key] = value;
     }
   }
 
@@ -502,23 +501,23 @@
 /// makes [stdout] and [stderr] work as though pub weren't running in verbose
 /// mode.
 class PubProcess extends TestProcess {
-  StreamSplitter<Pair<log.Level, String>> get _logSplitter {
-    __logSplitter ??= StreamSplitter(StreamGroup.merge([
+  late final StreamSplitter<Pair<log.Level, String>> _logSplitter =
+      createLogSplitter();
+
+  StreamSplitter<Pair<log.Level, String>> createLogSplitter() {
+    return StreamSplitter(StreamGroup.merge([
       _outputToLog(super.stdoutStream(), log.Level.MESSAGE),
       _outputToLog(super.stderrStream(), log.Level.ERROR)
     ]));
-    return __logSplitter;
   }
 
-  StreamSplitter<Pair<log.Level, String>> __logSplitter;
-
   static Future<PubProcess> start(String executable, Iterable<String> arguments,
-      {String workingDirectory,
-      Map<String, String> environment,
+      {String? workingDirectory,
+      Map<String, String>? environment,
       bool includeParentEnvironment = true,
       bool runInShell = false,
-      String description,
-      Encoding encoding,
+      String? description,
+      Encoding encoding = utf8,
       bool forwardStdio = false}) async {
     var process = await Process.start(executable, arguments.toList(),
         workingDirectory: workingDirectory,
@@ -533,14 +532,13 @@
       description = '$humanExecutable ${arguments.join(' ')}';
     }
 
-    encoding ??= utf8;
     return PubProcess(process, description,
         encoding: encoding, forwardStdio: forwardStdio);
   }
 
   /// This is protected.
   PubProcess(process, description,
-      {Encoding encoding, bool forwardStdio = false})
+      {Encoding encoding = utf8, bool forwardStdio = false})
       : super(process, description,
             encoding: encoding, forwardStdio: forwardStdio);
 
@@ -559,14 +557,14 @@
 
   Stream<Pair<log.Level, String>> _outputToLog(
       Stream<String> stream, log.Level defaultLevel) {
-    log.Level lastLevel;
+    late log.Level lastLevel;
     return stream.map((line) {
       var match = _logLineRegExp.firstMatch(line);
       if (match == null) return Pair<log.Level, String>(defaultLevel, line);
 
       var level = _logLevels[match[1]] ?? lastLevel;
       lastLevel = level;
-      return Pair<log.Level, String>(level, match[2]);
+      return Pair<log.Level, String>(level, match[2]!);
     });
   }
 
@@ -616,15 +614,15 @@
 /// [hosted] is a list of package names to version strings for dependencies on
 /// hosted packages.
 Future<void> createLockFile(String package,
-    {Iterable<String> dependenciesInSandBox,
-    Map<String, String> hosted}) async {
+    {Iterable<String>? dependenciesInSandBox,
+    Map<String, String>? hosted}) async {
   var cache = SystemCache(rootDir: _pathInSandbox(cachePath));
 
   var lockFile = _createLockFile(cache.sources,
       sandbox: dependenciesInSandBox, hosted: hosted);
 
   await d.dir(package, [
-    d.file('pubspec.lock', lockFile.serialize(null)),
+    d.file('pubspec.lock', lockFile.serialize(p.join(d.sandbox, package))),
     d.file(
       '.packages',
       lockFile.packagesFile(
@@ -639,8 +637,8 @@
 /// Like [createLockFile], but creates only a `.packages` file without a
 /// lockfile.
 Future<void> createPackagesFile(String package,
-    {Iterable<String> dependenciesInSandBox,
-    Map<String, String> hosted}) async {
+    {Iterable<String>? dependenciesInSandBox,
+    Map<String, String>? hosted}) async {
   var cache = SystemCache(rootDir: _pathInSandbox(cachePath));
   var lockFile = _createLockFile(cache.sources,
       sandbox: dependenciesInSandBox, hosted: hosted);
@@ -665,7 +663,7 @@
 /// [hosted] is a list of package names to version strings for dependencies on
 /// hosted packages.
 LockFile _createLockFile(SourceRegistry sources,
-    {Iterable<String> sandbox, Map<String, String> hosted}) {
+    {Iterable<String>? sandbox, Map<String, String>? hosted}) {
   var dependencies = {};
 
   if (sandbox != null) {
@@ -676,7 +674,9 @@
 
   var packages = dependencies.keys.map((name) {
     var dependencyPath = dependencies[name];
-    return sources.path.idFor(name, Version(0, 0, 0), dependencyPath);
+    return sources.path.parseId(
+        name, Version(0, 0, 0), {'path': dependencyPath, 'relative': true},
+        containingPath: p.join(d.sandbox, appPath));
   }).toList();
 
   if (hosted != null) {
@@ -703,14 +703,14 @@
 
 /// Describes a map representing a library package with the given [name],
 /// [version], and [dependencies].
-Map packageMap(
+Map<String, Object> packageMap(
   String name,
   String version, [
-  Map dependencies,
-  Map devDependencies,
-  Map environment,
+  Map? dependencies,
+  Map? devDependencies,
+  Map? environment,
 ]) {
-  var package = <String, dynamic>{
+  var package = <String, Object>{
     'name': name,
     'version': version,
     'homepage': 'http://pub.dartlang.org',
@@ -832,7 +832,7 @@
 /// which may be a literal JSON object, or any other [Matcher].
 void _validateOutputJson(
     List<String> failures, String pipe, expected, String actualText) {
-  Map actual;
+  late Map actual;
   try {
     actual = jsonDecode(actualText);
   } on FormatException {
@@ -913,8 +913,9 @@
     line = line
         .replaceAll(d.sandbox, r'$SANDBOX')
         .replaceAll(Platform.pathSeparator, '/');
-    if (globalPackageServer != null) {
-      line = line.replaceAll(globalPackageServer.port.toString(), '\$PORT');
+    var packageServer = globalPackageServer;
+    if (packageServer != null) {
+      line = line.replaceAll(packageServer.port.toString(), '\$PORT');
     }
     return line;
   });
@@ -924,8 +925,8 @@
 Future<void> runPubIntoBuffer(
   List<String> args,
   StringBuffer buffer, {
-  Map<String, String> environment,
-  String workingDirectory,
+  Map<String, String>? environment,
+  String? workingDirectory,
 }) async {
   final process = await startPub(
     args: args,