Add a Dart script to prepare the flutter repo for packaging. (#13548)

This is the first step in a two-step process of moving the package preparation step from a recipe in chrome_infra to a dart script in the flutter repo. This will make it easier to make changes to the process. The second step is to change the infra recipe to call this script.

In addition, I added a step to the packaging process to run flutter create for each type of template so that any pub dependencies of the templates get added to the cache that gets packaged (and thus users can run flutter create --offline and have it work).

Note that the actual packaging into a "tar" or "zip" file now happens here, so a developer could actually run this script on their machine to create a package.
diff --git a/dev/bots/test/prepare_package_test.dart b/dev/bots/test/prepare_package_test.dart
new file mode 100644
index 0000000..0d7a3a9
--- /dev/null
+++ b/dev/bots/test/prepare_package_test.dart
@@ -0,0 +1,193 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:mockito/mockito.dart';
+import 'package:test/test.dart';
+import 'package:path/path.dart' as path;
+
+import '../prepare_package.dart';
+
+void main() {
+  group('ArchiveCreator', () {
+    ArchiveCreator preparer;
+    Directory tmpDir;
+    Directory flutterDir;
+    File outputFile;
+    MockProcessRunner runner;
+    List<MockProcessResult> results;
+    final List<List<String>> args = <List<String>>[];
+    final List<Map<Symbol, dynamic>> namedArgs = <Map<Symbol, dynamic>>[];
+    final String zipExe = Platform.isWindows ? 'zip.exe' : 'zip';
+    final String tarExe = Platform.isWindows ? 'tar.exe' : 'tar';
+    final String gitExe = Platform.isWindows ? 'git.bat' : 'git';
+    String flutterExe;
+
+    void _verifyCommand(List<dynamic> args, String expected) {
+      final List<String> expectedList = expected.split(' ');
+      final String executable = expectedList.removeAt(0);
+      expect(args[0], executable);
+      expect(args[1], orderedEquals(expectedList));
+    }
+
+    ProcessResult _nextResult(Invocation invocation) {
+      args.add(invocation.positionalArguments);
+      namedArgs.add(invocation.namedArguments);
+      return results.isEmpty ? new MockProcessResult('', '', 0) : results.removeAt(0);
+    }
+
+    void _answerWithResults() {
+      when(
+        runner.call(
+          typed(captureAny),
+          typed(captureAny),
+          environment: typed(captureAny, named: 'environment'),
+          workingDirectory: typed(captureAny, named: 'workingDirectory'),
+          includeParentEnvironment: typed(captureAny, named: 'includeParentEnvironment'),
+        ),
+      ).thenAnswer(_nextResult);
+    }
+
+    setUp(() async {
+      runner = new MockProcessRunner();
+      args.clear();
+      namedArgs.clear();
+      tmpDir = await Directory.systemTemp.createTemp('flutter_');
+      flutterDir = new Directory(path.join(tmpDir.path, 'flutter'));
+      flutterExe =
+          path.join(flutterDir.path, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter');
+    });
+
+    tearDown(() async {
+      await tmpDir.delete(recursive: true);
+    });
+
+    test('sets PUB_CACHE properly', () async {
+      outputFile = new File(path.join(tmpDir.absolute.path, 'flutter_master.tar.bz2'));
+      preparer = new ArchiveCreator(tmpDir, outputFile, runner: runner);
+      _answerWithResults();
+      results = <MockProcessResult>[new MockProcessResult('deadbeef\n', '', 0)];
+      preparer.checkoutFlutter('master');
+      preparer.prepareArchive();
+      preparer.createArchive();
+      expect(
+        verify(runner.call(
+          captureAny,
+          captureAny,
+          workingDirectory: captureAny,
+          environment: captureAny,
+          includeParentEnvironment: typed(captureAny, named: 'includeParentEnvironment'),
+        )).captured[2]['PUB_CACHE'],
+        endsWith(path.join('flutter', '.pub-cache')),
+      );
+    });
+
+    test('calls the right commands for tar output', () async {
+      outputFile = new File(path.join(tmpDir.absolute.path, 'flutter_master.tar.bz2'));
+      preparer = new ArchiveCreator(tmpDir, outputFile, runner: runner);
+      _answerWithResults();
+      results = <MockProcessResult>[new MockProcessResult('deadbeef\n', '', 0)];
+      preparer.checkoutFlutter('master');
+      preparer.prepareArchive();
+      preparer.createArchive();
+      final List<String> commands = <String>[
+        '$gitExe clone -b master https://chromium.googlesource.com/external/github.com/flutter/flutter',
+        '$gitExe reset --hard master',
+        '$gitExe remote remove origin',
+        '$gitExe remote add origin https://github.com/flutter/flutter.git',
+        '$flutterExe doctor',
+        '$flutterExe update-packages',
+        '$flutterExe precache',
+        '$flutterExe ide-config',
+        '$flutterExe create --template=app ${path.join(tmpDir.path, 'create_app')}',
+        '$flutterExe create --template=package ${path.join(tmpDir.path, 'create_package')}',
+        '$flutterExe create --template=plugin ${path.join(tmpDir.path, 'create_plugin')}',
+        '$gitExe clean -f -X **/.packages',
+        '$tarExe cjf ${path.join(tmpDir.path, 'flutter_master.tar.bz2')} flutter',
+      ];
+      int step = 0;
+      for (String command in commands) {
+        _verifyCommand(args[step++], command);
+      }
+    });
+
+    test('calls the right commands for zip output', () async {
+      outputFile = new File(path.join(tmpDir.absolute.path, 'flutter_master.zip'));
+      preparer = new ArchiveCreator(tmpDir, outputFile, runner: runner);
+      _answerWithResults();
+      results = <MockProcessResult>[new MockProcessResult('deadbeef\n', '', 0)];
+      preparer.checkoutFlutter('master');
+      preparer.prepareArchive();
+      preparer.createArchive();
+      final List<String> commands = <String>[
+        '$gitExe clone -b master https://chromium.googlesource.com/external/github.com/flutter/flutter',
+        '$gitExe reset --hard master',
+        '$gitExe remote remove origin',
+        '$gitExe remote add origin https://github.com/flutter/flutter.git',
+        '$flutterExe doctor',
+        '$flutterExe update-packages',
+        '$flutterExe precache',
+        '$flutterExe ide-config',
+        '$flutterExe create --template=app ${path.join(tmpDir.path, 'create_app')}',
+        '$flutterExe create --template=package ${path.join(tmpDir.path, 'create_package')}',
+        '$flutterExe create --template=plugin ${path.join(tmpDir.path, 'create_plugin')}',
+        '$gitExe clean -f -X **/.packages',
+        '$zipExe -r -9 -q ${path.join(tmpDir.path, 'flutter_master.zip')} flutter',
+      ];
+      int step = 0;
+      for (String command in commands) {
+        _verifyCommand(args[step++], command);
+      }
+    });
+
+    test('throws when a command errors out', () async {
+      outputFile = new File(path.join(tmpDir.absolute.path, 'flutter.tar.bz2'));
+      preparer = new ArchiveCreator(
+        tmpDir,
+        outputFile,
+        runner: runner,
+      );
+
+      results = <MockProcessResult>[
+        new MockProcessResult('', '', 0),
+        new MockProcessResult('OMG! OMG! an ERROR!\n', '', -1)
+      ];
+      _answerWithResults();
+      expect(() => preparer.checkoutFlutter('master'),
+          throwsA(const isInstanceOf<ProcessFailedException>()));
+      expect(args.length, 2);
+      _verifyCommand(args[0],
+          '$gitExe clone -b master https://chromium.googlesource.com/external/github.com/flutter/flutter');
+      _verifyCommand(args[1], '$gitExe reset --hard master');
+    });
+  });
+}
+
+class MockProcessRunner extends Mock implements Function {
+  ProcessResult call(
+    String executable,
+    List<String> arguments, {
+    String workingDirectory,
+    Map<String, String> environment,
+    bool includeParentEnvironment,
+    bool runInShell,
+    Encoding stdoutEncoding,
+    Encoding stderrEncoding,
+  });
+}
+
+class MockProcessResult extends Mock implements ProcessResult {
+  MockProcessResult(this.stdout, [this.stderr = '', this.exitCode = 0]);
+
+  @override
+  dynamic stdout = '';
+
+  @override
+  dynamic stderr;
+
+  @override
+  int exitCode;
+}