1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:io';
6
7import 'package:args/args.dart';
8import 'package:file/local.dart';
9import 'package:glob/glob.dart';
10import 'package:path/path.dart' as path;
11
12import 'lib/runner.dart';
13
14Future<void> main(List<String> arguments) async {
15 exit(await run(arguments) ? 0 : 1);
16}
17
18// Return true if successful, false if failed.
19Future<bool> run(List<String> arguments) async {
20 final ArgParser argParser = ArgParser(allowTrailingOptions: false, usageLineLength: 72)
21 ..addOption(
22 'repeat',
23 defaultsTo: '1',
24 help:
25 'How many times to run each test. Set to a high value to look for flakes. If a test specifies a number of iterations, the lower of the two values is used.',
26 valueHelp: 'count',
27 )
28 ..addOption(
29 'shards',
30 defaultsTo: '1',
31 help: 'How many shards to split the tests into. Used in continuous integration.',
32 valueHelp: 'count',
33 )
34 ..addOption(
35 'shard-index',
36 defaultsTo: '0',
37 help:
38 'The current shard to run the tests with the range [0 .. shards - 1]. Used in continuous integration.',
39 valueHelp: 'count',
40 )
41 ..addFlag('skip-on-fetch-failure', help: 'Whether to skip tests that we fail to download.')
42 ..addFlag('skip-template', help: 'Whether to skip tests named "template.test".')
43 ..addFlag('verbose', help: 'Describe what is happening in detail.')
44 ..addFlag('help', negatable: false, help: 'Print this help message.');
45
46 void printHelp() {
47 print('run_tests.dart [options...] path/to/file1.test path/to/file2.test...');
48 print('For details on the test registry format, see:');
49 print(' https://github.com/flutter/tests/blob/main/registry/template.test');
50 print('');
51 print(argParser.usage);
52 print('');
53 }
54
55 ArgResults parsedArguments;
56 try {
57 parsedArguments = argParser.parse(arguments);
58 } on ArgParserException catch (error) {
59 printHelp();
60 print('Error: ${error.message} Use --help for usage information.');
61 exit(1);
62 }
63
64 final int? repeat = int.tryParse(parsedArguments['repeat'] as String);
65 final bool skipOnFetchFailure = parsedArguments['skip-on-fetch-failure'] as bool;
66 final bool skipTemplate = parsedArguments['skip-template'] as bool;
67 final bool verbose = parsedArguments['verbose'] as bool;
68 final bool help = parsedArguments['help'] as bool;
69 final int? numberShards = int.tryParse(parsedArguments['shards'] as String);
70 final int? shardIndex = int.tryParse(parsedArguments['shard-index'] as String);
71 final List<File> files = parsedArguments.rest
72 .expand((String path) => Glob(path).listFileSystemSync(const LocalFileSystem()))
73 .whereType<File>()
74 .where((File file) => !skipTemplate || path.basename(file.path) != 'template.test')
75 .toList();
76
77 if (files.isEmpty && parsedArguments.rest.isNotEmpty) {
78 print('No files resolved from glob(s): ${parsedArguments.rest}');
79 }
80
81 if (help ||
82 repeat == null ||
83 files.isEmpty ||
84 numberShards == null ||
85 numberShards <= 0 ||
86 shardIndex == null ||
87 shardIndex < 0) {
88 printHelp();
89 if (verbose) {
90 if (repeat == null) {
91 print('Error: Could not parse repeat count ("${parsedArguments['repeat']}")');
92 }
93 if (numberShards == null) {
94 print('Error: Could not parse shards count ("${parsedArguments['shards']}")');
95 } else if (numberShards < 1) {
96 print(
97 'Error: The specified shards count ($numberShards) is less than 1. It must be greater than zero.',
98 );
99 }
100 if (shardIndex == null) {
101 print('Error: Could not parse shard index ("${parsedArguments['shard-index']}")');
102 } else if (shardIndex < 0) {
103 print(
104 'Error: The specified shard index ($shardIndex) is negative. It must be in the range [0 .. shards - 1].',
105 );
106 }
107 if (parsedArguments.rest.isEmpty) {
108 print('Error: No file arguments specified.');
109 } else if (files.isEmpty) {
110 print(
111 'Error: File arguments ("${parsedArguments.rest.join('", "')}") did not identify any real files.',
112 );
113 }
114 }
115 return help;
116 }
117
118 if (shardIndex > numberShards - 1) {
119 print(
120 'Error: The specified shard index ($shardIndex) is more than the specified number of shards ($numberShards). '
121 'It must be in the range [0 .. shards - 1].',
122 );
123 return false;
124 }
125
126 if (files.length < numberShards) {
127 print('Warning: There are more shards than tests. Some shards will not run any tests.');
128 }
129
130 return runTests(
131 repeat: repeat,
132 skipOnFetchFailure: skipOnFetchFailure,
133 verbose: verbose,
134 numberShards: numberShards,
135 shardIndex: shardIndex,
136 files: files,
137 );
138}
139