|  | // Copyright 2014 The Flutter 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:path/path.dart' as path; | 
|  |  | 
|  | import 'customer_test.dart'; | 
|  |  | 
|  | Future<bool> runTests({ | 
|  | int repeat = 1, | 
|  | bool skipOnFetchFailure = false, | 
|  | bool verbose = false, | 
|  | int numberShards = 1, | 
|  | int shardIndex = 0, | 
|  | required List<File> files, | 
|  | }) async { | 
|  | if (verbose) | 
|  | print('Starting run_tests.dart...'); | 
|  |  | 
|  | // Best attempt at evenly splitting tests among the shards | 
|  | final List<File> shardedFiles = <File>[]; | 
|  | for (int i = shardIndex; i < files.length; i += numberShards) { | 
|  | shardedFiles.add(files[i]); | 
|  | } | 
|  |  | 
|  | int testCount = 0; | 
|  | int failures = 0; | 
|  |  | 
|  | if (verbose) { | 
|  | final String s = files.length == 1 ? '' : 's'; | 
|  | 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) { | 
|  | if (numberShards > 1) { | 
|  | print('Tests in this shard:'); | 
|  | } else { | 
|  | print('Tests:'); | 
|  | } | 
|  | for (final File file in shardedFiles) | 
|  | print(file.path); | 
|  | } | 
|  | print(''); | 
|  |  | 
|  | 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) { | 
|  | failure(error.message); | 
|  | print(''); | 
|  | continue; | 
|  | } on FileSystemException catch (error) { | 
|  | failure(error.message); | 
|  | print(''); | 
|  | 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 { | 
|  | assert(instructions.fetch.isNotEmpty); | 
|  | for (final String fetchCommand in instructions.fetch) { | 
|  | success = await shell(fetchCommand, checkout, verbose: verbose, silentFailure: skipOnFetchFailure, failedCallback: printHeader); | 
|  | if (!success) { | 
|  | if (skipOnFetchFailure) { | 
|  | if (verbose) { | 
|  | print('Skipping (fetch failed).'); | 
|  | } else { | 
|  | print('Skipping ${file.path} (fetch failed).'); | 
|  | } | 
|  | } else { | 
|  | failure('Failed to fetch repository.'); | 
|  | } | 
|  | 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.'); | 
|  | } | 
|  | } | 
|  | } finally { | 
|  | if (verbose) | 
|  | print('Deleting temporary directory...'); | 
|  | try { | 
|  | checkout.deleteSync(recursive: true); | 
|  | } on FileSystemException { | 
|  | print('Failed to delete "${checkout.path}".'); | 
|  | } | 
|  | } | 
|  | if (!success) { | 
|  | final String s = instructions.contacts.length == 1 ? '' : 's'; | 
|  | print('Contact$s: ${instructions.contacts.join(", ")}'); | 
|  | } | 
|  | if (verbose || !success) | 
|  | print(''); | 
|  | } | 
|  | if (failures > 0) { | 
|  | final String s = failures == 1 ? '' : 's'; | 
|  | print('$failures failure$s.'); | 
|  | return false; | 
|  | } | 
|  | print('$testCount tests all passed!'); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | final RegExp _spaces = RegExp(r' +'); | 
|  |  | 
|  | Future<bool> shell(String command, Directory directory, { bool verbose = false, bool silentFailure = false, void Function()? failedCallback }) async { | 
|  | if (verbose) | 
|  | print('>> $command'); | 
|  | Process process; | 
|  | if (Platform.isWindows) { | 
|  | process = await Process.start('CMD.EXE', <String>['/S', '/C', command], workingDirectory: directory.path); | 
|  | } else { | 
|  | final List<String> segments = command.trim().split(_spaces); | 
|  | process = await Process.start(segments.first, segments.skip(1).toList(), workingDirectory: directory.path); | 
|  | } | 
|  | final List<String> output = <String>[]; | 
|  | utf8.decoder.bind(process.stdout).transform(const LineSplitter()).listen(verbose ? printLog : output.add); | 
|  | utf8.decoder.bind(process.stderr).transform(const LineSplitter()).listen(verbose ? printLog : output.add); | 
|  | final bool success = await process.exitCode == 0; | 
|  | if (success || silentFailure) | 
|  | return success; | 
|  | if (!verbose) { | 
|  | if (failedCallback != null) | 
|  | failedCallback(); | 
|  | print('>> $command'); | 
|  | output.forEach(printLog); | 
|  | } | 
|  | return success; | 
|  | } | 
|  |  | 
|  | void printLog(String line) { | 
|  | print('| $line'.trimRight()); | 
|  | } |