From 0acf53e3108b2ab0739e2b5e458dc113b94f65a7 Mon Sep 17 00:00:00 2001
From: Ian Hickson <ian@hixie.ch>
Date: Fri, 4 Jun 2021 01:09:02 -0700
Subject: [PATCH] Run `dart fix` on customer tests as promised (#83944)

---
 dev/customer_testing/lib/runner.dart | 114 ++++++++++++++++++---------
 dev/customer_testing/run_tests.dart  |  28 +++++--
 2 files changed, 96 insertions(+), 46 deletions(-)

diff --git a/dev/customer_testing/lib/runner.dart b/dev/customer_testing/lib/runner.dart
index 79736a77bb..58e00ca26d 100644
--- a/dev/customer_testing/lib/runner.dart
+++ b/dev/customer_testing/lib/runner.dart
@@ -31,13 +31,21 @@ Future<bool> runTests({
 
   if (verbose) {
     final String s = files.length == 1 ? '' : 's';
-    final String ss = shardedFiles.length == 1 ? '' : 's';
-    print('${files.length} file$s specified. ${shardedFiles.length} test$ss in shard #$shardIndex.');
+    if (numberShards > 1) {
+      final String ss = shardedFiles.length == 1 ? '' : 's';
+      print('${files.length} file$s specified. ${shardedFiles.length} test$ss in shard #$shardIndex ($numberShards shards total).');
+    } else {
+      print('${files.length} file$s specified.');
+    }
     print('');
   }
 
   if (verbose) {
-    print('Tests in this shard:');
+    if (numberShards > 1) {
+      print('Tests in this shard:');
+    } else {
+      print('Tests:');
+    }
     for (final File file in shardedFiles)
       print(file.path);
   }
@@ -46,30 +54,40 @@ Future<bool> runTests({
   for (final File file in shardedFiles) {
     if (verbose)
       print('Processing ${file.path}...');
+
+    void printHeader() {
+      if (!verbose)
+        print('Processing ${file.path}...');
+    }
+
+    void failure(String message) {
+      printHeader();
+      print('ERROR: $message');
+      failures += 1;
+    }
+
     CustomerTest instructions;
     try {
       instructions = CustomerTest(file);
     } on FormatException catch (error) {
-      print('ERROR: ${error.message}');
+      failure(error.message);
       print('');
-      failures += 1;
       continue;
     } on FileSystemException catch (error) {
-      print('ERROR: ${error.message}');
-      print('  ${file.path}');
+      failure(error.message);
       print('');
-      failures += 1;
       continue;
     }
 
+    bool success = true;
+
     final Directory checkout = Directory.systemTemp.createTempSync('flutter_customer_testing.${path.basenameWithoutExtension(file.path)}.');
     if (verbose)
       print('Created temporary directory: ${checkout.path}');
     try {
-      bool? success;
-      bool showContacts = false;
+      assert(instructions.fetch.isNotEmpty);
       for (final String fetchCommand in instructions.fetch) {
-        success = await shell(fetchCommand, checkout, verbose: verbose, silentFailure: skipOnFetchFailure);
+        success = await shell(fetchCommand, checkout, verbose: verbose, silentFailure: skipOnFetchFailure, failedCallback: printHeader);
         if (!success) {
           if (skipOnFetchFailure) {
             if (verbose) {
@@ -78,39 +96,51 @@ Future<bool> runTests({
               print('Skipping ${file.path} (fetch failed).');
             }
           } else {
-            print('ERROR: Failed to fetch repository.');
-            failures += 1;
-            showContacts = true;
+            failure('Failed to fetch repository.');
           }
           break;
         }
       }
-      assert(success != null);
-      if (success == true) {
-        if (verbose)
-          print('Running tests...');
-        final Directory tests = Directory(path.join(checkout.path, 'tests'));
-        // TODO(ianh): Once we have a way to update source code, run that command in each directory of instructions.update
-        for (int iteration = 0; iteration < repeat; iteration += 1) {
-          if (verbose && repeat > 1)
-            print('Round ${iteration + 1} of $repeat.');
-          for (final String testCommand in instructions.tests) {
-            testCount += 1;
-            success = await shell(testCommand, tests, verbose: verbose);
-            if (!success) {
-              print('ERROR: One or more tests from ${path.basenameWithoutExtension(file.path)} failed.');
-              failures += 1;
-              showContacts = true;
-              break;
+      if (success) {
+        final Directory customerRepo = Directory(path.join(checkout.path, 'tests'));
+        for (final Directory updateDirectory in instructions.update) {
+          final Directory resolvedUpdateDirectory = Directory(path.join(customerRepo.path, updateDirectory.path));
+          if (verbose)
+            print('Updating code in ${resolvedUpdateDirectory.path}...');
+          if (!File(path.join(resolvedUpdateDirectory.path, 'pubspec.yaml')).existsSync()) {
+            failure('The directory ${updateDirectory.path}, which was specified as an update directory, does not contain a "pubspec.yaml" file.');
+            success = false;
+            break;
+          }
+          success = await shell('flutter packages get', resolvedUpdateDirectory, verbose: verbose, failedCallback: printHeader);
+          if (!success) {
+            failure('Could not run "flutter pub get" in ${updateDirectory.path}, which was specified as an update directory.');
+            break;
+          }
+          success = await shell('dart fix --apply', resolvedUpdateDirectory, verbose: verbose, failedCallback: printHeader);
+          if (!success) {
+            failure('Could not run "dart fix" in ${updateDirectory.path}, which was specified as an update directory.');
+            break;
+          }
+        }
+        if (success) {
+          if (verbose)
+            print('Running tests...');
+          for (int iteration = 0; iteration < repeat; iteration += 1) {
+            if (verbose && repeat > 1)
+              print('Round ${iteration + 1} of $repeat.');
+            for (final String testCommand in instructions.tests) {
+              testCount += 1;
+              success = await shell(testCommand, customerRepo, verbose: verbose, failedCallback: printHeader);
+              if (!success) {
+                failure('One or more tests from ${path.basenameWithoutExtension(file.path)} failed.');
+                break;
+              }
             }
           }
+          if (verbose && success)
+            print('Tests finished.');
         }
-        if (verbose && success == true)
-          print('Tests finished.');
-      }
-      if (showContacts) {
-        final String s = instructions.contacts.length == 1 ? '' : 's';
-        print('Contact$s: ${instructions.contacts.join(", ")}');
       }
     } finally {
       if (verbose)
@@ -121,7 +151,11 @@ Future<bool> runTests({
         print('Failed to delete "${checkout.path}".');
       }
     }
-    if (verbose)
+    if (!success) {
+      final String s = instructions.contacts.length == 1 ? '' : 's';
+      print('Contact$s: ${instructions.contacts.join(", ")}');
+    }
+    if (verbose || !success)
       print('');
   }
   if (failures > 0) {
@@ -135,7 +169,7 @@ Future<bool> runTests({
 
 final RegExp _spaces = RegExp(r' +');
 
-Future<bool> shell(String command, Directory directory, { bool verbose = false, bool silentFailure = false }) async {
+Future<bool> shell(String command, Directory directory, { bool verbose = false, bool silentFailure = false, void Function()? failedCallback }) async {
   if (verbose)
     print('>> $command');
   Process process;
@@ -152,6 +186,8 @@ Future<bool> shell(String command, Directory directory, { bool verbose = false,
   if (success || silentFailure)
     return success;
   if (!verbose) {
+    if (failedCallback != null)
+      failedCallback();
     print('>> $command');
     output.forEach(printLog);
   }
diff --git a/dev/customer_testing/run_tests.dart b/dev/customer_testing/run_tests.dart
index dbc2d63c6d..c792722a8b 100644
--- a/dev/customer_testing/run_tests.dart
+++ b/dev/customer_testing/run_tests.dart
@@ -15,6 +15,7 @@ Future<void> main(List<String> arguments) async {
   exit(await run(arguments) ? 0 : 1);
 }
 
+// Return true if successful, false if failed.
 Future<bool> run(List<String> arguments) async {
   final ArgParser argParser = ArgParser(
     allowTrailingOptions: false,
@@ -92,11 +93,21 @@ Future<bool> run(List<String> arguments) async {
     .where((File file) => !skipTemplate || path.basename(file.path) != 'template.test')
     .toList();
 
-  if (help || repeat == null || files.isEmpty) {
+  if (help || repeat == null || files.isEmpty || numberShards == null || numberShards <= 0 || shardIndex == null || shardIndex < 0) {
     printHelp();
     if (verbose) {
       if (repeat == null)
         print('Error: Could not parse repeat count ("${parsedArguments['repeat']}")');
+      if (numberShards == null) {
+        print('Error: Could not parse shards count ("${parsedArguments['shards']}")');
+      } else if (numberShards < 1) {
+        print('Error: The specified shards count ($numberShards) is less than 1. It must be greater than zero.');
+      }
+      if (shardIndex == null) {
+        print('Error: Could not parse shard index ("${parsedArguments['shard-index']}")');
+      } else if (shardIndex < 0) {
+        print('Error: The specified shard index ($shardIndex) is negative. It must be in the range [0 .. shards - 1].');
+      }
       if (parsedArguments.rest.isEmpty) {
         print('Error: No file arguments specified.');
       } else if (files.isEmpty) {
@@ -106,14 +117,17 @@ Future<bool> run(List<String> arguments) async {
     return help;
   }
 
-  if (files.length < shardIndex!)
-    print('Warning: There are more shards than tests. Some shards will not run any tests.');
-
-  if (numberShards! <= shardIndex) {
-    print('Error: There are more shard indexes than shards.');
-    return help;
+  if (shardIndex > numberShards - 1) {
+    print(
+      'Error: The specified shard index ($shardIndex) is more than the specified number of shards ($numberShards). '
+      'It must be in the range [0 .. shards - 1].'
+    );
+    return false;
   }
 
+  if (files.length < numberShards)
+    print('Warning: There are more shards than tests. Some shards will not run any tests.');
+
   return runTests(
     repeat: repeat,
     skipOnFetchFailure: skipOnFetchFailure,
-- 
2.21.0