Fix error handling for the packaging script (#15351)

This fixes the error handling for the packaging script so that it will properly report a failure exit code when it can't find the executable that it's looking for.

Added a test too.
diff --git a/dev/bots/prepare_package.dart b/dev/bots/prepare_package.dart
index 5f12782..0f1c055 100644
--- a/dev/bots/prepare_package.dart
+++ b/dev/bots/prepare_package.dart
@@ -29,7 +29,7 @@
 
   final String message;
   final ProcessResult result;
-  int get exitCode => result.exitCode ?? -1;
+  int get exitCode => result?.exitCode ?? -1;
 
   @override
   String toString() {
@@ -39,7 +39,7 @@
     }
     final String stderr = result?.stderr ?? '';
     if (stderr.isNotEmpty) {
-      output += ':\n${result.stderr}';
+      output += ':\n$stderr';
     }
     return output;
   }
@@ -157,6 +157,10 @@
       final String message = 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} '
           'failed with:\n${e.toString()}';
       throw new ProcessRunnerException(message);
+    } on ArgumentError catch (e) {
+      final String message = 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} '
+          'failed with:\n${e.toString()}';
+      throw new ProcessRunnerException(message);
     }
 
     final int exitCode = await allComplete();
@@ -441,7 +445,7 @@
     final String destGsPath = '$gsReleaseFolder/$destinationArchivePath';
     await _cloudCopy(outputFile.absolute.path, destGsPath);
     assert(tempDir.existsSync());
-    return _updateMetadata();
+    await _updateMetadata();
   }
 
   Future<Null> _updateMetadata() async {
@@ -632,6 +636,9 @@
   } on ProcessRunnerException catch (e) {
     exitCode = e.exitCode;
     message = e.message;
+  } catch (e) {
+    exitCode = -1;
+    message = e.toString();
   } finally {
     if (removeTempDir) {
       tempDir.deleteSync(recursive: true);
diff --git a/dev/bots/test/prepare_package_test.dart b/dev/bots/test/prepare_package_test.dart
index f9c18fe..c460e7b 100644
--- a/dev/bots/test/prepare_package_test.dart
+++ b/dev/bots/test/prepare_package_test.dart
@@ -17,6 +17,24 @@
 
 void main() {
   final String testRef = 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeef';
+  test('Throws on missing executable', () async {
+    // Uses a *real* process manager, since we want to know what happens if
+    // it can't find an executable.
+    final ProcessRunner processRunner = new ProcessRunner(subprocessOutput: false);
+    expect(
+        expectAsync1((List<String> commandLine) async {
+          return processRunner.runProcess(commandLine);
+        })(<String>['this_executable_better_not_exist_2857632534321']),
+        throwsA(const isInstanceOf<ProcessRunnerException>()));
+    try {
+      await processRunner.runProcess(<String>['this_executable_better_not_exist_2857632534321']);
+    } on ProcessRunnerException catch (e) {
+      expect(
+        e.message,
+        contains('Invalid argument(s): Cannot find executable for this_executable_better_not_exist_2857632534321.'),
+      );
+    }
+  });
   for (String platformName in <String>['macos', 'linux', 'windows']) {
     final FakePlatform platform = new FakePlatform(
       operatingSystem: platformName,