blob: 93ac2038e365b75bd3f5f264b332d9e9ff3536d7 [file] [log] [blame]
Ian Hickson449f4a62019-11-27 15:04:02 -08001// Copyright 2014 The Flutter Authors. All rights reserved.
Ian Hickson9ac16682017-06-12 16:52:35 -07002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Michael Goderbauer76964622017-02-14 10:21:33 -08005import 'dart:async';
Jonah Williams2a5690f2020-05-01 15:34:28 -07006import 'dart:convert';
Alexandre Ardhuin8c043d02017-02-23 22:37:26 +01007import 'dart:io';
Ian Hickson124dc662019-10-18 16:35:39 -07008import 'dart:math' as math;
Michael Goderbauer76964622017-02-14 10:21:33 -08009
Amir Hardon2ceb3712020-11-24 17:18:06 -080010import 'package:file/file.dart' as fs;
11import 'package:file/local.dart';
Dan Field20e0f132019-03-06 13:13:45 -080012import 'package:googleapis/bigquery/v2.dart' as bq;
13import 'package:googleapis_auth/auth_io.dart' as auth;
14import 'package:http/http.dart' as http;
Jonah Williams0b3f5cf2020-04-21 20:39:36 -070015import 'package:meta/meta.dart';
Ian Hicksonaaa0a1c2017-04-13 10:22:41 -070016import 'package:path/path.dart' as path;
Alexander Aprelev391e91c2018-08-30 07:30:25 -070017
Yegorb3404692020-02-13 18:34:08 -080018import 'browser.dart';
Dan Field20e0f132019-03-06 13:13:45 -080019import 'flutter_compact_formatter.dart';
Alexander Aprelev391e91c2018-08-30 07:30:25 -070020import 'run_command.dart';
Dan Field24f39d42020-01-02 11:47:28 -080021import 'utils.dart';
Michael Goderbauer76964622017-02-14 10:21:33 -080022
Alexandre Ardhuind340e2f2018-10-04 18:44:23 +020023typedef ShardRunner = Future<void> Function();
Todd Volkertbd679262017-06-15 17:54:45 -070024
James Linc02b8052019-08-09 15:10:45 -070025/// A function used to validate the output of a test.
26///
27/// If the output matches expectations, the function shall return null.
28///
29/// If the output does not match expectations, the function shall return an
30/// appropriate error message.
Yegorff9082c2020-10-19 20:56:25 -070031typedef OutputChecker = String Function(CommandResult);
James Linc02b8052019-08-09 15:10:45 -070032
Dan Field22239f42020-05-04 11:19:02 -070033final String exe = Platform.isWindows ? '.exe' : '';
34final String bat = Platform.isWindows ? '.bat' : '';
Ian Hicksonaaa0a1c2017-04-13 10:22:41 -070035final String flutterRoot = path.dirname(path.dirname(path.dirname(path.fromUri(Platform.script))));
Dan Field22239f42020-05-04 11:19:02 -070036final String flutter = path.join(flutterRoot, 'bin', 'flutter$bat');
37final String dart = path.join(flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', 'dart$exe');
38final String pub = path.join(flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', 'pub$bat');
Greg Spencerf29ecba2017-12-05 14:46:39 -080039final String pubCache = path.join(flutterRoot, '.pub-cache');
Jonah Williams33ad5ba2019-06-26 16:02:49 -070040final String toolRoot = path.join(flutterRoot, 'packages', 'flutter_tools');
Dan Field22239f42020-05-04 11:19:02 -070041final String engineVersionFile = path.join(flutterRoot, 'bin', 'internal', 'engine.version');
Amir Hardon2ceb3712020-11-24 17:18:06 -080042final String flutterPluginsVersionFile = path.join(flutterRoot, 'bin', 'internal', 'flutter_plugins.version');
Dan Field22239f42020-05-04 11:19:02 -070043
44String get platformFolderName {
45 if (Platform.isWindows)
46 return 'windows-x64';
47 if (Platform.isMacOS)
48 return 'darwin-x64';
49 if (Platform.isLinux)
50 return 'linux-x64';
51 throw UnsupportedError('The platform ${Platform.operatingSystem} is not supported by this script.');
52}
53final String flutterTester = path.join(flutterRoot, 'bin', 'cache', 'artifacts', 'engine', platformFolderName, 'flutter_tester$exe');
Ian Hickson124dc662019-10-18 16:35:39 -070054
55/// The arguments to pass to `flutter test` (typically the local engine
56/// configuration) -- prefilled with the arguments passed to test.dart.
Alexander Thomas425bd5a2017-12-09 00:49:29 +010057final List<String> flutterTestArgs = <String>[];
Michael Goderbauer76964622017-02-14 10:21:33 -080058
Dan Field97a81202019-03-14 08:26:59 -070059final bool useFlutterTestFormatter = Platform.environment['FLUTTER_TEST_FORMATTER'] == 'true';
Jonah Williams7f235ea2019-12-05 23:16:25 -080060
Ian Hickson124dc662019-10-18 16:35:39 -070061
Jenn Magderfab03bd2020-05-11 12:49:18 -070062/// The number of Cirrus jobs that run build tests in parallel.
63///
64/// WARNING: if you change this number, also change .cirrus.yml
65/// and make sure it runs _all_ shards.
66const int kBuildTestShardCount = 2;
67
Ian Hickson124dc662019-10-18 16:35:39 -070068/// The number of Cirrus jobs that run Web tests in parallel.
69///
Yegor14cceef2020-04-13 16:05:02 -070070/// The default is 8 shards. Typically .cirrus.yml would define the
71/// WEB_SHARD_COUNT environment variable rather than relying on the default.
72///
Ian Hickson124dc662019-10-18 16:35:39 -070073/// WARNING: if you change this number, also change .cirrus.yml
74/// and make sure it runs _all_ shards.
75///
76/// The last shard also runs the Web plugin tests.
Yegor14cceef2020-04-13 16:05:02 -070077int get webShardCount => Platform.environment.containsKey('WEB_SHARD_COUNT')
78 ? int.parse(Platform.environment['WEB_SHARD_COUNT'])
79 : 8;
Ian Hickson124dc662019-10-18 16:35:39 -070080
Yegor2fa03432020-10-29 14:23:02 -070081/// The number of shards the long-running Web tests are split into.
82///
83/// WARNING: this number must match the shard count in LUCI configs.
84const int kWebLongRunningTestShardCount = 3;
85
Jonah Williamse5000f62020-12-01 09:54:01 -080086/// Tests that we don't run on Web for compilation reasons.
Ian Hickson124dc662019-10-18 16:35:39 -070087//
Michael Goderbauer584fd5f2020-06-16 09:15:43 -070088// TODO(yjbanov): we're getting rid of this as part of https://github.com/flutter/flutter/projects/60
89const List<String> kWebTestFileKnownFailures = <String>[
Jonah Williamscfb63352020-11-18 14:55:26 -080090 'test/services/message_codecs_vm_test.dart',
Jonah Williamse5000f62020-12-01 09:54:01 -080091 'test/examples/sector_layout_test.dart',
Ian Hickson124dc662019-10-18 16:35:39 -070092];
Todd Volkertbd679262017-06-15 17:54:45 -070093
Alexander Thomas425bd5a2017-12-09 00:49:29 +010094/// When you call this, you can pass additional arguments to pass custom
Michael Goderbauer76964622017-02-14 10:21:33 -080095/// arguments to flutter test. For example, you might want to call this
Alexander Thomas425bd5a2017-12-09 00:49:29 +010096/// script with the parameter --local-engine=host_debug_unopt to
Michael Goderbauer76964622017-02-14 10:21:33 -080097/// use your own build of the engine.
Ian Hickson11c20322017-03-20 16:16:34 -070098///
Alexander Aprelev391e91c2018-08-30 07:30:25 -070099/// To run the tool_tests part, run it with SHARD=tool_tests
Ian Hickson11c20322017-03-20 16:16:34 -0700100///
Ian Hickson124dc662019-10-18 16:35:39 -0700101/// Examples:
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700102/// SHARD=tool_tests bin/cache/dart-sdk/bin/dart dev/bots/test.dart
Alexander Thomas425bd5a2017-12-09 00:49:29 +0100103/// bin/cache/dart-sdk/bin/dart dev/bots/test.dart --local-engine=host_debug_unopt
Alexandre Ardhuind340e2f2018-10-04 18:44:23 +0200104Future<void> main(List<String> args) async {
Dan Field24f39d42020-01-02 11:47:28 -0800105 print('$clock STARTING ANALYSIS');
106 try {
107 flutterTestArgs.addAll(args);
108 if (Platform.environment.containsKey(CIRRUS_TASK_NAME))
109 print('Running task: ${Platform.environment[CIRRUS_TASK_NAME]}');
110 print('═' * 80);
111 await _runSmokeTests();
112 print('═' * 80);
113 await selectShard(const <String, ShardRunner>{
chunhtai4964fb62020-01-27 10:38:02 -0800114 'add_to_app_life_cycle_tests': _runAddToAppLifeCycleTests,
Dan Field24f39d42020-01-02 11:47:28 -0800115 'build_tests': _runBuildTests,
116 'framework_coverage': _runFrameworkCoverage,
117 'framework_tests': _runFrameworkTests,
Dan Field24f39d42020-01-02 11:47:28 -0800118 'tool_coverage': _runToolCoverage,
119 'tool_tests': _runToolTests,
Anna Gringauzeedf26e72020-11-11 15:42:15 -0800120 'web_tool_tests': _runWebToolTests,
Yegorb3404692020-02-13 18:34:08 -0800121 'web_tests': _runWebUnitTests,
122 'web_integration_tests': _runWebIntegrationTests,
Yegor2fa03432020-10-29 14:23:02 -0700123 'web_long_running_tests': _runWebLongRunningTests,
Amir Hardon2b4a2352020-11-23 12:28:28 -0800124 'flutter_plugins': _runFlutterPluginsTests,
Dan Field24f39d42020-01-02 11:47:28 -0800125 });
126 } on ExitException catch (error) {
127 error.apply();
128 }
129 print('$clock ${bold}Test successful.$reset');
Todd Volkertbd679262017-06-15 17:54:45 -0700130}
Michael Goderbauer76964622017-02-14 10:21:33 -0800131
stuartmorganc23319e2020-10-29 09:27:59 -0700132/// Returns whether or not Linux desktop tests should be run.
133///
134/// The branch restrictions here should stay in sync with features.dart.
135bool _shouldRunLinux() {
136 return Platform.isLinux && (branchName != 'beta' && branchName != 'stable');
137}
138
stuartmorgan4ea6d3d2020-10-22 13:52:19 -0700139/// Returns whether or not macOS desktop tests should be run.
140///
141/// The branch restrictions here should stay in sync with features.dart.
142bool _shouldRunMacOS() {
143 return Platform.isMacOS && (branchName != 'beta' && branchName != 'stable');
144}
145
stuartmorgan22c502e2020-10-22 15:14:30 -0700146/// Returns whether or not Windows desktop tests should be run.
147///
148/// The branch restrictions here should stay in sync with features.dart.
149bool _shouldRunWindows() {
150 return Platform.isWindows && (branchName != 'beta' && branchName != 'stable');
151}
152
Dan Field22239f42020-05-04 11:19:02 -0700153/// Verify the Flutter Engine is the revision in
154/// bin/cache/internal/engine.version.
155Future<void> _validateEngineHash() async {
156 final String luciBotId = Platform.environment['SWARMING_BOT_ID'] ?? '';
157 if (luciBotId.startsWith('luci-dart-')) {
158 // The Dart HHH bots intentionally modify the local artifact cache
159 // and then use this script to run Flutter's test suites.
160 // Because the artifacts have been changed, this particular test will return
161 // a false positive and should be skipped.
162 print('${yellow}Skipping Flutter Engine Version Validation for swarming '
163 'bot $luciBotId.');
164 return;
165 }
166 final String expectedVersion = File(engineVersionFile).readAsStringSync().trim();
Yegorff9082c2020-10-19 20:56:25 -0700167 final CommandResult result = await runCommand(flutterTester, <String>['--help'], outputMode: OutputMode.capture);
168 final String actualVersion = result.flattenedStderr.split('\n').firstWhere((final String line) {
Dan Field22239f42020-05-04 11:19:02 -0700169 return line.startsWith('Flutter Engine Version:');
170 });
171 if (!actualVersion.contains(expectedVersion)) {
172 print('${red}Expected "Flutter Engine Version: $expectedVersion", '
173 'but found "$actualVersion".');
174 exit(1);
175 }
176}
177
Alexandre Ardhuind340e2f2018-10-04 18:44:23 +0200178Future<void> _runSmokeTests() async {
Ian Hickson124dc662019-10-18 16:35:39 -0700179 print('${green}Running smoketests...$reset');
Dan Field22239f42020-05-04 11:19:02 -0700180
181 await _validateEngineHash();
182
Ian Hicksondac2ebf2018-06-11 16:28:01 -0700183 // Verify that the tests actually return failure on failure and success on
184 // success.
Todd Volkertbd679262017-06-15 17:54:45 -0700185 final String automatedTests = path.join(flutterRoot, 'dev', 'automated_tests');
Ian Hicksondac2ebf2018-06-11 16:28:01 -0700186 // We run the "pass" and "fail" smoke tests first, and alone, because those
187 // are particularly critical and sensitive. If one of these fails, there's no
188 // point even trying the others.
Todd Volkertbd679262017-06-15 17:54:45 -0700189 await _runFlutterTest(automatedTests,
190 script: path.join('test_smoke_test', 'pass_test.dart'),
191 printOutput: false,
192 );
193 await _runFlutterTest(automatedTests,
Ian Hicksondac2ebf2018-06-11 16:28:01 -0700194 script: path.join('test_smoke_test', 'fail_test.dart'),
Todd Volkertbd679262017-06-15 17:54:45 -0700195 expectFailure: true,
196 printOutput: false,
197 );
Ian Hicksondac2ebf2018-06-11 16:28:01 -0700198 // We run the timeout tests individually because they are timing-sensitive.
199 await _runFlutterTest(automatedTests,
200 script: path.join('test_smoke_test', 'timeout_pass_test.dart'),
201 expectFailure: false,
202 printOutput: false,
Ian Hicksondac2ebf2018-06-11 16:28:01 -0700203 );
Todd Volkertbd679262017-06-15 17:54:45 -0700204 await _runFlutterTest(automatedTests,
Ian Hicksondac2ebf2018-06-11 16:28:01 -0700205 script: path.join('test_smoke_test', 'timeout_fail_test.dart'),
Todd Volkertbd679262017-06-15 17:54:45 -0700206 expectFailure: true,
207 printOutput: false,
208 );
James D. Linb2f8d3a2019-08-20 10:28:49 -0700209 await _runFlutterTest(automatedTests,
210 script: path.join('test_smoke_test', 'pending_timer_fail_test.dart'),
211 expectFailure: true,
212 printOutput: false,
Yegorff9082c2020-10-19 20:56:25 -0700213 outputChecker: (CommandResult result) {
214 return result.flattenedStdout.contains('failingPendingTimerTest')
Ian Hickson124dc662019-10-18 16:35:39 -0700215 ? null
216 : 'Failed to find the stack trace for the pending Timer.';
217 }
James D. Linb2f8d3a2019-08-20 10:28:49 -0700218 );
Ian Hicksondac2ebf2018-06-11 16:28:01 -0700219 // We run the remaining smoketests in parallel, because they each take some
220 // time to run (e.g. compiling), so we don't want to run them in series,
221 // especially on 20-core machines...
222 await Future.wait<void>(
223 <Future<void>>[
224 _runFlutterTest(automatedTests,
225 script: path.join('test_smoke_test', 'crash1_test.dart'),
226 expectFailure: true,
227 printOutput: false,
Ian Hicksondac2ebf2018-06-11 16:28:01 -0700228 ),
229 _runFlutterTest(automatedTests,
230 script: path.join('test_smoke_test', 'crash2_test.dart'),
231 expectFailure: true,
232 printOutput: false,
Ian Hicksondac2ebf2018-06-11 16:28:01 -0700233 ),
234 _runFlutterTest(automatedTests,
235 script: path.join('test_smoke_test', 'syntax_error_test.broken_dart'),
236 expectFailure: true,
237 printOutput: false,
Ian Hicksondac2ebf2018-06-11 16:28:01 -0700238 ),
239 _runFlutterTest(automatedTests,
240 script: path.join('test_smoke_test', 'missing_import_test.broken_dart'),
241 expectFailure: true,
242 printOutput: false,
Ian Hicksondac2ebf2018-06-11 16:28:01 -0700243 ),
244 _runFlutterTest(automatedTests,
245 script: path.join('test_smoke_test', 'disallow_error_reporter_modification_test.dart'),
246 expectFailure: true,
247 printOutput: false,
Ian Hicksondac2ebf2018-06-11 16:28:01 -0700248 ),
Ian Hicksondac2ebf2018-06-11 16:28:01 -0700249 ],
Todd Volkertbd679262017-06-15 17:54:45 -0700250 );
Michael Goderbauer76964622017-02-14 10:21:33 -0800251
Ian Hickson64e2e002018-01-26 16:59:56 -0800252 // Verify that we correctly generated the version file.
Ian Hickson124dc662019-10-18 16:35:39 -0700253 final String versionError = await verifyVersion(File(path.join(flutterRoot, 'version')));
Dan Field24f39d42020-01-02 11:47:28 -0800254 if (versionError != null)
255 exitWithError(<String>[versionError]);
Greg Spencer90a5f462018-07-20 10:21:34 -0700256}
Ian Hickson64e2e002018-01-26 16:59:56 -0800257
Dan Field20e0f132019-03-06 13:13:45 -0800258Future<bq.BigqueryApi> _getBigqueryApi() async {
Dan Field97a81202019-03-14 08:26:59 -0700259 if (!useFlutterTestFormatter) {
260 return null;
261 }
Dan Field20e0f132019-03-06 13:13:45 -0800262 // TODO(dnfield): How will we do this on LUCI?
263 final String privateKey = Platform.environment['GCLOUD_SERVICE_ACCOUNT_KEY'];
Dan Fieldb9f013c2019-03-10 11:26:17 -0700264 // If we're on Cirrus and a non-collaborator is doing this, we can't get the key.
265 if (privateKey == null || privateKey.isEmpty || privateKey.startsWith('ENCRYPTED[')) {
Dan Field20e0f132019-03-06 13:13:45 -0800266 return null;
267 }
Dan Fieldb9f013c2019-03-10 11:26:17 -0700268 try {
Dan Field97a81202019-03-14 08:26:59 -0700269 final auth.ServiceAccountCredentials accountCredentials = auth.ServiceAccountCredentials(
Dan Fieldb9f013c2019-03-10 11:26:17 -0700270 'flutter-ci-test-reporter@flutter-infra.iam.gserviceaccount.com',
271 auth.ClientId.serviceAccount('114390419920880060881.apps.googleusercontent.com'),
272 '-----BEGIN PRIVATE KEY-----\n$privateKey\n-----END PRIVATE KEY-----\n',
273 );
274 final List<String> scopes = <String>[bq.BigqueryApi.BigqueryInsertdataScope];
275 final http.Client client = await auth.clientViaServiceAccount(accountCredentials, scopes);
276 return bq.BigqueryApi(client);
277 } catch (e) {
Ian Hickson124dc662019-10-18 16:35:39 -0700278 print('${red}Failed to get BigQuery API client.$reset');
Dan Fieldb9f013c2019-03-10 11:26:17 -0700279 print(e);
280 return null;
281 }
Dan Field20e0f132019-03-06 13:13:45 -0800282}
283
Jonah Williams33ad5ba2019-06-26 16:02:49 -0700284Future<void> _runToolCoverage() async {
Jonah Williams124aa6f2020-03-04 17:12:58 -0800285 await _pubRunTest(
286 toolRoot,
287 testPaths: <String>[
288 path.join('test', 'general.shard'),
289 path.join('test', 'commands.shard', 'hermetic'),
290 ],
291 coverage: 'coverage',
Jonah Williams33ad5ba2019-06-26 16:02:49 -0700292 );
Jonah Williams124aa6f2020-03-04 17:12:58 -0800293 await runCommand(pub,
Jonah Williams94376392020-03-06 10:24:23 -0800294 <String>[
295 'run',
296 'coverage:format_coverage',
297 '--lcov',
298 '--in=coverage',
299 '--out=coverage/lcov.info',
300 '--packages=.packages',
301 '--report-on=lib/'
302 ],
Ian Hickson8c6d60b2019-10-02 12:46:51 -0700303 workingDirectory: toolRoot,
Yegorff9082c2020-10-19 20:56:25 -0700304 outputMode: OutputMode.capture,
Ian Hickson8c6d60b2019-10-02 12:46:51 -0700305 );
Jonah Williams33ad5ba2019-06-26 16:02:49 -0700306}
307
Alexandre Ardhuind340e2f2018-10-04 18:44:23 +0200308Future<void> _runToolTests() async {
Ian Hickson8c6d60b2019-10-02 12:46:51 -0700309 const String kDotShard = '.shard';
Anna Gringauzeedf26e72020-11-11 15:42:15 -0800310 const String kWeb = 'web';
Ian Hickson8c6d60b2019-10-02 12:46:51 -0700311 const String kTest = 'test';
312 final String toolsPath = path.join(flutterRoot, 'packages', 'flutter_tools');
Greg Spencer90a5f462018-07-20 10:21:34 -0700313
Ian Hickson8c6d60b2019-10-02 12:46:51 -0700314 final Map<String, ShardRunner> subshards = Map<String, ShardRunner>.fromIterable(
315 Directory(path.join(toolsPath, kTest))
316 .listSync()
317 .map<String>((FileSystemEntity entry) => entry.path)
Jonah Williams6e3ebc92020-11-05 23:58:40 -0800318 .where((String name) => name.endsWith(kDotShard))
Anna Gringauzeedf26e72020-11-11 15:42:15 -0800319 .where((String name) => path.basenameWithoutExtension(name) != kWeb)
Ian Hickson8c6d60b2019-10-02 12:46:51 -0700320 .map<String>((String name) => path.basenameWithoutExtension(name)),
321 // The `dynamic` on the next line is because Map.fromIterable isn't generic.
322 value: (dynamic subshard) => () async {
Jonah Williams7f235ea2019-12-05 23:16:25 -0800323 // Due to https://github.com/flutter/flutter/issues/46180, skip the hermetic directory
324 // on Windows.
325 final String suffix = Platform.isWindows && subshard == 'commands'
326 ? 'permeable'
327 : '';
Jonah Williamsa655a172020-10-22 13:17:31 -0700328 await _pubRunTest(
329 toolsPath,
330 forceSingleCore: subshard != 'general',
331 testPaths: <String>[path.join(kTest, '$subshard$kDotShard', suffix)],
332 enableFlutterToolAsserts: subshard != 'general',
333 );
Ian Hickson8c6d60b2019-10-02 12:46:51 -0700334 },
335 );
Anna Gringauzeedf26e72020-11-11 15:42:15 -0800336
337 await selectSubshard(subshards);
338}
339
340Future<void> _runWebToolTests() async {
341 const String kDotShard = '.shard';
342 const String kWeb = 'web';
343 const String kTest = 'test';
344 final String toolsPath = path.join(flutterRoot, 'packages', 'flutter_tools');
345
346 final Map<String, ShardRunner> subshards = <String, ShardRunner>{
347 kWeb:
348 () async {
349 await _pubRunTest(
350 toolsPath,
351 forceSingleCore: true,
352 testPaths: <String>[path.join(kTest, '$kWeb$kDotShard', '')],
353 enableFlutterToolAsserts: true,
354 );
355 }
356 };
Ian Hickson8c6d60b2019-10-02 12:46:51 -0700357
358 await selectSubshard(subshards);
Greg Spencer90a5f462018-07-20 10:21:34 -0700359}
360
Jonah Williams2a5690f2020-05-01 15:34:28 -0700361/// Verifies that APK, and IPA (if on macOS) builds the examples apps
Ian Hickson124dc662019-10-18 16:35:39 -0700362/// without crashing. It does not actually launch the apps. That happens later
363/// in the devicelab. This is just a smoke-test. In particular, this will verify
364/// we can build when there are spaces in the path name for the Flutter SDK and
Dan Field72926bd2018-11-29 09:32:11 -0800365/// target app.
366Future<void> _runBuildTests() async {
Jonah Williams0b3f5cf2020-04-21 20:39:36 -0700367 final List<FileSystemEntity> exampleDirectories = Directory(path.join(flutterRoot, 'examples')).listSync()
Jenn Magderbe1325f2020-12-09 13:07:36 -0800368 ..add(Directory(path.join(flutterRoot, 'packages', 'integration_test', 'example')))
Jonah Williamsb1d75fc2020-06-03 21:02:07 -0700369 ..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'non_nullable')))
Jonah Williams2a5690f2020-05-01 15:34:28 -0700370 ..add(Directory(path.join(flutterRoot, 'dev', 'integration_tests', 'flutter_gallery')));
Todd Volkertbae92c32019-11-24 22:29:28 -0800371
Jenn Magderfab03bd2020-05-11 12:49:18 -0700372 // The tests are randomly distributed into subshards so as to get a uniform
373 // distribution of costs, but the seed is fixed so that issues are reproducible.
374 final List<ShardRunner> tests = <ShardRunner>[
375 for (final FileSystemEntity exampleDirectory in exampleDirectories)
376 () => _runExampleProjectBuildTests(exampleDirectory),
stuartmorgan4ea6d3d2020-10-22 13:52:19 -0700377 if (branchName != 'beta' && branchName != 'stable')
Jenn Magderfab03bd2020-05-11 12:49:18 -0700378 ...<ShardRunner>[
379 // Web compilation tests.
380 () => _flutterBuildDart2js(
381 path.join('dev', 'integration_tests', 'web'),
382 path.join('lib', 'main.dart'),
383 ),
384 // Should not fail to compile with dart:io.
385 () => _flutterBuildDart2js(
386 path.join('dev', 'integration_tests', 'web_compile_tests'),
387 path.join('lib', 'dart_io_import.dart'),
388 ),
389 ],
390 ]..shuffle(math.Random(0));
391
392 await _selectIndexedSubshard(tests, kBuildTestShardCount);
393}
394
395Future<void> _runExampleProjectBuildTests(FileSystemEntity exampleDirectory) async {
396 // Only verify caching with flutter gallery.
397 final bool verifyCaching = exampleDirectory.path.contains('flutter_gallery');
398 if (exampleDirectory is! Directory) {
399 return;
400 }
401 final String examplePath = exampleDirectory.path;
402 final bool hasNullSafety = File(path.join(examplePath, 'null_safety')).existsSync();
403 final List<String> additionalArgs = hasNullSafety
Jonah Williams6fe800a2020-11-05 23:58:11 -0800404 ? <String>['--no-sound-null-safety']
Jenn Magderfab03bd2020-05-11 12:49:18 -0700405 : <String>[];
406 if (Directory(path.join(examplePath, 'android')).existsSync()) {
407 await _flutterBuildApk(examplePath, release: false, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
408 await _flutterBuildApk(examplePath, release: true, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
409 } else {
410 print('Example project ${path.basename(examplePath)} has no android directory, skipping apk');
411 }
412 if (Platform.isMacOS) {
413 if (Directory(path.join(examplePath, 'ios')).existsSync()) {
414 await _flutterBuildIpa(examplePath, release: false, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
415 await _flutterBuildIpa(examplePath, release: true, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
416 } else {
417 print('Example project ${path.basename(examplePath)} has no ios directory, skipping ipa');
418 }
Todd Volkertbae92c32019-11-24 22:29:28 -0800419 }
stuartmorganc23319e2020-10-29 09:27:59 -0700420 if (_shouldRunLinux()) {
421 if (Directory(path.join(examplePath, 'linux')).existsSync()) {
422 await _flutterBuildLinux(examplePath, release: false, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
423 await _flutterBuildLinux(examplePath, release: true, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
424 } else {
425 print('Example project ${path.basename(examplePath)} has no linux directory, skipping Linux');
426 }
427 }
stuartmorgan4ea6d3d2020-10-22 13:52:19 -0700428 if (_shouldRunMacOS()) {
429 if (Directory(path.join(examplePath, 'macos')).existsSync()) {
430 await _flutterBuildMacOS(examplePath, release: false, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
431 await _flutterBuildMacOS(examplePath, release: true, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
432 } else {
433 print('Example project ${path.basename(examplePath)} has no macos directory, skipping macOS');
434 }
435 }
stuartmorgan22c502e2020-10-22 15:14:30 -0700436 if (_shouldRunWindows()) {
437 if (Directory(path.join(examplePath, 'windows')).existsSync()) {
438 await _flutterBuildWin32(examplePath, release: false, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
439 await _flutterBuildWin32(examplePath, release: true, additionalArgs: additionalArgs, verifyCaching: verifyCaching);
440 } else {
441 print('Example project ${path.basename(examplePath)} has no windows directory, skipping Win32');
442 }
443 }
Ian Hickson124dc662019-10-18 16:35:39 -0700444}
Yegor8d643012018-10-08 12:38:46 -0700445
Jonah Williams0b3f5cf2020-04-21 20:39:36 -0700446Future<void> _flutterBuildApk(String relativePathToApplication, {
447 @required bool release,
Jonah Williams2a5690f2020-05-01 15:34:28 -0700448 bool verifyCaching = false,
Jonah Williams0b3f5cf2020-04-21 20:39:36 -0700449 List<String> additionalArgs = const <String>[],
450}) async {
Jonah Williams2a5690f2020-05-01 15:34:28 -0700451 print('${green}Testing APK build$reset for $cyan$relativePathToApplication$reset...');
stuartmorgan4ea6d3d2020-10-22 13:52:19 -0700452 await _flutterBuild(relativePathToApplication, 'APK', 'apk',
453 release: release,
454 verifyCaching: verifyCaching,
455 additionalArgs: additionalArgs
Ian Hickson124dc662019-10-18 16:35:39 -0700456 );
457}
458
Jonah Williams0b3f5cf2020-04-21 20:39:36 -0700459Future<void> _flutterBuildIpa(String relativePathToApplication, {
460 @required bool release,
461 List<String> additionalArgs = const <String>[],
Jonah Williams2a5690f2020-05-01 15:34:28 -0700462 bool verifyCaching = false,
Jonah Williams0b3f5cf2020-04-21 20:39:36 -0700463}) async {
Ian Hickson124dc662019-10-18 16:35:39 -0700464 assert(Platform.isMacOS);
465 print('${green}Testing IPA build$reset for $cyan$relativePathToApplication$reset...');
stuartmorgan4ea6d3d2020-10-22 13:52:19 -0700466 await _flutterBuild(relativePathToApplication, 'IPA', 'ios',
467 release: release,
468 verifyCaching: verifyCaching,
469 additionalArgs: <String>[...additionalArgs, '--no-codesign'],
470 );
471}
472
stuartmorganc23319e2020-10-29 09:27:59 -0700473Future<void> _flutterBuildLinux(String relativePathToApplication, {
474 @required bool release,
475 bool verifyCaching = false,
476 List<String> additionalArgs = const <String>[],
477}) async {
478 assert(Platform.isLinux);
479 await runCommand(flutter, <String>['config', '--enable-linux-desktop']);
480 print('${green}Testing Linux build$reset for $cyan$relativePathToApplication$reset...');
481 await _flutterBuild(relativePathToApplication, 'Linux', 'linux',
482 release: release,
483 verifyCaching: verifyCaching,
484 additionalArgs: additionalArgs
485 );
486}
487
stuartmorgan4ea6d3d2020-10-22 13:52:19 -0700488Future<void> _flutterBuildMacOS(String relativePathToApplication, {
489 @required bool release,
490 bool verifyCaching = false,
491 List<String> additionalArgs = const <String>[],
492}) async {
493 assert(Platform.isMacOS);
494 await runCommand(flutter, <String>['config', '--enable-macos-desktop']);
495 print('${green}Testing macOS build$reset for $cyan$relativePathToApplication$reset...');
496 await _flutterBuild(relativePathToApplication, 'macOS', 'macos',
497 release: release,
498 verifyCaching: verifyCaching,
499 additionalArgs: additionalArgs
500 );
501}
502
stuartmorgan22c502e2020-10-22 15:14:30 -0700503Future<void> _flutterBuildWin32(String relativePathToApplication, {
504 @required bool release,
505 bool verifyCaching = false,
506 List<String> additionalArgs = const <String>[],
507}) async {
508 assert(Platform.isWindows);
509 await runCommand(flutter, <String>['config', '--enable-windows-desktop']);
510 print('${green}Testing Windows build$reset for $cyan$relativePathToApplication$reset...');
511 await _flutterBuild(relativePathToApplication, 'Windows', 'windows',
512 release: release,
513 verifyCaching: verifyCaching,
514 additionalArgs: additionalArgs
515 );
516}
517
stuartmorgan4ea6d3d2020-10-22 13:52:19 -0700518Future<void> _flutterBuild(
519 String relativePathToApplication,
520 String platformLabel,
521 String platformBuildName, {
522 @required bool release,
523 bool verifyCaching = false,
524 List<String> additionalArgs = const <String>[],
525}) async {
Ian Hickson124dc662019-10-18 16:35:39 -0700526 await runCommand(flutter,
Jonah Williams0b3f5cf2020-04-21 20:39:36 -0700527 <String>[
528 'build',
stuartmorgan4ea6d3d2020-10-22 13:52:19 -0700529 platformBuildName,
Jonah Williams0b3f5cf2020-04-21 20:39:36 -0700530 ...additionalArgs,
Jonah Williams0b3f5cf2020-04-21 20:39:36 -0700531 if (release)
532 '--release'
533 else
534 '--debug',
535 '-v',
536 ],
Ian Hickson124dc662019-10-18 16:35:39 -0700537 workingDirectory: path.join(flutterRoot, relativePathToApplication),
538 );
stuartmorgan4ea6d3d2020-10-22 13:52:19 -0700539
Jonah Williams2a5690f2020-05-01 15:34:28 -0700540 if (verifyCaching) {
stuartmorgan4ea6d3d2020-10-22 13:52:19 -0700541 print('${green}Testing $platformLabel cache$reset for $cyan$relativePathToApplication$reset...');
Jonah Williams2a5690f2020-05-01 15:34:28 -0700542 await runCommand(flutter,
543 <String>[
544 'build',
stuartmorgan4ea6d3d2020-10-22 13:52:19 -0700545 platformBuildName,
Jonah Williams2a5690f2020-05-01 15:34:28 -0700546 '--performance-measurement-file=perf.json',
547 ...additionalArgs,
Jonah Williams2a5690f2020-05-01 15:34:28 -0700548 if (release)
549 '--release'
550 else
551 '--debug',
552 '-v',
553 ],
554 workingDirectory: path.join(flutterRoot, relativePathToApplication),
555 );
556 final File file = File(path.join(flutterRoot, relativePathToApplication, 'perf.json'));
557 if (!_allTargetsCached(file)) {
558 print('${red}Not all build targets cached after second run.$reset');
559 print('The target performance data was: ${file.readAsStringSync()}');
560 exit(1);
561 }
562 }
563}
564
565bool _allTargetsCached(File performanceFile) {
566 final Map<String, Object> data = json.decode(performanceFile.readAsStringSync())
567 as Map<String, Object>;
568 final List<Map<String, Object>> targets = (data['targets'] as List<Object>)
569 .cast<Map<String, Object>>();
570 return targets.every((Map<String, Object> element) => element['skipped'] == true);
Yegor8d643012018-10-08 12:38:46 -0700571}
572
Jonah Williams91af0712019-09-12 15:25:21 -0700573Future<void> _flutterBuildDart2js(String relativePathToApplication, String target, { bool expectNonZeroExit = false }) async {
Ian Hickson124dc662019-10-18 16:35:39 -0700574 print('${green}Testing Dart2JS build$reset for $cyan$relativePathToApplication$reset...');
Jonah Williams44b22c72019-03-25 18:47:37 -0700575 await runCommand(flutter,
Jonah Williams91af0712019-09-12 15:25:21 -0700576 <String>['build', 'web', '-v', '--target=$target'],
Jonah Williams44b22c72019-03-25 18:47:37 -0700577 workingDirectory: path.join(flutterRoot, relativePathToApplication),
Jonah Williams91af0712019-09-12 15:25:21 -0700578 expectNonZeroExit: expectNonZeroExit,
Jonah Williams3fedb8c2019-07-22 15:34:03 -0700579 environment: <String, String>{
580 'FLUTTER_WEB': 'true',
Alexandre Ardhuina36f8092019-09-19 07:54:46 +0200581 },
Jonah Williams44b22c72019-03-25 18:47:37 -0700582 );
Jonah Williams44b22c72019-03-25 18:47:37 -0700583}
Jonah Williams9bc56562019-02-14 22:42:30 -0800584
chunhtai4964fb62020-01-27 10:38:02 -0800585Future<void> _runAddToAppLifeCycleTests() async {
586 if (Platform.isMacOS) {
587 print('${green}Running add-to-app life cycle iOS integration tests$reset...');
588 final String addToAppDir = path.join(flutterRoot, 'dev', 'integration_tests', 'ios_add2app_life_cycle');
589 await runCommand('./build_and_test.sh',
590 <String>[],
591 workingDirectory: addToAppDir,
592 );
593 }
594}
595
Ian Hickson124dc662019-10-18 16:35:39 -0700596Future<void> _runFrameworkTests() async {
Dan Field20e0f132019-03-06 13:13:45 -0800597 final bq.BigqueryApi bigqueryApi = await _getBigqueryApi();
Jonah Williams6fe800a2020-11-05 23:58:11 -0800598 final List<String> soundNullSafetyOptions = <String>['--null-assertions', '--sound-null-safety'];
599 final List<String> mixedModeNullSafetyOptions = <String>['--null-assertions', '--no-sound-null-safety'];
Alexandre Ardhuin65ef1f92020-06-12 01:40:02 +0200600 final List<String> trackWidgetCreationAlternatives = <String>['--track-widget-creation', '--no-track-widget-creation'];
Greg Spencer90a5f462018-07-20 10:21:34 -0700601
Dan Fielda0fc3f32019-06-20 14:35:33 -0700602 Future<void> runWidgets() async {
Ian Hickson124dc662019-10-18 16:35:39 -0700603 print('${green}Running packages/flutter tests for$reset: ${cyan}test/widgets/$reset');
Alexandre Ardhuin65ef1f92020-06-12 01:40:02 +0200604 for (final String trackWidgetCreationOption in trackWidgetCreationAlternatives) {
605 await _runFlutterTest(
606 path.join(flutterRoot, 'packages', 'flutter'),
Michael Goderbauer42f37092020-10-21 10:09:11 -0700607 options: <String>[trackWidgetCreationOption, ...soundNullSafetyOptions],
Alexandre Ardhuin65ef1f92020-06-12 01:40:02 +0200608 tableData: bigqueryApi?.tabledata,
609 tests: <String>[ path.join('test', 'widgets') + path.separator ],
610 );
611 }
Ian Hickson124dc662019-10-18 16:35:39 -0700612 // Try compiling code outside of the packages/flutter directory with and without --track-widget-creation
Alexandre Ardhuin65ef1f92020-06-12 01:40:02 +0200613 for (final String trackWidgetCreationOption in trackWidgetCreationAlternatives) {
614 await _runFlutterTest(
615 path.join(flutterRoot, 'dev', 'integration_tests', 'flutter_gallery'),
616 options: <String>[trackWidgetCreationOption],
617 tableData: bigqueryApi?.tabledata,
618 );
619 }
Jonah Williams7ab04422020-12-01 09:53:50 -0800620 // Run release mode tests (see packages/flutter/test_release/README.md)
621 await _runFlutterTest(
622 path.join(flutterRoot, 'packages', 'flutter'),
623 options: <String>['--dart-define=dart.vm.product=true', ...soundNullSafetyOptions],
624 tableData: bigqueryApi?.tabledata,
625 tests: <String>[ 'test_release' + path.separator ],
626 );
Dan Fielda0fc3f32019-06-20 14:35:33 -0700627 }
628
Ian Hickson124dc662019-10-18 16:35:39 -0700629 Future<void> runLibraries() async {
Dan Fielda0fc3f32019-06-20 14:35:33 -0700630 final List<String> tests = Directory(path.join(flutterRoot, 'packages', 'flutter', 'test'))
631 .listSync(followLinks: false, recursive: false)
632 .whereType<Directory>()
Dan Field28906472019-07-15 12:34:53 -0700633 .where((Directory dir) => dir.path.endsWith('widgets') == false)
Ian Hickson124dc662019-10-18 16:35:39 -0700634 .map<String>((Directory dir) => path.join('test', path.basename(dir.path)) + path.separator)
Dan Fielda0fc3f32019-06-20 14:35:33 -0700635 .toList();
Ian Hickson124dc662019-10-18 16:35:39 -0700636 print('${green}Running packages/flutter tests$reset for: $cyan${tests.join(", ")}$reset');
Alexandre Ardhuin65ef1f92020-06-12 01:40:02 +0200637 for (final String trackWidgetCreationOption in trackWidgetCreationAlternatives) {
638 await _runFlutterTest(
639 path.join(flutterRoot, 'packages', 'flutter'),
Michael Goderbauer42f37092020-10-21 10:09:11 -0700640 options: <String>[trackWidgetCreationOption, ...soundNullSafetyOptions],
Alexandre Ardhuin65ef1f92020-06-12 01:40:02 +0200641 tableData: bigqueryApi?.tabledata,
642 tests: tests,
643 );
644 }
Dan Fielda0fc3f32019-06-20 14:35:33 -0700645 }
646
Kate Lovette9a83152020-12-15 14:48:04 -0600647 Future<void> runFixTests() async {
648 final List<String> args = <String>[
649 'fix',
650 '--compare-to-golden',
651 ];
652 await runCommand(
653 dart,
654 args,
655 workingDirectory: path.join(flutterRoot, 'packages', 'flutter', 'test_fixes'),
656 );
657 }
658
Greg Spencer94592ac2020-09-13 15:52:03 -0700659 Future<void> runPrivateTests() async {
660 final List<String> args = <String>[
661 'run',
Greg Spencer94592ac2020-09-13 15:52:03 -0700662 '--sound-null-safety',
663 'test_private.dart',
664 ];
665 final Map<String, String> pubEnvironment = <String, String>{
666 'FLUTTER_ROOT': flutterRoot,
667 };
668 if (Directory(pubCache).existsSync()) {
669 pubEnvironment['PUB_CACHE'] = pubCache;
670 }
671
672 // If an existing env variable exists append to it, but only if
673 // it doesn't appear to already include enable-asserts.
674 String toolsArgs = Platform.environment['FLUTTER_TOOL_ARGS'] ?? '';
675 if (!toolsArgs.contains('--enable-asserts')) {
676 toolsArgs += ' --enable-asserts';
677 }
678 pubEnvironment['FLUTTER_TOOL_ARGS'] = toolsArgs.trim();
679 // The flutter_tool will originally have been snapshotted without asserts.
680 // We need to force it to be regenerated with them enabled.
681 deleteFile(path.join(flutterRoot, 'bin', 'cache', 'flutter_tools.snapshot'));
682 deleteFile(path.join(flutterRoot, 'bin', 'cache', 'flutter_tools.stamp'));
683
684 await runCommand(
685 pub,
686 args,
687 workingDirectory: path.join(flutterRoot, 'packages', 'flutter', 'test_private'),
688 environment: pubEnvironment,
689 );
690 }
691
Ian Hickson124dc662019-10-18 16:35:39 -0700692 Future<void> runMisc() async {
693 print('${green}Running package tests$reset for directories other than packages/flutter');
Dan Fielda0fc3f32019-06-20 14:35:33 -0700694 await _pubRunTest(path.join(flutterRoot, 'dev', 'bots'), tableData: bigqueryApi?.tabledata);
695 await _pubRunTest(path.join(flutterRoot, 'dev', 'devicelab'), tableData: bigqueryApi?.tabledata);
696 await _pubRunTest(path.join(flutterRoot, 'dev', 'snippets'), tableData: bigqueryApi?.tabledata);
Shi-Hao Hong37e66b22019-12-06 11:35:08 -0800697 await _pubRunTest(path.join(flutterRoot, 'dev', 'tools'), tableData: bigqueryApi?.tabledata);
Yuqian Li8e7748e2020-11-08 20:32:04 -0800698 await _pubRunTest(path.join(flutterRoot, 'dev', 'benchmarks', 'metrics_center'), tableData: bigqueryApi?.tabledata);
Dan Fielda0fc3f32019-06-20 14:35:33 -0700699 await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing'), tableData: bigqueryApi?.tabledata);
700 await _runFlutterTest(path.join(flutterRoot, 'dev', 'manual_tests'), tableData: bigqueryApi?.tabledata);
701 await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'vitool'), tableData: bigqueryApi?.tabledata);
702 await _runFlutterTest(path.join(flutterRoot, 'examples', 'hello_world'), tableData: bigqueryApi?.tabledata);
Michael Goderbauerdd2ea7c2020-10-22 13:33:07 -0700703 await _runFlutterTest(path.join(flutterRoot, 'examples', 'layers'), tableData: bigqueryApi?.tabledata, options: soundNullSafetyOptions);
Greg Spencer4b4cff92020-01-30 09:31:07 -0800704 await _runFlutterTest(path.join(flutterRoot, 'dev', 'benchmarks', 'test_apps', 'stocks'), tableData: bigqueryApi?.tabledata);
Angjie Li9df17902020-01-02 19:48:01 -0800705 await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_driver'), tableData: bigqueryApi?.tabledata, tests: <String>[path.join('test', 'src', 'real_tests')]);
Dan Field76784652020-11-05 17:28:47 -0800706 await _runFlutterTest(path.join(flutterRoot, 'packages', 'integration_test'), tableData: bigqueryApi?.tabledata);
Kate Lovett7aa5b072019-11-15 10:29:19 -0800707 await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_goldens'), tableData: bigqueryApi?.tabledata);
Darren Austin1c7e34b2020-11-12 15:13:51 -0800708 await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_localizations'), tableData: bigqueryApi?.tabledata, options: soundNullSafetyOptions);
Michael Goderbauer42f37092020-10-21 10:09:11 -0700709 await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_test'), tableData: bigqueryApi?.tabledata, options: soundNullSafetyOptions);
Ian Hickson124dc662019-10-18 16:35:39 -0700710 await _runFlutterTest(path.join(flutterRoot, 'packages', 'fuchsia_remote_debug_protocol'), tableData: bigqueryApi?.tabledata);
Michael Goderbauer42f37092020-10-21 10:09:11 -0700711 await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'non_nullable'), options: mixedModeNullSafetyOptions);
Ian Hickson124dc662019-10-18 16:35:39 -0700712 await _runFlutterTest(
Dan Field766bd702020-02-13 14:04:02 -0800713 path.join(flutterRoot, 'dev', 'tracing_tests'),
714 options: <String>['--enable-vmservice'],
715 tableData: bigqueryApi?.tabledata,
716 );
Kate Lovette9a83152020-12-15 14:48:04 -0600717 await runFixTests();
Greg Spencer94592ac2020-09-13 15:52:03 -0700718 await runPrivateTests();
Dan Fieldb2a27c12020-01-31 09:22:30 -0800719 const String httpClientWarning =
720 'Warning: At least one test in this suite creates an HttpClient. When\n'
721 'running a test suite that uses TestWidgetsFlutterBinding, all HTTP\n'
722 'requests will return status code 400, and no network request will\n'
adityapstara57feac2020-03-25 17:26:01 -0500723 'actually be made. Any test expecting a real network connection and\n'
Dan Fieldb2a27c12020-01-31 09:22:30 -0800724 'status code will fail.\n'
725 'To test code that needs an HttpClient, provide your own HttpClient\n'
726 'implementation to the code under test, so that your test can\n'
727 'consistently provide a testable response to the code under test.';
728 await _runFlutterTest(
729 path.join(flutterRoot, 'packages', 'flutter_test'),
730 script: path.join('test', 'bindings_test_failure.dart'),
731 expectFailure: true,
732 printOutput: false,
Yegorff9082c2020-10-19 20:56:25 -0700733 outputChecker: (CommandResult result) {
734 final Iterable<Match> matches = httpClientWarning.allMatches(result.flattenedStdout);
Dan Fieldb2a27c12020-01-31 09:22:30 -0800735 if (matches == null || matches.isEmpty || matches.length > 1) {
736 return 'Failed to print warning about HttpClientUsage, or printed it too many times.\n'
Yegorff9082c2020-10-19 20:56:25 -0700737 'stdout:\n${result.flattenedStdout}';
Dan Fieldb2a27c12020-01-31 09:22:30 -0800738 }
739 return null;
740 },
741 tableData: bigqueryApi?.tabledata,
742 );
Dan Fielda0fc3f32019-06-20 14:35:33 -0700743 }
Todd Volkertbd679262017-06-15 17:54:45 -0700744
Ian Hickson124dc662019-10-18 16:35:39 -0700745 await selectSubshard(<String, ShardRunner>{
746 'widgets': runWidgets,
747 'libraries': runLibraries,
748 'misc': runMisc,
749 });
Todd Volkertbd679262017-06-15 17:54:45 -0700750}
751
Ian Hickson124dc662019-10-18 16:35:39 -0700752Future<void> _runFrameworkCoverage() async {
Sivacf18d012018-10-19 06:19:24 -0700753 final File coverageFile = File(path.join(flutterRoot, 'packages', 'flutter', 'coverage', 'lcov.info'));
754 if (!coverageFile.existsSync()) {
755 print('${red}Coverage file not found.$reset');
Ian Hickson124dc662019-10-18 16:35:39 -0700756 print('Expected to find: $cyan${coverageFile.absolute}$reset');
757 print('This file is normally obtained by running `${green}flutter update-packages$reset`.');
Sivacf18d012018-10-19 06:19:24 -0700758 exit(1);
759 }
760 coverageFile.deleteSync();
761 await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter'),
762 options: const <String>['--coverage'],
763 );
764 if (!coverageFile.existsSync()) {
765 print('${red}Coverage file not found.$reset');
Ian Hickson124dc662019-10-18 16:35:39 -0700766 print('Expected to find: $cyan${coverageFile.absolute}$reset');
767 print('This file should have been generated by the `${green}flutter test --coverage$reset` script, but was not.');
Sivacf18d012018-10-19 06:19:24 -0700768 exit(1);
769 }
Ian Hickson124dc662019-10-18 16:35:39 -0700770}
771
Yegorb3404692020-02-13 18:34:08 -0800772Future<void> _runWebUnitTests() async {
Ian Hickson124dc662019-10-18 16:35:39 -0700773 final Map<String, ShardRunner> subshards = <String, ShardRunner>{};
774
775 final Directory flutterPackageDirectory = Directory(path.join(flutterRoot, 'packages', 'flutter'));
776 final Directory flutterPackageTestDirectory = Directory(path.join(flutterPackageDirectory.path, 'test'));
777
778 final List<String> allTests = flutterPackageTestDirectory
779 .listSync()
780 .whereType<Directory>()
Ian Hickson124dc662019-10-18 16:35:39 -0700781 .expand((Directory directory) => directory
782 .listSync(recursive: true)
783 .where((FileSystemEntity entity) => entity.path.endsWith('_test.dart'))
784 )
785 .whereType<File>()
786 .map<String>((File file) => path.relative(file.path, from: flutterPackageDirectory.path))
Jonah Williamscfb63352020-11-18 14:55:26 -0800787 .where((String filePath) => !kWebTestFileKnownFailures.contains(path.split(filePath).join('/')))
Ian Hickson124dc662019-10-18 16:35:39 -0700788 .toList()
789 // Finally we shuffle the list because we want the average cost per file to be uniformly
790 // distributed. If the list is not sorted then different shards and batches may have
791 // very different characteristics.
792 // We use a constant seed for repeatability.
793 ..shuffle(math.Random(0));
794
Yegor14cceef2020-04-13 16:05:02 -0700795 assert(webShardCount >= 1);
796 final int testsPerShard = (allTests.length / webShardCount).ceil();
797 assert(testsPerShard * webShardCount >= allTests.length);
Ian Hickson124dc662019-10-18 16:35:39 -0700798
799 // This for loop computes all but the last shard.
Yegor14cceef2020-04-13 16:05:02 -0700800 for (int index = 0; index < webShardCount - 1; index += 1) {
Ian Hickson124dc662019-10-18 16:35:39 -0700801 subshards['$index'] = () => _runFlutterWebTest(
802 flutterPackageDirectory.path,
803 allTests.sublist(
804 index * testsPerShard,
805 (index + 1) * testsPerShard,
806 ),
807 );
808 }
809
810 // The last shard also runs the flutter_web_plugins tests.
811 //
812 // We make sure the last shard ends in _last so it's easier to catch mismatches
813 // between `.cirrus.yml` and `test.dart`.
Yegor14cceef2020-04-13 16:05:02 -0700814 subshards['${webShardCount - 1}_last'] = () async {
Ian Hickson124dc662019-10-18 16:35:39 -0700815 await _runFlutterWebTest(
816 flutterPackageDirectory.path,
817 allTests.sublist(
Yegor14cceef2020-04-13 16:05:02 -0700818 (webShardCount - 1) * testsPerShard,
Ian Hickson124dc662019-10-18 16:35:39 -0700819 allTests.length,
820 ),
821 );
822 await _runFlutterWebTest(
823 path.join(flutterRoot, 'packages', 'flutter_web_plugins'),
824 <String>['test'],
825 );
Angjie Li9df17902020-01-02 19:48:01 -0800826 await _runFlutterWebTest(
827 path.join(flutterRoot, 'packages', 'flutter_driver'),
828 <String>[path.join('test', 'src', 'web_tests', 'web_extension_test.dart')],
829 );
Ian Hickson124dc662019-10-18 16:35:39 -0700830 };
831
832 await selectSubshard(subshards);
833}
834
Yegor2fa03432020-10-29 14:23:02 -0700835/// Coarse-grained integration tests running on the Web.
836///
837/// These tests are sharded into [kWebLongRunningTestShardCount] shards.
838Future<void> _runWebLongRunningTests() async {
839 final List<ShardRunner> tests = <ShardRunner>[
840 () => _runGalleryE2eWebTest('debug'),
841 () => _runGalleryE2eWebTest('debug', canvasKit: true),
842 () => _runGalleryE2eWebTest('profile'),
843 () => _runGalleryE2eWebTest('profile', canvasKit: true),
844 () => _runGalleryE2eWebTest('release'),
845 () => _runGalleryE2eWebTest('release', canvasKit: true),
Yegorf03ac0b2020-10-30 17:27:55 -0700846 ];
847 await _ensureChromeDriverIsRunning();
Yegor2fa03432020-10-29 14:23:02 -0700848 await _selectIndexedSubshard(tests, kWebLongRunningTestShardCount);
Yegorf03ac0b2020-10-30 17:27:55 -0700849 await _stopChromeDriver();
Yegor2fa03432020-10-29 14:23:02 -0700850}
851
Amir Hardon2ceb3712020-11-24 17:18:06 -0800852/// Returns the commit hash of the flutter/plugins repository that's rolled in.
853///
854/// The flutter/plugins repository is a downstream dependency, it is only used
855/// by flutter/flutter for testing purposes, to assure stable tests for a given
856/// flutter commit the flutter/plugins commit hash to test against is coded in
857/// the bin/internal/flutter_plugins.version file.
858///
859/// The `filesystem` parameter specified filesystem to read the plugins version file from.
860/// The `pluginsVersionFile` parameter allows specifying an alternative path for the
861/// plugins version file, when null [flutterPluginsVersionFile] is used.
862Future<String> getFlutterPluginsVersion({
863 fs.FileSystem fileSystem = const LocalFileSystem(),
864 String pluginsVersionFile,
865}) async {
866 final File versionFile = fileSystem.file(pluginsVersionFile ?? flutterPluginsVersionFile);
867 final String versionFileContents = await versionFile.readAsString();
868 return versionFileContents.trim();
869}
870
Amir Hardon2b4a2352020-11-23 12:28:28 -0800871/// Executes the test suite for the flutter/plugins repo.
872Future<void> _runFlutterPluginsTests() async {
873 Future<void> runAnalyze() async {
874 print('${green}Running analysis for flutter/plugins$reset');
875 final Directory checkout = Directory.systemTemp.createTempSync('plugins');
876 await runCommand(
877 'git',
878 <String>[
879 '-c',
880 'core.longPaths=true',
881 'clone',
882 'https://github.com/flutter/plugins.git',
883 '.'
884 ],
885 workingDirectory: checkout.path,
886 );
Amir Hardon2ceb3712020-11-24 17:18:06 -0800887 final String pluginsCommit = await getFlutterPluginsVersion();
888 await runCommand(
889 'git',
890 <String>[
891 '-c',
892 'core.longPaths=true',
893 'checkout',
894 pluginsCommit,
895 ],
896 workingDirectory: checkout.path,
897 );
Amir Hardon2b4a2352020-11-23 12:28:28 -0800898 await runCommand(
899 pub,
900 <String>[
901 'global',
902 'activate',
903 'flutter_plugin_tools',
904 ],
905 workingDirectory: checkout.path,
906 );
907 await runCommand(
908 pub,
909 <String>[
910 'global',
911 'run',
912 'flutter_plugin_tools',
913 'analyze',
914 ],
915 workingDirectory: checkout.path,
916 );
917 }
918 await selectSubshard(<String, ShardRunner>{
919 'analyze': runAnalyze,
920 });
921}
922
Yegor2fa03432020-10-29 14:23:02 -0700923// The `chromedriver` process created by this test.
924//
925// If an existing chromedriver is already available on port 4444, the existing
926// process is reused and this variable remains null.
927Command _chromeDriver;
928
Yegor2fa03432020-10-29 14:23:02 -0700929Future<bool> _isChromeDriverRunning() async {
930 try {
Yegorf03ac0b2020-10-30 17:27:55 -0700931 final RawSocket socket = await RawSocket.connect('localhost', 4444);
932 socket.shutdown(SocketDirection.both);
933 await socket.close();
Yegor2fa03432020-10-29 14:23:02 -0700934 return true;
935 } on SocketException {
936 return false;
937 }
938}
939
940Future<void> _ensureChromeDriverIsRunning() async {
941 // If we cannot connect to ChromeDriver, assume it is not running. Launch it.
942 if (!await _isChromeDriverRunning()) {
943 print('Starting chromedriver');
944 // Assume chromedriver is in the PATH.
945 _chromeDriver = await startCommand(
946 'chromedriver',
947 <String>['--port=4444'],
948 );
949 while (!await _isChromeDriverRunning()) {
950 await Future<void>.delayed(const Duration(milliseconds: 100));
951 print('Waiting for chromedriver to start up.');
952 }
953 }
954
955 final HttpClient client = HttpClient();
956 final Uri chromeDriverUrl = Uri.parse('http://localhost:4444/status');
957 final HttpClientRequest request = await client.getUrl(chromeDriverUrl);
958 final HttpClientResponse response = await request.close();
959 final Map<String, dynamic> webDriverStatus = json.decode(await response.transform(utf8.decoder).join('')) as Map<String, dynamic>;
960 client.close();
961 final bool webDriverReady = webDriverStatus['value']['ready'] as bool;
962 if (!webDriverReady) {
963 throw Exception('WebDriver not available.');
964 }
965}
966
967Future<void> _stopChromeDriver() async {
968 if (_chromeDriver == null) {
969 return;
970 }
Yegorf03ac0b2020-10-30 17:27:55 -0700971 print('Stopping chromedriver');
Yegor2fa03432020-10-29 14:23:02 -0700972 _chromeDriver.process.kill();
Yegor2fa03432020-10-29 14:23:02 -0700973}
974
975/// Exercises the old gallery in a browser for a long period of time, looking
976/// for memory leaks and dangling pointers.
977///
978/// This is not a performance test.
979///
980/// If [canvasKit] is set to true, runs the test in CanvasKit mode.
981///
982/// The test is written using `package:integration_test` (despite the "e2e" in
983/// the name, which is there for historic reasons).
984Future<void> _runGalleryE2eWebTest(String buildMode, { bool canvasKit = false }) async {
985 print('${green}Running flutter_gallery integration test in --$buildMode using ${canvasKit ? 'CanvasKit' : 'HTML'} renderer.$reset');
986 final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'flutter_gallery');
987 await runCommand(
988 flutter,
989 <String>[ 'clean' ],
990 workingDirectory: testAppDirectory,
991 );
992 await runCommand(
993 flutter,
994 <String>[
995 'drive',
996 if (canvasKit)
997 '--dart-define=FLUTTER_WEB_USE_SKIA=true',
nturgut14206392020-12-21 09:55:23 -0800998 if (!canvasKit)
999 '--dart-define=FLUTTER_WEB_USE_SKIA=false',
nturgut272b23c2020-12-28 10:55:53 -08001000 if (!canvasKit)
1001 '--dart-define=FLUTTER_WEB_AUTO_DETECT=false',
Yegor2fa03432020-10-29 14:23:02 -07001002 '--driver=test_driver/transitions_perf_e2e_test.dart',
1003 '--target=test_driver/transitions_perf_e2e.dart',
1004 '--browser-name=chrome',
Jonah Williams3e419622020-10-30 13:42:54 -07001005 '--no-sound-null-safety',
Yegor2fa03432020-10-29 14:23:02 -07001006 '-d',
1007 'web-server',
1008 '--$buildMode',
1009 ],
1010 workingDirectory: testAppDirectory,
1011 environment: <String, String>{
1012 'FLUTTER_WEB': 'true',
1013 },
1014 );
1015 print('${green}Integration test passed.$reset');
1016}
1017
Yegorb3404692020-02-13 18:34:08 -08001018Future<void> _runWebIntegrationTests() async {
Yegor7a10b462020-10-26 15:57:02 -07001019 await _runWebStackTraceTest('profile', 'lib/stack_trace.dart');
1020 await _runWebStackTraceTest('release', 'lib/stack_trace.dart');
1021 await _runWebStackTraceTest('profile', 'lib/framework_stack_trace.dart');
1022 await _runWebStackTraceTest('release', 'lib/framework_stack_trace.dart');
Jonah Williams9b4159c2020-02-28 14:21:02 -08001023 await _runWebDebugTest('lib/stack_trace.dart');
Yegor7a10b462020-10-26 15:57:02 -07001024 await _runWebDebugTest('lib/framework_stack_trace.dart');
Jonah Williamsbbf913b2020-03-05 12:34:03 -08001025 await _runWebDebugTest('lib/web_directory_loading.dart');
Jonah Williams9b4159c2020-02-28 14:21:02 -08001026 await _runWebDebugTest('test/test.dart');
Jonah Williamscb7770b2020-07-15 09:54:39 -07001027 await _runWebDebugTest('lib/null_assert_main.dart', enableNullSafety: true);
Jonah Williamsb1d75fc2020-06-03 21:02:07 -07001028 await _runWebDebugTest('lib/null_safe_main.dart', enableNullSafety: true);
Jonah Williamse092dcf2020-04-16 10:56:49 -07001029 await _runWebDebugTest('lib/web_define_loading.dart',
1030 additionalArguments: <String>[
1031 '--dart-define=test.valueA=Example',
1032 '--dart-define=test.valueB=Value',
1033 ]
1034 );
1035 await _runWebReleaseTest('lib/web_define_loading.dart',
1036 additionalArguments: <String>[
1037 '--dart-define=test.valueA=Example',
1038 '--dart-define=test.valueB=Value',
1039 ]
1040 );
Jonah Williamsddb01a02020-10-02 21:52:38 -07001041 await _runWebDebugTest('lib/sound_mode.dart', additionalArguments: <String>[
Jonah Williamsddb01a02020-10-02 21:52:38 -07001042 '--sound-null-safety',
1043 ]);
1044 await _runWebReleaseTest('lib/sound_mode.dart', additionalArguments: <String>[
Jonah Williamsddb01a02020-10-02 21:52:38 -07001045 '--sound-null-safety',
1046 ]);
Yegorb3404692020-02-13 18:34:08 -08001047}
1048
Yegor7a10b462020-10-26 15:57:02 -07001049Future<void> _runWebStackTraceTest(String buildMode, String entrypoint) async {
Yegorb3404692020-02-13 18:34:08 -08001050 final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web');
Alexandre Ardhuin85ab3312020-02-14 10:41:23 +01001051 final String appBuildDirectory = path.join(testAppDirectory, 'build', 'web');
Yegorb3404692020-02-13 18:34:08 -08001052
1053 // Build the app.
1054 await runCommand(
1055 flutter,
1056 <String>[ 'clean' ],
1057 workingDirectory: testAppDirectory,
1058 );
1059 await runCommand(
1060 flutter,
1061 <String>[
1062 'build',
1063 'web',
1064 '--$buildMode',
1065 '-t',
Yegor7a10b462020-10-26 15:57:02 -07001066 entrypoint,
Yegorb3404692020-02-13 18:34:08 -08001067 ],
1068 workingDirectory: testAppDirectory,
1069 environment: <String, String>{
1070 'FLUTTER_WEB': 'true',
1071 },
1072 );
1073
1074 // Run the app.
1075 final String result = await evalTestAppInChrome(
1076 appUrl: 'http://localhost:8080/index.html',
1077 appDirectory: appBuildDirectory,
1078 );
1079
1080 if (result.contains('--- TEST SUCCEEDED ---')) {
1081 print('${green}Web stack trace integration test passed.$reset');
1082 } else {
1083 print(result);
1084 print('${red}Web stack trace integration test failed.$reset');
1085 exit(1);
1086 }
1087}
1088
Jonah Williamse092dcf2020-04-16 10:56:49 -07001089/// Run a web integration test in release mode.
1090Future<void> _runWebReleaseTest(String target, {
1091 List<String> additionalArguments = const<String>[],
1092}) async {
1093 final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web');
1094 final String appBuildDirectory = path.join(testAppDirectory, 'build', 'web');
1095
1096 // Build the app.
1097 await runCommand(
1098 flutter,
1099 <String>[ 'clean' ],
1100 workingDirectory: testAppDirectory,
1101 );
1102 await runCommand(
1103 flutter,
1104 <String>[
1105 'build',
1106 'web',
1107 '--release',
1108 ...additionalArguments,
1109 '-t',
1110 target,
1111 ],
1112 workingDirectory: testAppDirectory,
1113 environment: <String, String>{
1114 'FLUTTER_WEB': 'true',
1115 },
1116 );
1117
1118 // Run the app.
1119 final String result = await evalTestAppInChrome(
1120 appUrl: 'http://localhost:8080/index.html',
1121 appDirectory: appBuildDirectory,
1122 );
1123
1124 if (result.contains('--- TEST SUCCEEDED ---')) {
1125 print('${green}Web release mode test passed.$reset');
1126 } else {
1127 print(result);
1128 print('${red}Web release mode test failed.$reset');
1129 exit(1);
1130 }
1131}
1132
Yegorb3404692020-02-13 18:34:08 -08001133/// Debug mode is special because `flutter build web` doesn't build in debug mode.
1134///
1135/// Instead, we use `flutter run --debug` and sniff out the standard output.
Jonah Williamse092dcf2020-04-16 10:56:49 -07001136Future<void> _runWebDebugTest(String target, {
Jonah Williams0b3f5cf2020-04-21 20:39:36 -07001137 bool enableNullSafety = false,
Jonah Williamse092dcf2020-04-16 10:56:49 -07001138 List<String> additionalArguments = const<String>[],
1139}) async {
Yegorb3404692020-02-13 18:34:08 -08001140 final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web');
Yegorb3404692020-02-13 18:34:08 -08001141 bool success = false;
Yegorff9082c2020-10-19 20:56:25 -07001142 final CommandResult result = await runCommand(
Yegorb3404692020-02-13 18:34:08 -08001143 flutter,
1144 <String>[
1145 'run',
1146 '--debug',
Jonah Williams0b3f5cf2020-04-21 20:39:36 -07001147 if (enableNullSafety)
1148 ...<String>[
Jonah Williams69fd5c52020-08-19 14:52:47 -07001149 '--no-sound-null-safety',
1150 '--null-assertions',
Jonah Williams0b3f5cf2020-04-21 20:39:36 -07001151 ],
Yegorb3404692020-02-13 18:34:08 -08001152 '-d',
1153 'chrome',
1154 '--web-run-headless',
nturgut10c358b2020-12-21 16:07:51 -08001155 '--dart-define=FLUTTER_WEB_USE_SKIA=false',
nturgut272b23c2020-12-28 10:55:53 -08001156 '--dart-define=FLUTTER_WEB_AUTO_DETECT=false',
Jonah Williamse092dcf2020-04-16 10:56:49 -07001157 ...additionalArguments,
Jonah Williams9b4159c2020-02-28 14:21:02 -08001158 '-t',
1159 target,
Yegorb3404692020-02-13 18:34:08 -08001160 ],
Yegorb3404692020-02-13 18:34:08 -08001161 outputMode: OutputMode.capture,
1162 outputListener: (String line, Process process) {
1163 if (line.contains('--- TEST SUCCEEDED ---')) {
1164 success = true;
1165 }
1166 if (success || line.contains('--- TEST FAILED ---')) {
1167 process.stdin.add('q'.codeUnits);
1168 }
1169 },
1170 workingDirectory: testAppDirectory,
1171 environment: <String, String>{
1172 'FLUTTER_WEB': 'true',
1173 },
1174 );
1175
1176 if (success) {
1177 print('${green}Web stack trace integration test passed.$reset');
1178 } else {
Yegorff9082c2020-10-19 20:56:25 -07001179 print(result.flattenedStdout);
1180 print(result.flattenedStderr);
Yegorb3404692020-02-13 18:34:08 -08001181 print('${red}Web stack trace integration test failed.$reset');
1182 exit(1);
1183 }
1184}
1185
Ian Hickson124dc662019-10-18 16:35:39 -07001186Future<void> _runFlutterWebTest(String workingDirectory, List<String> tests) async {
Yegor7b4c1952020-03-18 15:11:48 -07001187 await runCommand(
1188 flutter,
1189 <String>[
1190 'test',
1191 if (ciProvider == CiProviders.cirrus)
1192 '--concurrency=1', // do not parallelize on Cirrus, to reduce flakiness
1193 '-v',
1194 '--platform=chrome',
nturgut985e5ff2021-01-05 10:57:01 -08001195 // TODO(ferhatb): Run web tests with both rendering backends.
1196 '--web-renderer=html', // use html backend for web tests.
Jonah Williamsa76289b2020-11-19 16:49:05 -08001197 '--sound-null-safety', // web tests do not autodetect yet.
Yegor7b4c1952020-03-18 15:11:48 -07001198 ...?flutterTestArgs,
1199 ...tests,
1200 ],
1201 workingDirectory: workingDirectory,
1202 environment: <String, String>{
1203 'FLUTTER_WEB': 'true',
Yegor7b4c1952020-03-18 15:11:48 -07001204 },
1205 );
Michael Goderbauer76964622017-02-14 10:21:33 -08001206}
1207
Ian Hickson8c6d60b2019-10-02 12:46:51 -07001208Future<void> _pubRunTest(String workingDirectory, {
Jonah Williams124aa6f2020-03-04 17:12:58 -08001209 List<String> testPaths,
Ian Hickson8c6d60b2019-10-02 12:46:51 -07001210 bool enableFlutterToolAsserts = true,
1211 bool useBuildRunner = false,
Jonah Williams124aa6f2020-03-04 17:12:58 -08001212 String coverage,
Dan Field20e0f132019-03-06 13:13:45 -08001213 bq.TabledataResourceApi tableData,
Jonah Williams68840862020-03-17 17:29:53 -07001214 bool forceSingleCore = false,
Jenn Magder9037ccd2020-06-09 12:38:01 -07001215 Duration perTestTimeout,
Dan Field20e0f132019-03-06 13:13:45 -08001216}) async {
Ian Hickson124dc662019-10-18 16:35:39 -07001217 int cpus;
1218 final String cpuVariable = Platform.environment['CPU']; // CPU is set in cirrus.yml
1219 if (cpuVariable != null) {
1220 cpus = int.tryParse(cpuVariable, radix: 10);
1221 if (cpus == null) {
1222 print('${red}The CPU environment variable, if set, must be set to the integer number of available cores.$reset');
1223 print('Actual value: "$cpuVariable"');
1224 exit(1);
1225 }
1226 } else {
1227 cpus = 2; // Don't default to 1, otherwise we won't catch race conditions.
1228 }
Jonah Williams68840862020-03-17 17:29:53 -07001229 // Integration tests that depend on external processes like chrome
1230 // can get stuck if there are multiple instances running at once.
1231 if (forceSingleCore) {
1232 cpus = 1;
1233 }
Jonah Williams124aa6f2020-03-04 17:12:58 -08001234
1235 final List<String> args = <String>[
1236 'run',
1237 'test',
1238 if (useFlutterTestFormatter)
1239 '-rjson'
1240 else
1241 '-rcompact',
1242 '-j$cpus',
1243 if (!hasColor)
1244 '--no-color',
1245 if (coverage != null)
1246 '--coverage=$coverage',
Jenn Magder9037ccd2020-06-09 12:38:01 -07001247 if (perTestTimeout != null)
1248 '--timeout=${perTestTimeout.inMilliseconds.toString()}ms',
Jonah Williams124aa6f2020-03-04 17:12:58 -08001249 if (testPaths != null)
1250 for (final String testPath in testPaths)
1251 testPath,
1252 ];
Jonah Williamsd91cfff2019-03-01 19:18:38 -08001253 final Map<String, String> pubEnvironment = <String, String>{
1254 'FLUTTER_ROOT': flutterRoot,
1255 };
Ian Hickson8c6d60b2019-10-02 12:46:51 -07001256 if (Directory(pubCache).existsSync()) {
1257 pubEnvironment['PUB_CACHE'] = pubCache;
1258 }
Jonah Williamsd91cfff2019-03-01 19:18:38 -08001259 if (enableFlutterToolAsserts) {
1260 // If an existing env variable exists append to it, but only if
1261 // it doesn't appear to already include enable-asserts.
1262 String toolsArgs = Platform.environment['FLUTTER_TOOL_ARGS'] ?? '';
1263 if (!toolsArgs.contains('--enable-asserts'))
Ian Hickson8c6d60b2019-10-02 12:46:51 -07001264 toolsArgs += ' --enable-asserts';
Jonah Williamsd91cfff2019-03-01 19:18:38 -08001265 pubEnvironment['FLUTTER_TOOL_ARGS'] = toolsArgs.trim();
Ian Hickson8c6d60b2019-10-02 12:46:51 -07001266 // The flutter_tool will originally have been snapshotted without asserts.
1267 // We need to force it to be regenerated with them enabled.
1268 deleteFile(path.join(flutterRoot, 'bin', 'cache', 'flutter_tools.snapshot'));
1269 deleteFile(path.join(flutterRoot, 'bin', 'cache', 'flutter_tools.stamp'));
Jonah Williamsd91cfff2019-03-01 19:18:38 -08001270 }
Dan Field97a81202019-03-14 08:26:59 -07001271 if (useFlutterTestFormatter) {
1272 final FlutterCompactFormatter formatter = FlutterCompactFormatter();
Dan Field24f39d42020-01-02 11:47:28 -08001273 Stream<String> testOutput;
1274 try {
1275 testOutput = runAndGetStdout(
1276 pub,
1277 args,
1278 workingDirectory: workingDirectory,
1279 environment: pubEnvironment,
1280 );
1281 } finally {
1282 formatter.finish();
1283 }
Dan Field97a81202019-03-14 08:26:59 -07001284 await _processTestOutput(formatter, testOutput, tableData);
1285 } else {
1286 await runCommand(
1287 pub,
1288 args,
Ian Hickson8c6d60b2019-10-02 12:46:51 -07001289 workingDirectory: workingDirectory,
1290 environment: pubEnvironment,
1291 removeLine: useBuildRunner ? (String line) => line.startsWith('[INFO]') : null,
Dan Field97a81202019-03-14 08:26:59 -07001292 );
1293 }
Jonah Williamsd91cfff2019-03-01 19:18:38 -08001294}
1295
Alexandre Ardhuind340e2f2018-10-04 18:44:23 +02001296Future<void> _runFlutterTest(String workingDirectory, {
Todd Volkert58d34932018-04-17 10:00:06 -07001297 String script,
Alexandre Ardhuin09276be2018-06-05 08:50:40 +02001298 bool expectFailure = false,
1299 bool printOutput = true,
James Linc02b8052019-08-09 15:10:45 -07001300 OutputChecker outputChecker,
Alexandre Ardhuin09276be2018-06-05 08:50:40 +02001301 List<String> options = const <String>[],
1302 bool skip = false,
Dan Field20e0f132019-03-06 13:13:45 -08001303 bq.TabledataResourceApi tableData,
Jonah Williams7bed3782019-03-14 17:12:40 -07001304 Map<String, String> environment,
Dan Fielda0fc3f32019-06-20 14:35:33 -07001305 List<String> tests = const <String>[],
Dan Field20e0f132019-03-06 13:13:45 -08001306}) async {
Ian Hickson124dc662019-10-18 16:35:39 -07001307 assert(!printOutput || outputChecker == null, 'Output either can be printed or checked but not both');
James Linc02b8052019-08-09 15:10:45 -07001308
Alexandre Ardhuin758009b2019-07-02 21:11:56 +02001309 final List<String> args = <String>[
1310 'test',
1311 ...options,
1312 ...?flutterTestArgs,
1313 ];
Dan Field20e0f132019-03-06 13:13:45 -08001314
Dan Field97a81202019-03-14 08:26:59 -07001315 final bool shouldProcessOutput = useFlutterTestFormatter && !expectFailure && !options.contains('--coverage');
Ian Hickson124dc662019-10-18 16:35:39 -07001316 if (shouldProcessOutput)
Dan Field20e0f132019-03-06 13:13:45 -08001317 args.add('--machine');
Dan Field20e0f132019-03-06 13:13:45 -08001318
Ian Hicksondac2ebf2018-06-11 16:28:01 -07001319 if (script != null) {
1320 final String fullScriptPath = path.join(workingDirectory, script);
1321 if (!FileSystemEntity.isFileSync(fullScriptPath)) {
Ian Hickson124dc662019-10-18 16:35:39 -07001322 print('${red}Could not find test$reset: $green$fullScriptPath$reset');
1323 print('Working directory: $cyan$workingDirectory$reset');
1324 print('Script: $green$script$reset');
Ian Hicksondac2ebf2018-06-11 16:28:01 -07001325 if (!printOutput)
1326 print('This is one of the tests that does not normally print output.');
1327 if (skip)
1328 print('This is one of the tests that is normally skipped in this configuration.');
1329 exit(1);
1330 }
Michael Goderbauer76964622017-02-14 10:21:33 -08001331 args.add(script);
Ian Hicksondac2ebf2018-06-11 16:28:01 -07001332 }
Dan Fielda0fc3f32019-06-20 14:35:33 -07001333
1334 args.addAll(tests);
1335
Dan Fieldf67a5292019-03-07 11:31:35 -08001336 if (!shouldProcessOutput) {
Yegorff9082c2020-10-19 20:56:25 -07001337 final OutputMode outputMode = outputChecker == null && printOutput
1338 ? OutputMode.print
1339 : OutputMode.capture;
James Linc02b8052019-08-09 15:10:45 -07001340
Yegorff9082c2020-10-19 20:56:25 -07001341 final CommandResult result = await runCommand(
James Linc02b8052019-08-09 15:10:45 -07001342 flutter,
1343 args,
Dan Field20e0f132019-03-06 13:13:45 -08001344 workingDirectory: workingDirectory,
Dan Fielda3b484d2019-03-07 18:59:43 -08001345 expectNonZeroExit: expectFailure,
James Linc02b8052019-08-09 15:10:45 -07001346 outputMode: outputMode,
Dan Field20e0f132019-03-06 13:13:45 -08001347 skip: skip,
Jonah Williams7bed3782019-03-14 17:12:40 -07001348 environment: environment,
Dan Field20e0f132019-03-06 13:13:45 -08001349 );
James Linc02b8052019-08-09 15:10:45 -07001350
1351 if (outputChecker != null) {
Yegorff9082c2020-10-19 20:56:25 -07001352 final String message = outputChecker(result);
Dan Field24f39d42020-01-02 11:47:28 -08001353 if (message != null)
1354 exitWithError(<String>[message]);
James Linc02b8052019-08-09 15:10:45 -07001355 }
1356 return;
Dan Field20e0f132019-03-06 13:13:45 -08001357 }
Dan Field97a81202019-03-14 08:26:59 -07001358
1359 if (useFlutterTestFormatter) {
Ian Hickson2767d372019-06-03 10:25:54 -07001360 final FlutterCompactFormatter formatter = FlutterCompactFormatter();
Dan Field24f39d42020-01-02 11:47:28 -08001361 Stream<String> testOutput;
1362 try {
1363 testOutput = runAndGetStdout(
1364 flutter,
1365 args,
1366 workingDirectory: workingDirectory,
1367 expectNonZeroExit: expectFailure,
1368 environment: environment,
1369 );
1370 } finally {
1371 formatter.finish();
1372 }
Ian Hickson2767d372019-06-03 10:25:54 -07001373 await _processTestOutput(formatter, testOutput, tableData);
Dan Field97a81202019-03-14 08:26:59 -07001374 } else {
1375 await runCommand(
1376 flutter,
1377 args,
1378 workingDirectory: workingDirectory,
1379 expectNonZeroExit: expectFailure,
Dan Field97a81202019-03-14 08:26:59 -07001380 );
1381 }
Michael Goderbauer76964622017-02-14 10:21:33 -08001382}
1383
Ian Hickson124dc662019-10-18 16:35:39 -07001384Map<String, String> _initGradleEnvironment() {
Jonah Williams1982a5c2019-10-17 19:47:16 -07001385 final String androidSdkRoot = (Platform.environment['ANDROID_HOME']?.isEmpty ?? true)
1386 ? Platform.environment['ANDROID_SDK_ROOT']
1387 : Platform.environment['ANDROID_HOME'];
1388 if (androidSdkRoot == null || androidSdkRoot.isEmpty) {
Jenn Magder6a2bc262020-06-22 18:43:04 -07001389 print('${red}Could not find Android SDK; set ANDROID_SDK_ROOT.$reset');
Ian Hickson124dc662019-10-18 16:35:39 -07001390 exit(1);
Jonah Williams1982a5c2019-10-17 19:47:16 -07001391 }
Ian Hickson124dc662019-10-18 16:35:39 -07001392 return <String, String>{
Jonah Williams1982a5c2019-10-17 19:47:16 -07001393 'ANDROID_HOME': androidSdkRoot,
1394 'ANDROID_SDK_ROOT': androidSdkRoot,
Jonah Williams1982a5c2019-10-17 19:47:16 -07001395 };
Ian Hickson124dc662019-10-18 16:35:39 -07001396}
1397
1398final Map<String, String> gradleEnvironment = _initGradleEnvironment();
1399
Ian Hickson124dc662019-10-18 16:35:39 -07001400void deleteFile(String path) {
1401 // This is technically a race condition but nobody else should be running
1402 // while this script runs, so we should be ok. (Sadly recursive:true does not
1403 // obviate the need for existsSync, at least on Windows.)
1404 final File file = File(path);
1405 if (file.existsSync())
1406 file.deleteSync();
1407}
1408
1409enum CiProviders {
1410 cirrus,
1411 luci,
1412}
1413
1414Future<void> _processTestOutput(
1415 FlutterCompactFormatter formatter,
1416 Stream<String> testOutput,
1417 bq.TabledataResourceApi tableData,
1418) async {
1419 final Timer heartbeat = Timer.periodic(const Duration(seconds: 30), (Timer timer) {
1420 print('Processing...');
1421 });
1422
1423 await testOutput.forEach(formatter.processRawOutput);
1424 heartbeat.cancel();
1425 formatter.finish();
1426 if (tableData == null || formatter.tests.isEmpty) {
1427 return;
1428 }
1429 final bq.TableDataInsertAllRequest request = bq.TableDataInsertAllRequest();
1430 final String authors = await _getAuthors();
1431 request.rows = List<bq.TableDataInsertAllRequestRows>.from(
1432 formatter.tests.map<bq.TableDataInsertAllRequestRows>((TestResult result) =>
1433 bq.TableDataInsertAllRequestRows.fromJson(<String, dynamic> {
1434 'json': <String, dynamic>{
1435 'source': <String, dynamic>{
1436 'provider': ciProviderName,
1437 'url': ciUrl,
1438 'platform': <String, dynamic>{
1439 'os': Platform.operatingSystem,
1440 'version': Platform.operatingSystemVersion,
1441 },
1442 },
1443 'test': <String, dynamic>{
1444 'name': result.name,
1445 'result': result.status.toString(),
1446 'file': result.path,
1447 'line': result.line,
1448 'column': result.column,
1449 'time': result.totalTime,
1450 },
1451 'git': <String, dynamic>{
1452 'author': authors,
1453 'pull_request': prNumber,
1454 'commit': gitHash,
1455 'organization': 'flutter',
1456 'repository': 'flutter',
1457 },
1458 'error': result.status != TestStatus.failed ? null : <String, dynamic>{
1459 'message': result.errorMessage,
1460 'stack_trace': result.stackTrace,
1461 },
1462 'information': result.messages,
1463 },
1464 }),
1465 ),
1466 growable: false,
1467 );
1468 final bq.TableDataInsertAllResponse response = await tableData.insertAll(request, 'flutter-infra', 'tests', 'ci');
1469 if (response.insertErrors != null && response.insertErrors.isNotEmpty) {
1470 print('${red}BigQuery insert errors:');
1471 print(response.toJson());
1472 print(reset);
Dan Field2a644f32019-03-10 07:52:44 -07001473 }
Jonah Williams72605d22019-03-15 19:33:24 -07001474}
Ian Hickson8c6d60b2019-10-02 12:46:51 -07001475
Ian Hickson124dc662019-10-18 16:35:39 -07001476CiProviders get ciProvider {
1477 if (Platform.environment['CIRRUS_CI'] == 'true') {
1478 return CiProviders.cirrus;
1479 }
1480 if (Platform.environment['LUCI_CONTEXT'] != null) {
1481 return CiProviders.luci;
1482 }
1483 return null;
1484}
Ian Hickson8c6d60b2019-10-02 12:46:51 -07001485
Ian Hickson124dc662019-10-18 16:35:39 -07001486String get ciProviderName {
1487 switch (ciProvider) {
1488 case CiProviders.cirrus:
1489 return 'cirrusci';
1490 case CiProviders.luci:
1491 return 'luci';
1492 }
1493 return 'unknown';
1494}
1495
1496int get prNumber {
1497 switch (ciProvider) {
1498 case CiProviders.cirrus:
1499 return Platform.environment['CIRRUS_PR'] == null
1500 ? -1
1501 : int.tryParse(Platform.environment['CIRRUS_PR']);
1502 case CiProviders.luci:
1503 return -1; // LUCI doesn't know about this.
1504 }
1505 return -1;
1506}
1507
1508Future<String> _getAuthors() async {
Ian Hickson124dc662019-10-18 16:35:39 -07001509 final String author = await runAndGetStdout(
1510 'git$exe', <String>['-c', 'log.showSignature=false', 'log', gitHash, '--pretty="%an <%ae>"'],
1511 workingDirectory: flutterRoot,
1512 ).first;
1513 return author;
1514}
1515
1516String get ciUrl {
1517 switch (ciProvider) {
1518 case CiProviders.cirrus:
1519 return 'https://cirrus-ci.com/task/${Platform.environment['CIRRUS_TASK_ID']}';
1520 case CiProviders.luci:
1521 return 'https://ci.chromium.org/p/flutter/g/framework/console'; // TODO(dnfield): can we get a direct link to the actual build?
1522 }
1523 return '';
1524}
1525
1526String get gitHash {
1527 switch(ciProvider) {
1528 case CiProviders.cirrus:
1529 return Platform.environment['CIRRUS_CHANGE_IN_REPO'];
1530 case CiProviders.luci:
1531 return 'HEAD'; // TODO(dnfield): Set this in the env for LUCI.
1532 }
1533 return '';
1534}
1535
stuartmorgan4ea6d3d2020-10-22 13:52:19 -07001536/// Returns the name of the branch being tested.
1537String get branchName {
1538 switch(ciProvider) {
1539 case CiProviders.cirrus:
1540 return Platform.environment['CIRRUS_BRANCH'];
1541 case CiProviders.luci:
1542 return Platform.environment['LUCI_BRANCH'];
1543 }
1544 return '';
1545}
1546
Ian Hickson124dc662019-10-18 16:35:39 -07001547/// Checks the given file's contents to determine if they match the allowed
1548/// pattern for version strings.
1549///
1550/// Returns null if the contents are good. Returns a string if they are bad.
1551/// The string is an error message.
1552Future<String> verifyVersion(File file) async {
Christopher Fujino19c89482020-04-08 12:47:05 -07001553 final RegExp pattern = RegExp(
1554 r'^(\d+)\.(\d+)\.(\d+)((-\d+\.\d+)?\.pre(\.\d+)?)?$');
Ian Hickson124dc662019-10-18 16:35:39 -07001555 final String version = await file.readAsString();
1556 if (!file.existsSync())
1557 return 'The version logic failed to create the Flutter version file.';
1558 if (version == '0.0.0-unknown')
1559 return 'The version logic failed to determine the Flutter version.';
1560 if (!version.contains(pattern))
1561 return 'The version logic generated an invalid version string: "$version".';
1562 return null;
1563}
1564
Jenn Magderfab03bd2020-05-11 12:49:18 -07001565/// Parse (zero-)index-named subshards and equally distribute [tests]
1566/// between them. Last shard should end in "_last" to catch mismatches
1567/// between `.cirrus.yml` and `test.dart`. See [selectShard] for naming details.
1568///
1569/// Examples:
1570/// build_tests-0-linux
1571/// build_tests-1-linux
1572/// build_tests-2_last-linux
1573Future<void> _selectIndexedSubshard(List<ShardRunner> tests, int numberOfShards) async {
1574 final int testsPerShard = tests.length ~/ numberOfShards;
1575 final Map<String, ShardRunner> subshards = <String, ShardRunner>{};
1576
1577 for (int subshard = 0; subshard < numberOfShards; subshard += 1) {
1578 String last = '';
1579 List<ShardRunner> sublist;
1580 if (subshard < numberOfShards - 1) {
1581 sublist = tests.sublist(subshard * testsPerShard, (subshard + 1) * testsPerShard);
1582 } else {
1583 sublist = tests.sublist(subshard * testsPerShard, tests.length);
1584 // We make sure the last shard ends in _last.
1585 last = '_last';
1586 }
1587 subshards['$subshard$last'] = () async {
1588 for (final ShardRunner test in sublist)
1589 await test();
1590 };
1591 }
1592
1593 await selectSubshard(subshards);
1594}
1595
Ian Hickson124dc662019-10-18 16:35:39 -07001596/// If the CIRRUS_TASK_NAME environment variable exists, we use that to determine
Greg Spencera60bf8e2019-11-22 08:43:55 -08001597/// the shard and sub-shard (parsing it in the form shard-subshard-platform, ignoring
Ian Hickson124dc662019-10-18 16:35:39 -07001598/// the platform).
1599///
1600/// However, for local testing you can just set the SHARD and SUBSHARD
1601/// environment variables. For example, to run all the framework tests you can
1602/// just set SHARD=framework_tests. To run specifically the third subshard of
1603/// the Web tests you can set SHARD=web_tests SUBSHARD=2 (it's zero-based).
1604Future<void> selectShard(Map<String, ShardRunner> shards) => _runFromList(shards, 'SHARD', 'shard', 0);
1605Future<void> selectSubshard(Map<String, ShardRunner> subshards) => _runFromList(subshards, 'SUBSHARD', 'subshard', 1);
1606
1607const String CIRRUS_TASK_NAME = 'CIRRUS_TASK_NAME';
1608
1609Future<void> _runFromList(Map<String, ShardRunner> items, String key, String name, int positionInTaskName) async {
1610 String item = Platform.environment[key];
1611 if (item == null && Platform.environment.containsKey(CIRRUS_TASK_NAME)) {
1612 final List<String> parts = Platform.environment[CIRRUS_TASK_NAME].split('-');
1613 assert(positionInTaskName < parts.length);
1614 item = parts[positionInTaskName];
1615 }
1616 if (item == null) {
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +01001617 for (final String currentItem in items.keys) {
Ian Hickson124dc662019-10-18 16:35:39 -07001618 print('$bold$key=$currentItem$reset');
1619 await items[currentItem]();
1620 print('');
1621 }
1622 } else {
Ian Hickson8c6d60b2019-10-02 12:46:51 -07001623 if (!items.containsKey(item)) {
1624 print('${red}Invalid $name: $item$reset');
1625 print('The available ${name}s are: ${items.keys.join(", ")}');
1626 exit(1);
1627 }
1628 print('$bold$key=$item$reset');
1629 await items[item]();
Ian Hickson8c6d60b2019-10-02 12:46:51 -07001630 }
1631}