Remove a number of unused members (#1881)

Made members only used within a library private
Moved a number of items only used it tests closer to their usage
diff --git a/lib/src/git.dart b/lib/src/git.dart
index 04883d5..9ea1e15 100644
--- a/lib/src/git.dart
+++ b/lib/src/git.dart
@@ -73,18 +73,6 @@
   return result.stdout;
 }
 
-/// Starts a git process and returns it.
-Future<PubProcess> start(List<String> args,
-    {String workingDir, Map<String, String> environment}) {
-  if (!isInstalled) {
-    fail("Cannot find a Git executable.\n"
-        "Please ensure Git is correctly installed.");
-  }
-
-  return startProcess(command, args,
-      workingDir: workingDir, environment: environment);
-}
-
 /// Returns the name of the git command-line app, or null if Git could not be
 /// found on the user's PATH.
 String get command {
diff --git a/lib/src/io.dart b/lib/src/io.dart
index bce55b7..f153033 100644
--- a/lib/src/io.dart
+++ b/lib/src/io.dart
@@ -70,7 +70,7 @@
   while (components.isNotEmpty) {
     seen.add(path.join(newPath, path.joinAll(components)));
     var resolvedPath =
-        resolveLink(path.join(newPath, components.removeFirst()));
+        _resolveLink(path.join(newPath, components.removeFirst()));
     var relative = path.relative(resolvedPath, from: newPath);
 
     // If the resolved path of the component relative to `newPath` is just ".",
@@ -138,7 +138,7 @@
 /// return `"B"`).
 ///
 /// This accepts paths to non-links or broken links, and returns them as-is.
-String resolveLink(String link) {
+String _resolveLink(String link) {
   var seen = new Set<String>();
   while (linkExists(link) && !seen.contains(link)) {
     seen.add(link);
@@ -173,32 +173,21 @@
     log.fine("Contents:\n$contents");
   }
 
-  _deleteIfLink(file);
+  deleteIfLink(file);
   new File(file).writeAsStringSync(contents, encoding: encoding);
   return file;
 }
 
-/// Creates [file] and writes [contents] to it.
-String writeBinaryFile(String file, List<int> contents) {
-  log.io("Writing ${contents.length} bytes to binary file $file.");
-  _deleteIfLink(file);
-  new File(file).openSync(mode: FileMode.WRITE)
-    ..writeFromSync(contents)
-    ..closeSync();
-  log.fine("Wrote text file $file.");
-  return file;
-}
-
 /// Writes [stream] to a new file at path [file].
 ///
 /// Replaces any file already at that path. Completes when the file is done
 /// being written.
-Future<String> createFileFromStream(Stream<List<int>> stream, String file) {
+Future<String> _createFileFromStream(Stream<List<int>> stream, String file) {
   // TODO(nweiz): remove extra logging when we figure out the windows bot issue.
   log.io("Creating $file from stream.");
 
   return _descriptorPool.withResource(() async {
-    _deleteIfLink(file);
+    deleteIfLink(file);
     await stream.pipe(new File(file).openWrite());
     log.fine("Created $file from stream.");
     return file;
@@ -209,36 +198,12 @@
 ///
 /// The [File] class overwrites the symlink targets when writing to a file,
 /// which is never what we want, so this delete the symlink first if necessary.
-void _deleteIfLink(String file) {
+void deleteIfLink(String file) {
   if (!linkExists(file)) return;
   log.io("Deleting symlink at $file.");
   new Link(file).deleteSync();
 }
 
-/// Copies all files in [files] to the directory [destination].
-///
-/// Their locations in [destination] will be determined by their relative
-/// location to [baseDir]. Any existing files at those paths will be
-/// overwritten.
-void copyFiles(Iterable<String> files, String baseDir, String destination) {
-  for (var file in files) {
-    var newPath = path.join(destination, path.relative(file, from: baseDir));
-    ensureDir(path.dirname(newPath));
-    copyFile(file, newPath);
-  }
-}
-
-/// Copies a file from [source] to [destination].
-void copyFile(String source, String destination) {
-  writeBinaryFile(destination, readBinaryFile(source));
-}
-
-/// Creates a directory [dir].
-String createDir(String dir) {
-  new Directory(dir).createSync();
-  return dir;
-}
-
 /// Ensures that [dir] and all its parent directories exist.
 ///
 /// If they don't exist, creates them.
@@ -261,7 +226,7 @@
 /// 'pub_' with characters appended to it to make a unique name.
 ///
 /// Returns the path of the created directory.
-String createSystemTempDir() {
+String _createSystemTempDir() {
   var tempDir = Directory.systemTemp.createTempSync('pub_');
   log.io("Created temp directory ${tempDir.path}");
   return tempDir.resolveSymbolicLinksSync();
@@ -508,7 +473,7 @@
 /// test package's test runner.
 ///
 /// The test runner starts all tests from a `data:` URI.
-final bool runningAsTestRunner = Platform.script.scheme == 'data';
+final bool _runningAsTestRunner = Platform.script.scheme == 'data';
 
 /// Whether the current process is a pub subprocess being run from a test.
 ///
@@ -518,7 +483,7 @@
 
 /// Whether pub is running from within the Dart SDK, as opposed to from the Dart
 /// source repository.
-final bool runningFromSdk =
+final bool _runningFromSdk =
     !runningFromTest && Platform.script.path.endsWith('.snapshot');
 
 /// A regular expression to match the script path of a pub script running from
@@ -534,7 +499,7 @@
 /// This can happen when running tests against the repo, as well as when
 /// building Observatory.
 final bool runningFromDartRepo = (() {
-  if (runningAsTestRunner) {
+  if (_runningAsTestRunner) {
     // When running from the test runner, we can't find our location via
     // Platform.script since the runner munges that. However, it guarantees that
     // the working directory is <repo>/third_party/pkg/pub.
@@ -545,15 +510,10 @@
   }
 })();
 
-/// Resolves [target] relative to the path to pub's `asset` directory.
-String assetPath(String target) => runningFromSdk
-    ? sdkAssetPath(target)
-    : path.join(pubRoot, 'lib', 'src', 'asset', target);
-
 /// Resolves [target] relative to the Dart SDK's `asset` directory.
 ///
 /// Throws a [StateError] if called from within the Dart repo.
-String sdkAssetPath(String target) {
+String _sdkAssetPath(String target) {
   if (runningFromDartRepo) {
     throw new StateError("Can't get SDK assets from within the Dart repo.");
   }
@@ -566,12 +526,12 @@
 ///
 /// This throws a [StateError] if it's called when running pub from the SDK.
 final String pubRoot = (() {
-  if (runningFromSdk) {
+  if (_runningFromSdk) {
     throw new StateError("Can't get pub's root from the SDK.");
   }
 
   // The test runner always runs from the working directory.
-  if (runningAsTestRunner) return path.current;
+  if (_runningAsTestRunner) return path.current;
 
   var script = path.fromUri(Platform.script);
   if (runningAsTest) {
@@ -595,7 +555,7 @@
     throw new StateError("Not running from source in the Dart repo.");
   }
 
-  if (runningAsTestRunner) {
+  if (_runningAsTestRunner) {
     // When running in test code started by the test runner, the working
     // directory will always be <repo>/third_party/pkg/pub.
     return path.dirname(path.dirname(path.dirname(path.current)));
@@ -609,7 +569,7 @@
 })();
 
 /// A line-by-line stream of standard input.
-final Stream<String> stdinLines =
+final Stream<String> _stdinLines =
     streamToLines(new ByteStream(stdin).toStringStream());
 
 /// Displays a message and reads a yes/no confirmation from the user.
@@ -626,17 +586,10 @@
   } else {
     stdout.write(log.format("$message (y/n)? "));
   }
-  return streamFirst(stdinLines)
+  return streamFirst(_stdinLines)
       .then((line) => new RegExp(r"^[yY]").hasMatch(line));
 }
 
-/// Reads and discards all output from [stream].
-///
-/// Returns a [Future] that completes when the stream is closed.
-Future drainStream(Stream stream) {
-  return stream.fold(null, (x, y) {});
-}
-
 /// Flushes the stdout and stderr streams, then exits the program with the given
 /// status code.
 ///
@@ -651,7 +604,7 @@
 /// Returns a [EventSink] that pipes all data to [consumer] and a [Future] that
 /// will succeed when [EventSink] is closed or fail with any errors that occur
 /// while writing.
-Pair<EventSink<T>, Future> consumerToSink<T>(StreamConsumer<T> consumer) {
+Pair<EventSink<T>, Future> _consumerToSink<T>(StreamConsumer<T> consumer) {
   var controller = new StreamController<T>(sync: true);
   var done = controller.stream.pipe(consumer);
   return new Pair(controller.sink, done);
@@ -668,7 +621,7 @@
 /// more data or errors will be piped from [stream] to [sink]. If
 /// [cancelOnError] and [closeSink] are both true, [sink] will then be
 /// closed.
-Future store(Stream stream, EventSink sink,
+Future _store(Stream stream, EventSink sink,
     {bool cancelOnError: true, bool closeSink: true}) {
   var completer = new Completer();
   stream.listen(sink.add, onError: (e, stackTrace) {
@@ -715,7 +668,7 @@
 /// The spawned process will inherit its parent's environment variables. If
 /// [environment] is provided, that will be used to augment (not replace) the
 /// the inherited variables.
-Future<PubProcess> startProcess(String executable, List<String> args,
+Future<_PubProcess> _startProcess(String executable, List<String> args,
     {workingDir, Map<String, String> environment, bool runInShell: false}) {
   return _descriptorPool.request().then((resource) async {
     var ioProcess = await _doProcess(Process.start, executable, args,
@@ -723,7 +676,7 @@
         environment: environment,
         runInShell: runInShell);
 
-    var process = new PubProcess(ioProcess);
+    var process = new _PubProcess(ioProcess);
     process.exitCode.whenComplete(resource.release);
     return process;
   });
@@ -743,7 +696,7 @@
 }
 
 /// A wrapper around [Process] that exposes `dart:async`-style APIs.
-class PubProcess {
+class _PubProcess {
   /// The underlying `dart:io` [Process].
   final Process _process;
 
@@ -802,11 +755,11 @@
   /// error handler unless nothing has handled it.
   Future<int> get exitCode => _exitCode;
 
-  /// Creates a new [PubProcess] wrapping [process].
-  PubProcess(Process process) : _process = process {
+  /// Creates a new [_PubProcess] wrapping [process].
+  _PubProcess(Process process) : _process = process {
     var errorGroup = new ErrorGroup();
 
-    var pair = consumerToSink(process.stdin);
+    var pair = _consumerToSink(process.stdin);
     _stdin = pair.first;
     _stdinClosed = errorGroup.registerFuture(pair.last);
 
@@ -867,7 +820,7 @@
 /// Returns a future that completes to the value that the future returned from
 /// [fn] completes to.
 Future<T> withTempDir<T>(Future<T> fn(String path)) async {
-  var tempDir = createSystemTempDir();
+  var tempDir = _createSystemTempDir();
   try {
     return await fn(tempDir);
   } finally {
@@ -911,15 +864,15 @@
     args.insert(0, "--warning=no-unknown-keyword");
   }
 
-  var process = await startProcess("tar", args);
+  var process = await _startProcess("tar", args);
 
   // Ignore errors on process.std{out,err}. They'll be passed to
   // process.exitCode, and we don't want them being top-levelled by
   // std{out,err}Sink.
-  store(process.stdout.handleError((_) {}), stdout, closeSink: false);
-  store(process.stderr.handleError((_) {}), stderr, closeSink: false);
+  _store(process.stdout.handleError((_) {}), stdout, closeSink: false);
+  _store(process.stderr.handleError((_) {}), stderr, closeSink: false);
   var results =
-      await Future.wait([store(stream, process.stdin), process.exitCode]);
+      await Future.wait([_store(stream, process.stdin), process.exitCode]);
 
   var exitCode = results[1];
   if (exitCode != exit_codes.SUCCESS) {
@@ -951,8 +904,8 @@
   return major >= 2 || (major == 1 && minor >= 23);
 }
 
-final String pathTo7zip = (() {
-  if (!runningFromDartRepo) return sdkAssetPath(path.join('7zip', '7za.exe'));
+final String _pathTo7zip = (() {
+  if (!runningFromDartRepo) return _sdkAssetPath(path.join('7zip', '7za.exe'));
   return path.join(dartRepoRoot, 'third_party', '7zip', '7za.exe');
 })();
 
@@ -967,14 +920,14 @@
   return withTempDir((tempDir) async {
     // Write the archive to a temp file.
     var dataFile = path.join(tempDir, 'data.tar.gz');
-    await createFileFromStream(stream, dataFile);
+    await _createFileFromStream(stream, dataFile);
 
     // 7zip can't unarchive from gzip -> tar -> destination all in one step
     // first we un-gzip it to a tar file.
     // Note: Setting the working directory instead of passing in a full file
     // path because 7zip says "A full path is not allowed here."
-    var unzipResult =
-        await runProcess(pathTo7zip, ['e', 'data.tar.gz'], workingDir: tempDir);
+    var unzipResult = await runProcess(_pathTo7zip, ['e', 'data.tar.gz'],
+        workingDir: tempDir);
 
     if (unzipResult.exitCode != exit_codes.SUCCESS) {
       throw new Exception(
@@ -991,7 +944,7 @@
 
     // Untar the archive into the destination directory.
     var untarResult =
-        await runProcess(pathTo7zip, ['x', tarFile], workingDir: destination);
+        await runProcess(_pathTo7zip, ['x', tarFile], workingDir: destination);
     if (untarResult.exitCode != exit_codes.SUCCESS) {
       throw new Exception(
           'Could not un-tar (exit code ${untarResult.exitCode}). Error:\n'
@@ -1075,7 +1028,7 @@
       // input file, relative paths in the mtree file are interpreted as
       // relative to the current working directory, not the "--directory"
       // argument.
-      var process = await startProcess("tar", args, workingDir: baseDir);
+      var process = await _startProcess("tar", args, workingDir: baseDir);
       process.stdin.add(utf8.encode(stdin));
       process.stdin.close();
       return process.stdout;
@@ -1083,7 +1036,7 @@
 
     // Don't use [withTempDir] here because we don't want to delete the temp
     // directory until the returned stream has closed.
-    var tempDir = createSystemTempDir();
+    var tempDir = _createSystemTempDir();
 
     try {
       // Create the file containing the list of files to compress.
@@ -1099,12 +1052,12 @@
       // files added to the archive have the correct relative path in the
       // archive. The latter enables relative paths in the "-i" args to be
       // resolved.
-      await runProcess(pathTo7zip, args, workingDir: baseDir);
+      await runProcess(_pathTo7zip, args, workingDir: baseDir);
 
       // GZIP it. 7zip doesn't support doing both as a single operation.
       // Send the output to stdout.
       args = ["a", "unused", "-tgzip", "-so", tarFile];
-      return (await startProcess(pathTo7zip, args))
+      return (await _startProcess(_pathTo7zip, args))
           .stdout
           .transform(onDoneTransformer(() => deleteEntry(tempDir)));
     } catch (_) {
@@ -1133,21 +1086,3 @@
 
   bool get success => exitCode == exit_codes.SUCCESS;
 }
-
-/// Returns the top level directory in [uri].
-///
-/// Throws an [ArgumentError] if [uri] is just a filename with no directory.
-String topLevelDir(String uri) {
-  var parts = path.url.split(path.url.normalize(uri));
-  String error;
-  if (parts.length == 1) {
-    error = 'The uri `$uri` does not contain a directory.';
-  } else if (parts.first == '..') {
-    error = 'The uri `$uri` reaches outside the root directory.';
-  }
-  if (error != null) {
-    throw new ArgumentError(
-        'Cannot compute top level dir for path `$uri`. $error');
-  }
-  return parts.first;
-}
diff --git a/lib/src/log.dart b/lib/src/log.dart
index d6ead03..dfa3ba7 100644
--- a/lib/src/log.dart
+++ b/lib/src/log.dart
@@ -42,15 +42,13 @@
 
 /// The list of recorded log messages. Will only be recorded if
 /// [recordTranscript()] is called.
-Transcript<Entry> _transcript;
+Transcript<_Entry> _transcript;
 
 /// The currently-animated progress indicator, if any.
 ///
 /// This will also be in [_progresses].
 Progress _animatedProgress;
 
-_Collapser _collapser;
-
 final _cyan = getSpecial('\u001b[36m');
 final _green = getSpecial('\u001b[32m');
 final _magenta = getSpecial('\u001b[35m');
@@ -103,7 +101,7 @@
   String toString() => name;
 }
 
-typedef _LogFn(Entry entry);
+typedef _LogFn(_Entry entry);
 
 /// An enum type to control which log levels are displayed and how they are
 /// displayed.
@@ -189,11 +187,11 @@
 }
 
 /// A single log entry.
-class Entry {
+class _Entry {
   final Level level;
   final List<String> lines;
 
-  Entry(this.level, this.lines);
+  _Entry(this.level, this.lines);
 }
 
 /// Logs [message] at [Level.ERROR].
@@ -227,9 +225,6 @@
 
 /// Logs [message] at [level].
 void write(Level level, message) {
-  // Don't allow interleaving collapsible messages with other kinds.
-  if (_collapser != null) _collapser.end();
-
   message = message.toString();
   var lines = splitLines(message);
 
@@ -239,7 +234,7 @@
     lines.removeLast();
   }
 
-  var entry = new Entry(level, lines.map(format).toList());
+  var entry = new _Entry(level, lines.map(format).toList());
 
   var logFn = verbosity._loggers[level];
   if (logFn != null) logFn(entry);
@@ -271,32 +266,6 @@
   return string;
 }
 
-/// Logs an asynchronous IO operation.
-///
-/// Logs [startMessage] before the operation starts, then when [operation]
-/// completes, invokes [endMessage] with the completion value and logs the
-/// result of that. Returns a future that completes after the logging is done.
-///
-/// If [endMessage] is omitted, then logs "Begin [startMessage]" before the
-/// operation and "End [startMessage]" after it.
-Future ioAsync(String startMessage, Future operation,
-    [String endMessage(value)]) {
-  if (endMessage == null) {
-    io("Begin $startMessage.");
-  } else {
-    io(startMessage);
-  }
-
-  return operation.then((result) {
-    if (endMessage == null) {
-      io("End $startMessage.");
-    } else {
-      io(endMessage(result));
-    }
-    return result;
-  });
-}
-
 /// Logs the spawning of an [executable] process with [arguments] at [IO]
 /// level.
 void process(
@@ -376,7 +345,7 @@
 
 /// Enables recording of log entries.
 void recordTranscript() {
-  _transcript = new Transcript<Entry>(_MAX_TRANSCRIPT);
+  _transcript = new Transcript<_Entry>(_MAX_TRANSCRIPT);
 }
 
 /// If [recordTranscript()] was called, then prints the previously recorded log
@@ -440,31 +409,6 @@
   _numMutes--;
 }
 
-/// Logs a collapsible [message].
-///
-/// If a number of collapsible messages are printed in short succession, they
-/// are collapsed to just showing [template] with "##" replaced with the number
-/// of collapsed messages. Avoids spamming the output with not-very-interesting
-/// output.
-void collapsible(String message, String template) {
-  // Only collapse messages when the output is not verbose.
-  if (verbosity._loggers[Level.MESSAGE] != _logToStdout) {
-    write(Level.MESSAGE, message);
-    return;
-  }
-
-  // If this is a different set of collapsed messages, end the previous ones.
-  if (_collapser != null && _collapser._template != template) {
-    _collapser.end();
-  }
-
-  if (_collapser != null) {
-    _collapser.increment();
-  } else {
-    _collapser = new _Collapser(message, template);
-  }
-}
-
 /// Wraps [text] in the ANSI escape codes to make it bold when on a platform
 /// that supports that.
 ///
@@ -533,32 +477,32 @@
 }
 
 /// Log function that prints the message to stdout.
-void _logToStdout(Entry entry) {
+void _logToStdout(_Entry entry) {
   _logToStream(stdout, entry, showLabel: false);
 }
 
 /// Log function that prints the message to stdout with the level name.
-void _logToStdoutWithLabel(Entry entry) {
+void _logToStdoutWithLabel(_Entry entry) {
   _logToStream(stdout, entry, showLabel: true);
 }
 
 /// Log function that prints the message to stderr.
-void _logToStderr(Entry entry) {
+void _logToStderr(_Entry entry) {
   _logToStream(stderr, entry, showLabel: false);
 }
 
 /// Log function that prints the message to stderr with the level name.
-void _logToStderrWithLabel(Entry entry) {
+void _logToStderrWithLabel(_Entry entry) {
   _logToStream(stderr, entry, showLabel: true);
 }
 
-void _logToStream(IOSink sink, Entry entry, {bool showLabel}) {
+void _logToStream(IOSink sink, _Entry entry, {bool showLabel}) {
   if (json.enabled) return;
 
   _printToStream(sink, entry, showLabel: showLabel);
 }
 
-void _printToStream(IOSink sink, Entry entry, {bool showLabel}) {
+void _printToStream(IOSink sink, _Entry entry, {bool showLabel}) {
   _stopProgress();
 
   bool firstLine = true;
@@ -620,57 +564,3 @@
     print(jsonEncode(message));
   }
 }
-
-/// Collapses a series of collapsible messages into a single line of output if
-/// they happen within a short window of time.
-class _Collapser {
-  /// The window of time where a series of calls to [collapsible] will be
-  /// collapsed to a single message.
-  static final _window = new Duration(milliseconds: 100);
-
-  /// The Timer used to coalesce a number of collapsible messages.
-  ///
-  /// This is `null` if no collapsible messages are waiting to be displayed.
-  Timer _timer;
-
-  /// The first collapsible message waiting to be displayed.
-  String _firstMessage;
-
-  /// The template used to display the number of collapsed messages when more
-  /// than one collapsible message is logged within the window of time.
-  ///
-  /// Inside the template, "##" will be replaced with the number of collapsed
-  /// messages.
-  String _template;
-
-  /// The number of collapsible messages that are waiting to be logged.
-  int _count = 1;
-
-  _Collapser(this._firstMessage, this._template) {
-    _initTimer();
-  }
-
-  void increment() {
-    // Reset the timer.
-    _timer.cancel();
-    _initTimer();
-
-    _count++;
-  }
-
-  void end() {
-    // Clear this first so we don't stack overflow when we call message() below.
-    _collapser = null;
-
-    _timer.cancel();
-    if (_count == 1) {
-      message(_firstMessage);
-    } else {
-      message(_template.replaceAll("##", _count.toString()));
-    }
-  }
-
-  void _initTimer() {
-    _timer = new Timer(_window, end);
-  }
-}
diff --git a/lib/src/oauth2.dart b/lib/src/oauth2.dart
index 281ad6a..e38c3af 100644
--- a/lib/src/oauth2.dart
+++ b/lib/src/oauth2.dart
@@ -34,7 +34,7 @@
 /// a refresh token from the server. See the [Google OAuth2 documentation][].
 ///
 /// [Google OAuth2 documentation]: https://developers.google.com/accounts/docs/OAuth2WebServer#offline
-final authorizationEndpoint =
+final _authorizationEndpoint =
     Uri.parse('https://accounts.google.com/o/oauth2/auth?access_type=offline'
         '&approval_prompt=force');
 
@@ -67,7 +67,7 @@
 Credentials _credentials;
 
 /// Delete the cached credentials, if they exist.
-void clearCredentials(SystemCache cache) {
+void _clearCredentials(SystemCache cache) {
   _credentials = null;
   var credentialsFile = _credentialsFile(cache);
   if (entryExists(credentialsFile)) deleteEntry(credentialsFile);
@@ -97,7 +97,7 @@
         message = "$message (${error.description})";
       }
       log.error("$message.");
-      clearCredentials(cache);
+      _clearCredentials(cache);
       return withClient(cache, fn);
     } else {
       throw error;
@@ -171,7 +171,7 @@
 /// Returns a Future that completes to a fully-authorized [Client].
 Future<Client> _authorize() {
   var grant = new AuthorizationCodeGrant(
-      _identifier, authorizationEndpoint, tokenEndpoint,
+      _identifier, _authorizationEndpoint, tokenEndpoint,
       secret: _secret,
       // Google's OAuth2 API doesn't support basic auth.
       basicAuth: false,
diff --git a/lib/src/pubspec.dart b/lib/src/pubspec.dart
index f412546..65d3253 100644
--- a/lib/src/pubspec.dart
+++ b/lib/src/pubspec.dart
@@ -42,11 +42,11 @@
 ///
 /// This has a default value of `true` but can be overridden with the
 /// PUB_ALLOW_PRERELEASE_SDK system environment variable.
-bool get allowPreReleaseSdk => allowPreReleaseSdkValue != 'false';
+bool get _allowPreReleaseSdk => _allowPreReleaseSdkValue != 'false';
 
 /// The value of the PUB_ALLOW_PRERELEASE_SDK environment variable, defaulted
 /// to `true`.
-final String allowPreReleaseSdkValue = () {
+final String _allowPreReleaseSdkValue = () {
   var value =
       Platform.environment["PUB_ALLOW_PRERELEASE_SDK"]?.toLowerCase() ?? 'true';
   if (!['true', 'quiet', 'false'].contains(value)) {
@@ -61,7 +61,7 @@
 }();
 
 /// Whether or not to warn about pre-release SDK overrides.
-bool get warnAboutPreReleaseSdkOverrides => allowPreReleaseSdkValue != 'quiet';
+bool get warnAboutPreReleaseSdkOverrides => _allowPreReleaseSdkValue != 'quiet';
 
 /// The parsed contents of a pubspec file.
 ///
@@ -289,7 +289,7 @@
   ///
   /// This is true if the following conditions are met:
   ///
-  ///   - [allowPreReleaseSdk] is `true`
+  ///   - [_allowPreReleaseSdk] is `true`
   ///   - The user's current SDK is a pre-release version.
   ///   - The original [sdkConstraint] max version is exclusive (`includeMax`
   ///     is `false`).
@@ -297,7 +297,7 @@
   ///   - The original [sdkConstraint] matches the exact same major, minor, and
   ///     patch versions as the user's current SDK.
   bool _shouldEnableCurrentSdk(VersionRange sdkConstraint) {
-    if (!allowPreReleaseSdk) return false;
+    if (!_allowPreReleaseSdk) return false;
     if (!sdk.version.isPreRelease) return false;
     if (sdkConstraint.includeMax) return false;
     if (sdkConstraint.min != null &&
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 2ae6db5..565c136 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -70,7 +70,7 @@
 ///
 /// If pub isn't attached to a terminal, uses an infinite line length and does
 /// not wrap text.
-final int lineLength = () {
+final int _lineLength = () {
   try {
     return stdout.terminalColumns;
   } on StdoutException {
@@ -163,23 +163,10 @@
   });
 }
 
-// TODO(rnystrom): Move into String?
-/// 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();
-}
-
 /// Pads [source] to [length] by adding [char]s at the beginning.
 ///
 /// If [char] is `null`, it defaults to a space.
-String padLeft(String source, int length, [String char]) {
+String _padLeft(String source, int length, [String char]) {
   if (char == null) char = ' ';
   if (source.length >= length) return source;
 
@@ -237,23 +224,6 @@
   return string;
 }
 
-/// Creates a URL string for [address]:[port].
-///
-/// Handles properly formatting IPv6 addresses.
-Uri baseUrlForAddress(InternetAddress address, int port) {
-  if (address.isLoopback) {
-    return new Uri(scheme: "http", host: "localhost", port: port);
-  }
-
-  // IPv6 addresses in URLs need to be enclosed in square brackets to avoid
-  // URL ambiguity with the ":" in the address.
-  if (address.type == InternetAddressType.IP_V6) {
-    return new Uri(scheme: "http", host: "[${address.address}]", port: port);
-  }
-
-  return new Uri(scheme: "http", host: address.address, port: port);
-}
-
 /// Returns whether [host] is a host for a localhost or loopback URL.
 ///
 /// Unlike [InternetAddress.isLoopback], this hostnames from URLs as well as
@@ -380,49 +350,6 @@
   future.then(completer.complete, onError: completer.completeError);
 }
 
-/// Ensures that [stream] can emit at least one value successfully (or close
-/// without any values).
-///
-/// For example, reading asynchronously from a non-existent file will return a
-/// stream that fails on the first chunk. In order to handle that more
-/// gracefully, you may want to check that the stream looks like it's working
-/// before you pipe the stream to something else.
-///
-/// This lets you do that. It returns a [Future] that completes to a [Stream]
-/// emitting the same values and errors as [stream], but only if at least one
-/// value can be read successfully. If an error occurs before any values are
-/// emitted, the returned Future completes to that error.
-Future<Stream<T>> validateStream<T>(Stream<T> stream) {
-  var completer = new Completer<Stream>();
-  var controller = new StreamController(sync: true);
-
-  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]) {
-    // If the error came after values, it's OK.
-    if (completer.isCompleted) {
-      controller.addError(error, stackTrace);
-      return;
-    }
-
-    // Otherwise, the error came first and the stream is invalid.
-    completer.completeError(error, stackTrace);
-
-    // We won't be returning the stream at all in this case, so unsubscribe
-    // and swallow the error.
-    subscription.cancel();
-  }, onDone: () {
-    // It closed with no errors, so the stream is valid.
-    if (!completer.isCompleted) completer.complete(controller.stream);
-    controller.close();
-  });
-
-  return completer.future;
-}
-
 // TODO(nweiz): remove this when issue 7964 is fixed.
 /// Returns a [Future] that will complete to the first element of [stream].
 ///
@@ -493,14 +420,6 @@
   ];
 }
 
-/// Adds additional query parameters to [url], overwriting the original
-/// parameters if a name conflict occurs.
-Uri addQueryParameters(Uri url, Map<String, String> parameters) {
-  var queryMap = queryToMap(url.query);
-  queryMap.addAll(parameters);
-  return url.resolve("?${mapToQuery(queryMap)}");
-}
-
 /// Convert a URL query string (or `application/x-www-form-urlencoded` body)
 /// into a [Map] from parameter names to values.
 Map<String, String> queryToMap(String queryList) {
@@ -508,29 +427,13 @@
   for (var pair in queryList.split("&")) {
     var split = split1(pair, "=");
     if (split.isEmpty) continue;
-    var key = urlDecode(split[0]);
-    var value = split.length > 1 ? urlDecode(split[1]) : "";
+    var key = _urlDecode(split[0]);
+    var value = split.length > 1 ? _urlDecode(split[1]) : "";
     map[key] = value;
   }
   return map;
 }
 
-/// Convert a [Map] from parameter names to values to a URL query string.
-String mapToQuery(Map<String, String> map) {
-  var pairs = <List<String>>[];
-  map.forEach((key, value) {
-    key = Uri.encodeQueryComponent(key);
-    value = (value == null || value.isEmpty)
-        ? null
-        : Uri.encodeQueryComponent(value);
-    pairs.add([key, value]);
-  });
-  return pairs.map((pair) {
-    if (pair[1] == null) return pair[0];
-    return "${pair[0]}=${pair[1]}";
-  }).join("&");
-}
-
 /// Returns a human-friendly representation of [duration].
 String niceDuration(Duration duration) {
   var hasMinutes = duration.inMinutes > 0;
@@ -542,43 +445,19 @@
   // If we're using verbose logging, be more verbose but more accurate when
   // reporting timing information.
   var msString = log.verbosity.isLevelVisible(log.Level.FINE)
-      ? padLeft(ms.toString(), 3, '0')
+      ? _padLeft(ms.toString(), 3, '0')
       : (ms ~/ 100).toString();
 
-  return "$result${hasMinutes ? padLeft(s.toString(), 2, '0') : s}"
+  return "$result${hasMinutes ? _padLeft(s.toString(), 2, '0') : s}"
       ".${msString}s";
 }
 
 /// Decodes a URL-encoded string.
 ///
 /// Unlike [Uri.decodeComponent], this includes replacing `+` with ` `.
-String urlDecode(String encoded) =>
+String _urlDecode(String encoded) =>
     Uri.decodeComponent(encoded.replaceAll("+", " "));
 
-/// Takes a simple data structure (composed of [Map]s, [Iterable]s, scalar
-/// objects, and [Future]s) and recursively resolves all the [Future]s contained
-/// within.
-///
-/// Completes with the fully resolved structure.
-Future<T> awaitObject<T>(T object) async {
-  // Unroll nested futures.
-  if (object is Future) return await awaitObject(await object);
-
-  if (object is Iterable) {
-    return await Future.wait(object.map(awaitObject)) as T;
-  }
-
-  if (object is Map) {
-    var newMap = {};
-    await Future.wait(object.keys.map((key) async {
-      newMap[key] = await awaitObject(await object[key]);
-    }));
-    return newMap as T;
-  }
-
-  return object;
-}
-
 /// Whether "special" strings such as Unicode characters or color escapes are
 /// safe to use.
 ///
@@ -723,7 +602,7 @@
       '${chars.substring(12, 16)}-${chars.substring(16, 20)}-${chars.substring(20, 32)}';
 }
 
-/// Wraps [text] so that it fits within [lineLength], if there is a line length.
+/// Wraps [text] so that it fits within [_lineLength], if there is a line length.
 ///
 /// This preserves existing newlines and doesn't consider terminal color escapes
 /// part of a word's length. It only splits words on spaces, not on other sorts
@@ -732,7 +611,7 @@
 /// If [prefix] is passed, it's added at the beginning of any wrapped lines.
 String wordWrap(String text, {String prefix}) {
   // If there is no limit, don't wrap.
-  if (lineLength == null) return text;
+  if (_lineLength == null) return text;
 
   prefix ??= "";
   return text.split("\n").map((originalLine) {
@@ -740,8 +619,8 @@
     var lengthSoFar = 0;
     var firstLine = true;
     for (var word in originalLine.split(" ")) {
-      var wordLength = withoutColors(word).length;
-      if (wordLength > lineLength) {
+      var wordLength = _withoutColors(word).length;
+      if (wordLength > _lineLength) {
         if (lengthSoFar != 0) buffer.writeln();
         if (!firstLine) buffer.write(prefix);
         buffer.writeln(word);
@@ -750,7 +629,7 @@
         if (!firstLine) buffer.write(prefix);
         buffer.write(word);
         lengthSoFar = wordLength + prefix.length;
-      } else if (lengthSoFar + 1 + wordLength > lineLength) {
+      } else if (lengthSoFar + 1 + wordLength > _lineLength) {
         buffer.writeln();
         buffer.write(prefix);
         buffer.write(word);
@@ -769,7 +648,7 @@
 final _colorCode = new RegExp('\u001b\\[[0-9;]+m');
 
 /// Returns [str] without any color codes.
-String withoutColors(String str) => str.replaceAll(_colorCode, '');
+String _withoutColors(String str) => str.replaceAll(_colorCode, '');
 
 /// A regular expression to match the exception prefix that some exceptions'
 /// [Object.toString] values contain.
diff --git a/lib/src/validator/sdk_constraint.dart b/lib/src/validator/sdk_constraint.dart
index 7f8d908..30a828a 100644
--- a/lib/src/validator/sdk_constraint.dart
+++ b/lib/src/validator/sdk_constraint.dart
@@ -8,7 +8,6 @@
 
 import '../entrypoint.dart';
 import '../sdk.dart';
-import '../utils.dart';
 import '../validator.dart';
 
 /// A validator that validates that a package's SDK constraint doesn't use the
diff --git a/test/descriptor/tar.dart b/test/descriptor/tar.dart
index f8f4e6e..3e1480d 100644
--- a/test/descriptor/tar.dart
+++ b/test/descriptor/tar.dart
@@ -3,8 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:io';
 
 import 'package:path/path.dart' as path;
+import 'package:pub/src/log.dart' as log;
 import 'package:pub/src/io.dart';
 import 'package:test_descriptor/test_descriptor.dart';
 
@@ -28,7 +30,7 @@
           await createTarGz(createdContents, baseDir: tempDir).toBytes();
 
       var file = path.join(parent ?? sandbox, name);
-      writeBinaryFile(file, bytes);
+      _writeBinaryFile(file, bytes);
       return file;
     });
   }
@@ -49,3 +51,14 @@
     }));
   }
 }
+
+/// Creates [file] and writes [contents] to it.
+String _writeBinaryFile(String file, List<int> contents) {
+  log.io("Writing ${contents.length} bytes to binary file $file.");
+  deleteIfLink(file);
+  new File(file).openSync(mode: FileMode.WRITE)
+    ..writeFromSync(contents)
+    ..closeSync();
+  log.fine("Wrote text file $file.");
+  return file;
+}
diff --git a/test/descriptor_server.dart b/test/descriptor_server.dart
index f438cf7..92b9066 100644
--- a/test/descriptor_server.dart
+++ b/test/descriptor_server.dart
@@ -83,7 +83,7 @@
       requestedPaths.add(path);
 
       try {
-        var stream = await validateStream(_baseDir.load(path));
+        var stream = await _validateStream(_baseDir.load(path));
         return new shelf.Response.ok(stream);
       } catch (_) {
         return new shelf.Response.notFound('File "$path" not found.');
@@ -103,3 +103,46 @@
   /// Closes this server.
   Future close() => _server.close();
 }
+
+/// Ensures that [stream] can emit at least one value successfully (or close
+/// without any values).
+///
+/// For example, reading asynchronously from a non-existent file will return a
+/// stream that fails on the first chunk. In order to handle that more
+/// gracefully, you may want to check that the stream looks like it's working
+/// before you pipe the stream to something else.
+///
+/// This lets you do that. It returns a [Future] that completes to a [Stream]
+/// emitting the same values and errors as [stream], but only if at least one
+/// value can be read successfully. If an error occurs before any values are
+/// emitted, the returned Future completes to that error.
+Future<Stream<T>> _validateStream<T>(Stream<T> stream) {
+  var completer = new Completer<Stream>();
+  var controller = new StreamController(sync: true);
+
+  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]) {
+    // If the error came after values, it's OK.
+    if (completer.isCompleted) {
+      controller.addError(error, stackTrace);
+      return;
+    }
+
+    // Otherwise, the error came first and the stream is invalid.
+    completer.completeError(error, stackTrace);
+
+    // We won't be returning the stream at all in this case, so unsubscribe
+    // and swallow the error.
+    subscription.cancel();
+  }, onDone: () {
+    // It closed with no errors, so the stream is valid.
+    if (!completer.isCompleted) completer.complete(controller.stream);
+    controller.close();
+  });
+
+  return completer.future;
+}
diff --git a/test/error_group_test.dart b/test/error_group_test.dart
index 5b912c4..020481b 100644
--- a/test/error_group_test.dart
+++ b/test/error_group_test.dart
@@ -5,7 +5,6 @@
 import 'dart:async';
 
 import 'package:pub/src/error_group.dart';
-import 'package:pub/src/utils.dart';
 import 'package:test/test.dart';
 
 ErrorGroup errorGroup;
diff --git a/test/io_test.dart b/test/io_test.dart
index a381e08..33be4ff 100644
--- a/test/io_test.dart
+++ b/test/io_test.dart
@@ -16,7 +16,7 @@
         writeTextFile(path.join(temp, 'file1.txt'), '');
         writeTextFile(path.join(temp, 'file2.txt'), '');
         writeTextFile(path.join(temp, '.file3.txt'), '');
-        createDir(path.join(temp, '.subdir'));
+        _createDir(path.join(temp, '.subdir'));
         writeTextFile(path.join(temp, '.subdir', 'file3.txt'), '');
 
         expect(
@@ -31,7 +31,7 @@
         writeTextFile(path.join(temp, 'file1.txt'), '');
         writeTextFile(path.join(temp, 'file2.txt'), '');
         writeTextFile(path.join(temp, '.file3.txt'), '');
-        createDir(path.join(temp, '.subdir'));
+        _createDir(path.join(temp, '.subdir'));
         writeTextFile(path.join(temp, '.subdir', 'file3.txt'), '');
 
         expect(
@@ -67,7 +67,7 @@
 
   group('canonicalize', () {
     test('resolves a non-link', () {
-      expect(withCanonicalTempDir((temp) {
+      expect(_withCanonicalTempDir((temp) {
         var filePath = path.join(temp, 'file');
         writeTextFile(filePath, '');
         expect(canonicalize(filePath), equals(filePath));
@@ -75,15 +75,15 @@
     });
 
     test('resolves a non-existent file', () {
-      expect(withCanonicalTempDir((temp) {
+      expect(_withCanonicalTempDir((temp) {
         expect(canonicalize(path.join(temp, 'nothing')),
             equals(path.join(temp, 'nothing')));
       }), completes);
     });
 
     test('resolves a symlink', () {
-      expect(withCanonicalTempDir((temp) {
-        createDir(path.join(temp, 'linked-dir'));
+      expect(_withCanonicalTempDir((temp) {
+        _createDir(path.join(temp, 'linked-dir'));
         createSymlink(path.join(temp, 'linked-dir'), path.join(temp, 'dir'));
         expect(canonicalize(path.join(temp, 'dir')),
             equals(path.join(temp, 'linked-dir')));
@@ -91,8 +91,8 @@
     });
 
     test('resolves a relative symlink', () {
-      expect(withCanonicalTempDir((temp) {
-        createDir(path.join(temp, 'linked-dir'));
+      expect(_withCanonicalTempDir((temp) {
+        _createDir(path.join(temp, 'linked-dir'));
         createSymlink(path.join(temp, 'linked-dir'), path.join(temp, 'dir'),
             relative: true);
         expect(canonicalize(path.join(temp, 'dir')),
@@ -101,7 +101,7 @@
     });
 
     test('resolves a single-level horizontally recursive symlink', () {
-      expect(withCanonicalTempDir((temp) {
+      expect(_withCanonicalTempDir((temp) {
         var linkPath = path.join(temp, 'foo');
         createSymlink(linkPath, linkPath);
         expect(canonicalize(linkPath), equals(linkPath));
@@ -109,7 +109,7 @@
     });
 
     test('resolves a multi-level horizontally recursive symlink', () {
-      expect(withCanonicalTempDir((temp) {
+      expect(_withCanonicalTempDir((temp) {
         var fooPath = path.join(temp, 'foo');
         var barPath = path.join(temp, 'bar');
         var bazPath = path.join(temp, 'baz');
@@ -126,7 +126,7 @@
     });
 
     test('resolves a broken symlink', () {
-      expect(withCanonicalTempDir((temp) {
+      expect(_withCanonicalTempDir((temp) {
         createSymlink(path.join(temp, 'nonexistent'), path.join(temp, 'foo'));
         expect(canonicalize(path.join(temp, 'foo')),
             equals(path.join(temp, 'nonexistent')));
@@ -134,13 +134,13 @@
     });
 
     test('resolves multiple nested symlinks', () {
-      expect(withCanonicalTempDir((temp) {
+      expect(_withCanonicalTempDir((temp) {
         var dir1 = path.join(temp, 'dir1');
         var dir2 = path.join(temp, 'dir2');
         var subdir1 = path.join(dir1, 'subdir1');
         var subdir2 = path.join(dir2, 'subdir2');
-        createDir(dir2);
-        createDir(subdir2);
+        _createDir(dir2);
+        _createDir(subdir2);
         createSymlink(dir2, dir1);
         createSymlink(subdir2, subdir1);
         expect(canonicalize(path.join(subdir1, 'file')),
@@ -149,12 +149,12 @@
     });
 
     test('resolves a nested vertical symlink', () {
-      expect(withCanonicalTempDir((temp) {
+      expect(_withCanonicalTempDir((temp) {
         var dir1 = path.join(temp, 'dir1');
         var dir2 = path.join(temp, 'dir2');
         var subdir = path.join(dir1, 'subdir');
-        createDir(dir1);
-        createDir(dir2);
+        _createDir(dir1);
+        _createDir(dir2);
         createSymlink(dir2, subdir);
         expect(canonicalize(path.join(subdir, 'file')),
             equals(path.join(dir2, 'file')));
@@ -162,10 +162,10 @@
     });
 
     test('resolves a vertically recursive symlink', () {
-      expect(withCanonicalTempDir((temp) {
+      expect(_withCanonicalTempDir((temp) {
         var dir = path.join(temp, 'dir');
         var subdir = path.join(dir, 'subdir');
-        createDir(dir);
+        _createDir(dir);
         createSymlink(dir, subdir);
         expect(
             canonicalize(path.join(
@@ -176,11 +176,11 @@
 
     test('resolves a symlink that links to a path that needs more resolving',
         () {
-      expect(withCanonicalTempDir((temp) {
+      expect(_withCanonicalTempDir((temp) {
         var dir = path.join(temp, 'dir');
         var linkdir = path.join(temp, 'linkdir');
         var linkfile = path.join(dir, 'link');
-        createDir(dir);
+        _createDir(dir);
         createSymlink(dir, linkdir);
         createSymlink(path.join(linkdir, 'file'), linkfile);
         expect(canonicalize(linkfile), equals(path.join(dir, 'file')));
@@ -188,7 +188,7 @@
     });
 
     test('resolves a pair of pathologically-recursive symlinks', () {
-      expect(withCanonicalTempDir((temp) {
+      expect(_withCanonicalTempDir((temp) {
         var foo = path.join(temp, 'foo');
         var subfoo = path.join(foo, 'subfoo');
         var bar = path.join(temp, 'bar');
@@ -263,7 +263,7 @@
     test('returns $forDirectory for a directory', () {
       expect(withTempDir((temp) {
         var file = path.join(temp, "dir");
-        createDir(file);
+        _createDir(file);
         expect(predicate(file), equals(forDirectory));
       }), completes);
     });
@@ -272,7 +272,7 @@
       expect(withTempDir((temp) {
         var targetPath = path.join(temp, "dir");
         var symlinkPath = path.join(temp, "linkdir");
-        createDir(targetPath);
+        _createDir(targetPath);
         createSymlink(targetPath, symlinkPath);
         expect(predicate(symlinkPath), equals(forDirectorySymlink));
       }), completes);
@@ -285,7 +285,7 @@
         var targetPath = path.join(temp, "dir");
         var symlink1Path = path.join(temp, "link1dir");
         var symlink2Path = path.join(temp, "link2dir");
-        createDir(targetPath);
+        _createDir(targetPath);
         createSymlink(targetPath, symlink1Path);
         createSymlink(symlink1Path, symlink2Path);
         expect(predicate(symlink2Path), equals(forMultiLevelDirectorySymlink));
@@ -296,7 +296,7 @@
       expect(withTempDir((temp) {
         var targetPath = path.join(temp, "dir");
         var symlinkPath = path.join(temp, "linkdir");
-        createDir(targetPath);
+        _createDir(targetPath);
         createSymlink(targetPath, symlinkPath);
         deleteEntry(targetPath);
         expect(predicate(symlinkPath), equals(forBrokenSymlink));
@@ -309,7 +309,7 @@
         var targetPath = path.join(temp, "dir");
         var symlink1Path = path.join(temp, "link1dir");
         var symlink2Path = path.join(temp, "link2dir");
-        createDir(targetPath);
+        _createDir(targetPath);
         createSymlink(targetPath, symlink1Path);
         createSymlink(symlink1Path, symlink2Path);
         deleteEntry(targetPath);
@@ -344,25 +344,14 @@
       });
     }
   });
-
-  group('topLevelDir', () {
-    test('returns the top level dir in a path', () {
-      expect(topLevelDir('foo/bar/baz.dart'), 'foo');
-      expect(topLevelDir('foo/../bar/baz.dart'), 'bar');
-      expect(topLevelDir('./foo/baz.dart'), 'foo');
-    });
-
-    test('throws for invalid paths', () {
-      expect(() => topLevelDir('foo/../../bar.dart'), throwsArgumentError,
-          reason: 'Paths reaching outside the root dir should throw.');
-      expect(() => topLevelDir('foo.dart'), throwsArgumentError,
-          reason: 'Paths to the root directory should throw.');
-      expect(() => topLevelDir('foo/../foo.dart'), throwsArgumentError,
-          reason: 'Normalized paths to the root directory should throw.');
-    });
-  });
 }
 
 /// Like [withTempDir], but canonicalizes the path before passing it to [fn].
-Future withCanonicalTempDir(Future fn(String path)) =>
+Future _withCanonicalTempDir(Future fn(String path)) =>
     withTempDir((temp) => fn(canonicalize(temp)));
+
+/// Creates a directory [dir].
+String _createDir(String dir) {
+  new Directory(dir).createSync();
+  return dir;
+}
diff --git a/test/lish/cloud_storage_upload_doesnt_redirect_test.dart b/test/lish/cloud_storage_upload_doesnt_redirect_test.dart
index c4a0884..e383cd4 100644
--- a/test/lish/cloud_storage_upload_doesnt_redirect_test.dart
+++ b/test/lish/cloud_storage_upload_doesnt_redirect_test.dart
@@ -6,8 +6,6 @@
 import 'package:shelf_test_handler/shelf_test_handler.dart';
 import 'package:test/test.dart';
 
-import 'package:pub/src/io.dart';
-
 import '../descriptor.dart' as d;
 import '../test_pub.dart';
 import 'utils.dart';
@@ -24,7 +22,7 @@
     handleUploadForm(server);
 
     server.handler.expect('POST', '/upload', (request) async {
-      await drainStream(request.read());
+      await request.read().drain();
       return new shelf.Response(200);
     });
 
diff --git a/test/lish/cloud_storage_upload_provides_an_error_test.dart b/test/lish/cloud_storage_upload_provides_an_error_test.dart
index 1b0a93d..452b73f 100644
--- a/test/lish/cloud_storage_upload_provides_an_error_test.dart
+++ b/test/lish/cloud_storage_upload_provides_an_error_test.dart
@@ -6,8 +6,6 @@
 import 'package:shelf_test_handler/shelf_test_handler.dart';
 import 'package:test/test.dart';
 
-import 'package:pub/src/io.dart';
-
 import '../descriptor.dart' as d;
 import '../test_pub.dart';
 import 'utils.dart';
@@ -24,7 +22,7 @@
     handleUploadForm(server);
 
     server.handler.expect('POST', '/upload', (request) {
-      return drainStream(request.read()).then((_) {
+      return request.read().drain().then((_) {
         return new shelf.Response.notFound(
             '<Error><Message>Your request sucked.</Message></Error>',
             headers: {'content-type': 'application/xml'});
diff --git a/test/lish/utils.dart b/test/lish/utils.dart
index c6d8400..02c74b8 100644
--- a/test/lish/utils.dart
+++ b/test/lish/utils.dart
@@ -8,8 +8,6 @@
 import 'package:shelf_test_handler/shelf_test_handler.dart';
 import 'package:test/test.dart';
 
-import 'package:pub/src/io.dart';
-
 void handleUploadForm(ShelfTestServer server, [Map body]) {
   server.handler.expect('GET', '/api/packages/versions/new', (request) {
     expect(
@@ -31,7 +29,9 @@
   server.handler.expect('POST', '/upload', (request) {
     // TODO(nweiz): Once a multipart/form-data parser in Dart exists, validate
     // that the request body is correctly formatted. See issue 6952.
-    return drainStream(request.read())
+    return request
+        .read()
+        .drain()
         .then((_) => server.url)
         .then((url) => new shelf.Response.found(url.resolve('/create')));
   });
diff --git a/test/oauth2/utils.dart b/test/oauth2/utils.dart
index 51610d6..7bbf1d1 100644
--- a/test/oauth2/utils.dart
+++ b/test/oauth2/utils.dart
@@ -26,7 +26,7 @@
   expect(match, isNotNull);
 
   var redirectUrl = Uri.parse(Uri.decodeComponent(match.group(1)));
-  redirectUrl = addQueryParameters(redirectUrl, {'code': 'access code'});
+  redirectUrl = _addQueryParameters(redirectUrl, {'code': 'access code'});
   var response = await (new http.Request('GET', redirectUrl)
         ..followRedirects = false)
       .send();
@@ -46,3 +46,27 @@
         headers: {'content-type': 'application/json'});
   });
 }
+
+/// Adds additional query parameters to [url], overwriting the original
+/// parameters if a name conflict occurs.
+Uri _addQueryParameters(Uri url, Map<String, String> parameters) {
+  var queryMap = queryToMap(url.query);
+  queryMap.addAll(parameters);
+  return url.resolve("?${_mapToQuery(queryMap)}");
+}
+
+/// Convert a [Map] from parameter names to values to a URL query string.
+String _mapToQuery(Map<String, String> map) {
+  var pairs = <List<String>>[];
+  map.forEach((key, value) {
+    key = Uri.encodeQueryComponent(key);
+    value = (value == null || value.isEmpty)
+        ? null
+        : Uri.encodeQueryComponent(value);
+    pairs.add([key, value]);
+  });
+  return pairs.map((pair) {
+    if (pair[1] == null) return pair[0];
+    return "${pair[0]}=${pair[1]}";
+  }).join("&");
+}
diff --git a/test/oauth2/with_a_server_rejected_refresh_token_authenticates_again_test.dart b/test/oauth2/with_a_server_rejected_refresh_token_authenticates_again_test.dart
index eaa17a4..c41afc1 100644
--- a/test/oauth2/with_a_server_rejected_refresh_token_authenticates_again_test.dart
+++ b/test/oauth2/with_a_server_rejected_refresh_token_authenticates_again_test.dart
@@ -4,7 +4,6 @@
 
 import 'dart:convert';
 
-import 'package:pub/src/io.dart';
 import 'package:shelf/shelf.dart' as shelf;
 import 'package:shelf_test_handler/shelf_test_handler.dart';
 import 'package:test/test.dart';
@@ -32,7 +31,7 @@
     await confirmPublish(pub);
 
     server.handler.expect('POST', '/token', (request) {
-      return drainStream(request.read()).then((_) {
+      return request.read().drain().then((_) {
         return new shelf.Response(400,
             body: jsonEncode({"error": "invalid_request"}),
             headers: {'content-type': 'application/json'});