| Index: utils/tests/pub/test_pub.dart
 | 
| diff --git a/utils/tests/pub/test_pub.dart b/utils/tests/pub/test_pub.dart
 | 
| index e2fbfdb617cee88b1af09ac5754a3459833c64ab..2fcab1cada389ed7968df8e58669acb07843a20b 100644
 | 
| --- a/utils/tests/pub/test_pub.dart
 | 
| +++ b/utils/tests/pub/test_pub.dart
 | 
| @@ -29,6 +29,12 @@ DirectoryDescriptor dir(String name, [List<Descriptor> contents]) =>
 | 
|      new DirectoryDescriptor(name, contents);
 | 
|  
 | 
|  /**
 | 
| + * Creates a new [GitRepoDescriptor] with [name] and [contents].
 | 
| + */
 | 
| +DirectoryDescriptor git(String name, [List<Descriptor> contents]) =>
 | 
| +    new GitRepoDescriptor(name, contents);
 | 
| +
 | 
| +/**
 | 
|   * The path of the package cache directory used for tests. Relative to the
 | 
|   * sandbox directory.
 | 
|   */
 | 
| @@ -69,7 +75,7 @@ List<_ScheduledEvent> _scheduledBeforePub;
 | 
|   */
 | 
|  List<_ScheduledEvent> _scheduledAfterPub;
 | 
|  
 | 
| -void runPub([List<String> args, String output, int exitCode = 0]) {
 | 
| +void runPub([List<String> args, Pattern output, int exitCode = 0]) {
 | 
|    var createdSandboxDir;
 | 
|  
 | 
|    var asyncDone = expectAsync0(() {});
 | 
| @@ -157,11 +163,19 @@ Future<ProcessResult> _runPub(List<String> pubArgs, String workingDir) {
 | 
|  }
 | 
|  
 | 
|  /**
 | 
| - * Compares the [actual] output from running pub with [expectedText]. Ignores
 | 
| - * leading and trailing whitespace differences and tries to report the
 | 
| - * offending difference in a nice way.
 | 
| + * Compares the [actual] output from running pub with [expected]. For [String]
 | 
| + * patterns, ignores leading and trailing whitespace differences and tries to
 | 
| + * report the offending difference in a nice way. For other [Pattern]s, just
 | 
| + * reports whether the output contained the pattern.
 | 
|   */
 | 
| -void _validateOutput(String expectedText, List<String> actual) {
 | 
| +void _validateOutput(Pattern expected, List<String> actual) {
 | 
| +  if (expected is String) return _validateOutputString(expected, actual);
 | 
| +  var actualText = Strings.join(actual, "\n");
 | 
| +  if (actualText.contains(expected)) return;
 | 
| +  Expect.fail('Expected output to match "$expected", was:\n$actualText');
 | 
| +}
 | 
| +
 | 
| +void _validateOutputString(String expectedText, List<String> actual) {
 | 
|    final expected = expectedText.split('\n');
 | 
|  
 | 
|    // Strip off the last line. This lets us have expected multiline strings
 | 
| @@ -230,7 +244,8 @@ class Descriptor {
 | 
|     * Schedules the directory to be created before Pub is run with [runPub]. The
 | 
|     * directory will be created relative to the sandbox directory.
 | 
|     */
 | 
| -  void scheduleCreate() => _scheduleBeforePub(create);
 | 
| +  // TODO(nweiz): Use implicit closurization once issue 2984 is fixed.
 | 
| +  void scheduleCreate() => _scheduleBeforePub((dir) => this.create(dir));
 | 
|  
 | 
|    /**
 | 
|     * Schedules the directory to be validated after Pub is run with [runPub]. The
 | 
| @@ -333,6 +348,35 @@ class DirectoryDescriptor extends Descriptor {
 | 
|  }
 | 
|  
 | 
|  /**
 | 
| + * Describes a Git repository and its contents.
 | 
| + */
 | 
| +class GitRepoDescriptor extends DirectoryDescriptor {
 | 
| +  GitRepoDescriptor(String name, List<Descriptor> contents)
 | 
| +  : super(name, contents);
 | 
| +
 | 
| +  /**
 | 
| +   * Creates the Git repository and commits the contents.
 | 
| +   */
 | 
| +  Future<Directory> create(parentDir) {
 | 
| +    var workingDir;
 | 
| +    Future runGit(List<String> args) {
 | 
| +      return runProcess('git', args, workingDir: workingDir.path).
 | 
| +        transform((result) {
 | 
| +          if (!result.success) throw "Error running git: ${result.stderr}";
 | 
| +          return null;
 | 
| +        });
 | 
| +    }
 | 
| +
 | 
| +    return super.create(parentDir).chain((rootDir) {
 | 
| +      workingDir = rootDir;
 | 
| +      return runGit(['init']);
 | 
| +    }).chain((_) => runGit(['add', '.']))
 | 
| +      .chain((_) => runGit(['commit', '-m', 'initial commit']))
 | 
| +      .transform((_) => workingDir);
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +/**
 | 
|   * Schedules a callback to be called before Pub is run with [runPub].
 | 
|   */
 | 
|  void _scheduleBeforePub(_ScheduledEvent event) {
 | 
| 
 |