anchor.diff:        environment['DART_SDK'] = pathInSandbox(sdkPath);
   
         return runProcess(dartBin, dartArgs, workingDir: pathInSandbox(appPath),
  -          environment: environment, pipeStdout: output == null,
  -          pipeStderr: error == null);
  +          environment: environment);
       }).transform((result) {
  -      _validateOutput(output, result.stdout);
  -      _validateOutput(error, result.stderr);
  -
  -      Expect.equals(result.exitCode, exitCode,
  -          'Pub returned exit code ${result.exitCode}, expected $exitCode.');
  +      var failures = [];
  +
  +      _validateOutput(failures, 'stdout', output, result.stdout);
  +      _validateOutput(failures, 'stderr', error, result.stderr);
  +
  +      if (result.exitCode != exitCode) {
  +        failures.add(
  +            'Pub returned exit code ${result.exitCode}, expected $exitCode.');
  +      }
  +
  +      if (failures.length > 0) {
  +        if (error == null) {
  +          // If we aren't validating the error, still show it on failure.
  +          failures.add('Pub stderr:');
  +          failures.addAll(result.stderr.map((line) => '| $line'));
  +        }
  +
  +        throw new ExpectException(Strings.join(failures, '\n'));
  +      }
   
         return null;
       });
  
positive.code:  * report the offending difference in a nice way. For other [Pattern]s, just
   * reports whether the output contained the pattern.
   */
  void _validateOutput(Pattern expected, List<String> actual) {
    if (expected == null) return;
  
    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
  
negative.code:   // to expect zero lines of output, not a single empty line.
    expected.removeLast();
  
    var results = [];
    var failed = false;
  
    // Compare them line by line to see which ones match.
    var length = max(expected.length, actual.length);
    for (var i = 0; i < length; i++) {
      if (i >= actual.length) {
        // Missing output.
        failed = true;
        results.add('? ${expected[i]}');
      } else if (i >= expected.length) {
        // Unexpected extra output.
        failed = true;
        results.add('X ${actual[i]}');
      } else {
        var expectedLine = expected[i].trim();
        var actualLine = actual[i].trim();
  
        if (expectedLine != actualLine) {
          // Mismatched lines.
          failed = true;
          results.add('X ${actual[i]}');
        } else {
          // Output is OK, but include it in case other lines are wrong.
          results.add('| ${actual[i]}');
        }
      }
    }
  
    // If any lines mismatched, show the expected and actual.
    if (failed) {
      failures.add('Expected $pipe:');
      failures.addAll(expected.map((line) => '| $line'));
      failures.add('Got:');
      failures.addAll(results);
    }
  }
  
  