blob: 5b92184caa8e4a0c2eab10f2f051983bce81e4a1 [file] [log] [blame]
Ian Hickson9ac16682017-06-12 16:52:35 -07001// Copyright 2017 The Chromium 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
Michael Goderbauer76964622017-02-14 10:21:33 -08005import 'dart:async';
Alexandre Ardhuin8c043d02017-02-23 22:37:26 +01006import 'dart:io';
Michael Goderbauer76964622017-02-14 10:21:33 -08007
Dan Field20e0f132019-03-06 13:13:45 -08008import 'package:googleapis/bigquery/v2.dart' as bq;
9import 'package:googleapis_auth/auth_io.dart' as auth;
10import 'package:http/http.dart' as http;
Ian Hicksonaaa0a1c2017-04-13 10:22:41 -070011import 'package:path/path.dart' as path;
Alexander Aprelev391e91c2018-08-30 07:30:25 -070012
Dan Field20e0f132019-03-06 13:13:45 -080013import 'flutter_compact_formatter.dart';
Alexander Aprelev391e91c2018-08-30 07:30:25 -070014import 'run_command.dart';
Michael Goderbauer76964622017-02-14 10:21:33 -080015
Alexandre Ardhuind340e2f2018-10-04 18:44:23 +020016typedef ShardRunner = Future<void> Function();
Todd Volkertbd679262017-06-15 17:54:45 -070017
Ian Hicksonaaa0a1c2017-04-13 10:22:41 -070018final String flutterRoot = path.dirname(path.dirname(path.dirname(path.fromUri(Platform.script))));
19final String flutter = path.join(flutterRoot, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter');
20final String dart = path.join(flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', Platform.isWindows ? 'dart.exe' : 'dart');
21final String pub = path.join(flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', Platform.isWindows ? 'pub.bat' : 'pub');
Greg Spencerf29ecba2017-12-05 14:46:39 -080022final String pubCache = path.join(flutterRoot, '.pub-cache');
Alexander Thomas425bd5a2017-12-09 00:49:29 +010023final List<String> flutterTestArgs = <String>[];
Michael Goderbauer76964622017-02-14 10:21:33 -080024
Dan Field97a81202019-03-14 08:26:59 -070025
26final bool useFlutterTestFormatter = Platform.environment['FLUTTER_TEST_FORMATTER'] == 'true';
27
Jonah Williams55a2ee52019-03-18 15:50:11 -070028final bool noUseBuildRunner = Platform.environment['FLUTTER_TEST_NO_BUILD_RUNNER'] == 'true';
29
Alexandre Ardhuineda03e22018-08-02 12:02:32 +020030const Map<String, ShardRunner> _kShards = <String, ShardRunner>{
Todd Volkertbd679262017-06-15 17:54:45 -070031 'tests': _runTests,
Greg Spencer90a5f462018-07-20 10:21:34 -070032 'tool_tests': _runToolTests,
Dan Field72926bd2018-11-29 09:32:11 -080033 'build_tests': _runBuildTests,
Todd Volkertbd679262017-06-15 17:54:45 -070034 'coverage': _runCoverage,
Dan Field2a644f32019-03-10 07:52:44 -070035 'integration_tests': _runIntegrationTests,
Dan Fieldb484a912019-02-23 09:56:27 -080036 'add2app_test': _runAdd2AppTest,
Todd Volkertbd679262017-06-15 17:54:45 -070037};
38
Alexandre Ardhuineda03e22018-08-02 12:02:32 +020039const Duration _kLongTimeout = Duration(minutes: 45);
40const Duration _kShortTimeout = Duration(minutes: 5);
Todd Volkert58d34932018-04-17 10:00:06 -070041
Alexander Thomas425bd5a2017-12-09 00:49:29 +010042/// When you call this, you can pass additional arguments to pass custom
Michael Goderbauer76964622017-02-14 10:21:33 -080043/// arguments to flutter test. For example, you might want to call this
Alexander Thomas425bd5a2017-12-09 00:49:29 +010044/// script with the parameter --local-engine=host_debug_unopt to
Michael Goderbauer76964622017-02-14 10:21:33 -080045/// use your own build of the engine.
Ian Hickson11c20322017-03-20 16:16:34 -070046///
Alexander Aprelev391e91c2018-08-30 07:30:25 -070047/// To run the tool_tests part, run it with SHARD=tool_tests
Ian Hickson11c20322017-03-20 16:16:34 -070048///
49/// For example:
Alexander Aprelev391e91c2018-08-30 07:30:25 -070050/// SHARD=tool_tests bin/cache/dart-sdk/bin/dart dev/bots/test.dart
Alexander Thomas425bd5a2017-12-09 00:49:29 +010051/// bin/cache/dart-sdk/bin/dart dev/bots/test.dart --local-engine=host_debug_unopt
Alexandre Ardhuind340e2f2018-10-04 18:44:23 +020052Future<void> main(List<String> args) async {
Alexander Thomas425bd5a2017-12-09 00:49:29 +010053 flutterTestArgs.addAll(args);
54
Ian Hickson18c60d32018-01-17 10:17:52 -080055 final String shard = Platform.environment['SHARD'];
56 if (shard != null) {
Ian Hicksondac2ebf2018-06-11 16:28:01 -070057 if (!_kShards.containsKey(shard)) {
58 print('Invalid shard: $shard');
59 print('The available shards are: ${_kShards.keys.join(", ")}');
60 exit(1);
61 }
Ian Hicksona9c13992018-01-29 21:14:41 -080062 print('${bold}SHARD=$shard$reset');
Ian Hickson18c60d32018-01-17 10:17:52 -080063 await _kShards[shard]();
64 } else {
65 for (String currentShard in _kShards.keys) {
66 print('${bold}SHARD=$currentShard$reset');
67 await _kShards[currentShard]();
Ian Hicksona9c13992018-01-29 21:14:41 -080068 print('');
Ian Hickson18c60d32018-01-17 10:17:52 -080069 }
70 }
Todd Volkertbd679262017-06-15 17:54:45 -070071}
Michael Goderbauer76964622017-02-14 10:21:33 -080072
Alexandre Ardhuind340e2f2018-10-04 18:44:23 +020073Future<void> _runSmokeTests() async {
Ian Hicksondac2ebf2018-06-11 16:28:01 -070074 // Verify that the tests actually return failure on failure and success on
75 // success.
Todd Volkertbd679262017-06-15 17:54:45 -070076 final String automatedTests = path.join(flutterRoot, 'dev', 'automated_tests');
Ian Hicksondac2ebf2018-06-11 16:28:01 -070077 // We run the "pass" and "fail" smoke tests first, and alone, because those
78 // are particularly critical and sensitive. If one of these fails, there's no
79 // point even trying the others.
Todd Volkertbd679262017-06-15 17:54:45 -070080 await _runFlutterTest(automatedTests,
81 script: path.join('test_smoke_test', 'pass_test.dart'),
82 printOutput: false,
Todd Volkert58d34932018-04-17 10:00:06 -070083 timeout: _kShortTimeout,
Todd Volkertbd679262017-06-15 17:54:45 -070084 );
85 await _runFlutterTest(automatedTests,
Ian Hicksondac2ebf2018-06-11 16:28:01 -070086 script: path.join('test_smoke_test', 'fail_test.dart'),
Todd Volkertbd679262017-06-15 17:54:45 -070087 expectFailure: true,
88 printOutput: false,
Todd Volkert58d34932018-04-17 10:00:06 -070089 timeout: _kShortTimeout,
Todd Volkertbd679262017-06-15 17:54:45 -070090 );
Ian Hicksondac2ebf2018-06-11 16:28:01 -070091 // We run the timeout tests individually because they are timing-sensitive.
92 await _runFlutterTest(automatedTests,
93 script: path.join('test_smoke_test', 'timeout_pass_test.dart'),
94 expectFailure: false,
95 printOutput: false,
96 timeout: _kShortTimeout,
97 );
Todd Volkertbd679262017-06-15 17:54:45 -070098 await _runFlutterTest(automatedTests,
Ian Hicksondac2ebf2018-06-11 16:28:01 -070099 script: path.join('test_smoke_test', 'timeout_fail_test.dart'),
Todd Volkertbd679262017-06-15 17:54:45 -0700100 expectFailure: true,
101 printOutput: false,
Todd Volkert58d34932018-04-17 10:00:06 -0700102 timeout: _kShortTimeout,
Todd Volkertbd679262017-06-15 17:54:45 -0700103 );
Ian Hicksondac2ebf2018-06-11 16:28:01 -0700104 // We run the remaining smoketests in parallel, because they each take some
105 // time to run (e.g. compiling), so we don't want to run them in series,
106 // especially on 20-core machines...
107 await Future.wait<void>(
108 <Future<void>>[
109 _runFlutterTest(automatedTests,
110 script: path.join('test_smoke_test', 'crash1_test.dart'),
111 expectFailure: true,
112 printOutput: false,
113 timeout: _kShortTimeout,
114 ),
115 _runFlutterTest(automatedTests,
116 script: path.join('test_smoke_test', 'crash2_test.dart'),
117 expectFailure: true,
118 printOutput: false,
119 timeout: _kShortTimeout,
120 ),
121 _runFlutterTest(automatedTests,
122 script: path.join('test_smoke_test', 'syntax_error_test.broken_dart'),
123 expectFailure: true,
124 printOutput: false,
125 timeout: _kShortTimeout,
126 ),
127 _runFlutterTest(automatedTests,
128 script: path.join('test_smoke_test', 'missing_import_test.broken_dart'),
129 expectFailure: true,
130 printOutput: false,
131 timeout: _kShortTimeout,
132 ),
133 _runFlutterTest(automatedTests,
134 script: path.join('test_smoke_test', 'disallow_error_reporter_modification_test.dart'),
135 expectFailure: true,
136 printOutput: false,
137 timeout: _kShortTimeout,
138 ),
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700139 runCommand(flutter,
Ian Hicksondac2ebf2018-06-11 16:28:01 -0700140 <String>['drive', '--use-existing-app', '-t', path.join('test_driver', 'failure.dart')],
141 workingDirectory: path.join(flutterRoot, 'packages', 'flutter_driver'),
Greg Spencerf44f6252018-07-23 15:41:31 -0700142 expectNonZeroExit: true,
Ian Hicksondac2ebf2018-06-11 16:28:01 -0700143 printOutput: false,
144 timeout: _kShortTimeout,
145 ),
146 ],
Todd Volkertbd679262017-06-15 17:54:45 -0700147 );
Michael Goderbauer76964622017-02-14 10:21:33 -0800148
Ian Hickson64e2e002018-01-26 16:59:56 -0800149 // Verify that we correctly generated the version file.
150 await _verifyVersion(path.join(flutterRoot, 'version'));
Greg Spencer90a5f462018-07-20 10:21:34 -0700151}
Ian Hickson64e2e002018-01-26 16:59:56 -0800152
Dan Field20e0f132019-03-06 13:13:45 -0800153Future<bq.BigqueryApi> _getBigqueryApi() async {
Dan Field97a81202019-03-14 08:26:59 -0700154 if (!useFlutterTestFormatter) {
155 return null;
156 }
Dan Field20e0f132019-03-06 13:13:45 -0800157 // TODO(dnfield): How will we do this on LUCI?
158 final String privateKey = Platform.environment['GCLOUD_SERVICE_ACCOUNT_KEY'];
Dan Fieldb9f013c2019-03-10 11:26:17 -0700159 // If we're on Cirrus and a non-collaborator is doing this, we can't get the key.
160 if (privateKey == null || privateKey.isEmpty || privateKey.startsWith('ENCRYPTED[')) {
Dan Field20e0f132019-03-06 13:13:45 -0800161 return null;
162 }
Dan Fieldb9f013c2019-03-10 11:26:17 -0700163 try {
Dan Field97a81202019-03-14 08:26:59 -0700164 final auth.ServiceAccountCredentials accountCredentials = auth.ServiceAccountCredentials(
Dan Fieldb9f013c2019-03-10 11:26:17 -0700165 'flutter-ci-test-reporter@flutter-infra.iam.gserviceaccount.com',
166 auth.ClientId.serviceAccount('114390419920880060881.apps.googleusercontent.com'),
167 '-----BEGIN PRIVATE KEY-----\n$privateKey\n-----END PRIVATE KEY-----\n',
168 );
169 final List<String> scopes = <String>[bq.BigqueryApi.BigqueryInsertdataScope];
170 final http.Client client = await auth.clientViaServiceAccount(accountCredentials, scopes);
171 return bq.BigqueryApi(client);
172 } catch (e) {
173 print('Failed to get BigQuery API client.');
174 print(e);
175 return null;
176 }
Dan Field20e0f132019-03-06 13:13:45 -0800177}
178
Alexandre Ardhuind340e2f2018-10-04 18:44:23 +0200179Future<void> _runToolTests() async {
Dan Field20e0f132019-03-06 13:13:45 -0800180 final bq.BigqueryApi bigqueryApi = await _getBigqueryApi();
Greg Spencer90a5f462018-07-20 10:21:34 -0700181 await _runSmokeTests();
182
Jonah Williams0a2175f2019-03-27 15:24:08 -0700183 // The flutter_tool will currently be snapshotted without asserts. We need
184 // to force it to be regenerated with them enabled.
185 if (!Platform.isWindows) {
186 File(path.join(flutterRoot, 'bin', 'cache', 'flutter_tools.snapshot')).deleteSync();
187 File(path.join(flutterRoot, 'bin', 'cache', 'flutter_tools.stamp')).deleteSync();
188 }
Jonah Williams55a2ee52019-03-18 15:50:11 -0700189 if (noUseBuildRunner) {
190 await _pubRunTest(
191 path.join(flutterRoot, 'packages', 'flutter_tools'),
192 tableData: bigqueryApi?.tabledata,
Jonah Williams0a2175f2019-03-27 15:24:08 -0700193 enableFlutterToolAsserts: !Platform.isWindows,
Jonah Williams55a2ee52019-03-18 15:50:11 -0700194 );
195 } else {
196 await _buildRunnerTest(
197 path.join(flutterRoot, 'packages', 'flutter_tools'),
198 flutterRoot,
199 tableData: bigqueryApi?.tabledata,
Jonah Williams0a2175f2019-03-27 15:24:08 -0700200 enableFlutterToolAsserts: !Platform.isWindows,
Jonah Williams55a2ee52019-03-18 15:50:11 -0700201 );
202 }
Greg Spencer90a5f462018-07-20 10:21:34 -0700203
204 print('${bold}DONE: All tests successful.$reset');
205}
206
Dan Field72926bd2018-11-29 09:32:11 -0800207/// Verifies that AOT, APK, and IPA (if on macOS) builds of some
208/// examples apps finish without crashing. It does not actually
209/// launch the apps. That happens later in the devicelab. This is
210/// just a smoke-test. In particular, this will verify we can build
211/// when there are spaces in the path name for the Flutter SDK and
212/// target app.
213Future<void> _runBuildTests() async {
214 final List<String> paths = <String>[
215 path.join('examples', 'hello_world'),
216 path.join('examples', 'flutter_gallery'),
217 path.join('examples', 'flutter_view'),
218 ];
219 for (String path in paths) {
220 await _flutterBuildAot(path);
221 await _flutterBuildApk(path);
222 await _flutterBuildIpa(path);
223 }
Jonah Williams44b22c72019-03-25 18:47:37 -0700224 await _flutterBuildDart2js(path.join('dev', 'integration_tests', 'web'));
Yegor8d643012018-10-08 12:38:46 -0700225
Dan Field72926bd2018-11-29 09:32:11 -0800226 print('${bold}DONE: All build tests successful.$reset');
Yegor8d643012018-10-08 12:38:46 -0700227}
228
Jonah Williams44b22c72019-03-25 18:47:37 -0700229Future<void> _flutterBuildDart2js(String relativePathToApplication) async {
230 print('Running Dart2JS build tests...');
231 await runCommand(flutter,
232 <String>['build', 'web', '-v'],
233 workingDirectory: path.join(flutterRoot, relativePathToApplication),
234 expectNonZeroExit: false,
235 timeout: _kShortTimeout,
236 );
237 print('Done.');
238}
Jonah Williams9bc56562019-02-14 22:42:30 -0800239
Dan Field72926bd2018-11-29 09:32:11 -0800240Future<void> _flutterBuildAot(String relativePathToApplication) async {
241 print('Running AOT build tests...');
242 await runCommand(flutter,
243 <String>['build', 'aot', '-v'],
Yegor8d643012018-10-08 12:38:46 -0700244 workingDirectory: path.join(flutterRoot, relativePathToApplication),
245 expectNonZeroExit: false,
246 timeout: _kShortTimeout,
247 );
Dan Field72926bd2018-11-29 09:32:11 -0800248 print('Done.');
249}
250
251Future<void> _flutterBuildApk(String relativePathToApplication) async {
Danny Tuppenyc19142d2018-12-17 17:29:09 +0000252 if (
253 (Platform.environment['ANDROID_HOME']?.isEmpty ?? true) &&
254 (Platform.environment['ANDROID_SDK_ROOT']?.isEmpty ?? true)) {
Dan Field72926bd2018-11-29 09:32:11 -0800255 return;
256 }
257 print('Running APK build tests...');
258 await runCommand(flutter,
259 <String>['build', 'apk', '--debug', '-v'],
260 workingDirectory: path.join(flutterRoot, relativePathToApplication),
261 expectNonZeroExit: false,
262 timeout: _kShortTimeout,
263 );
264 print('Done.');
265}
266
267Future<void> _flutterBuildIpa(String relativePathToApplication) async {
268 if (!Platform.isMacOS) {
269 return;
270 }
271 print('Running IPA build tests...');
272 // Install Cocoapods. We don't have these checked in for the examples,
273 // and build ios doesn't take care of it automatically.
274 final File podfile = File(path.join(flutterRoot, relativePathToApplication, 'ios', 'Podfile'));
275 if (podfile.existsSync()) {
276 await runCommand('pod',
277 <String>['install'],
278 workingDirectory: podfile.parent.path,
279 expectNonZeroExit: false,
280 timeout: _kShortTimeout,
281 );
282 }
283 await runCommand(flutter,
284 <String>['build', 'ios', '--no-codesign', '--debug', '-v'],
285 workingDirectory: path.join(flutterRoot, relativePathToApplication),
286 expectNonZeroExit: false,
287 timeout: _kShortTimeout,
288 );
289 print('Done.');
Yegor8d643012018-10-08 12:38:46 -0700290}
291
Dan Fieldb484a912019-02-23 09:56:27 -0800292Future<void> _runAdd2AppTest() async {
293 if (!Platform.isMacOS) {
294 return;
295 }
296 print('Running Add2App iOS integration tests...');
297 final String add2AppDir = path.join(flutterRoot, 'dev', 'integration_tests', 'ios_add2app');
298 await runCommand('./build_and_test.sh',
299 <String>[],
300 workingDirectory: add2AppDir,
301 expectNonZeroExit: false,
302 timeout: _kShortTimeout,
303 );
304 print('Done.');
305}
306
Alexandre Ardhuind340e2f2018-10-04 18:44:23 +0200307Future<void> _runTests() async {
Dan Field20e0f132019-03-06 13:13:45 -0800308 final bq.BigqueryApi bigqueryApi = await _getBigqueryApi();
Greg Spencer90a5f462018-07-20 10:21:34 -0700309 await _runSmokeTests();
310
Dan Field20e0f132019-03-06 13:13:45 -0800311 await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter'), tableData: bigqueryApi?.tabledata);
Jacob Richmanb21fb8c2018-10-23 14:10:53 -0700312 // Only packages/flutter/test/widgets/widget_inspector_test.dart really
313 // needs to be run with --track-widget-creation but it is nice to run
314 // all of the tests in package:flutter with the flag to ensure that
315 // the Dart kernel transformer triggered by the flag does not break anything.
Dan Field20e0f132019-03-06 13:13:45 -0800316 await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter'), options: <String>['--track-widget-creation'], tableData: bigqueryApi?.tabledata);
317 await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_localizations'), tableData: bigqueryApi?.tabledata);
318 await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_driver'), tableData: bigqueryApi?.tabledata);
319 await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_test'), tableData: bigqueryApi?.tabledata);
320 await _runFlutterTest(path.join(flutterRoot, 'packages', 'fuchsia_remote_debug_protocol'), tableData: bigqueryApi?.tabledata);
321 await _pubRunTest(path.join(flutterRoot, 'dev', 'bots'), tableData: bigqueryApi?.tabledata);
322 await _pubRunTest(path.join(flutterRoot, 'dev', 'devicelab'), tableData: bigqueryApi?.tabledata);
323 await _pubRunTest(path.join(flutterRoot, 'dev', 'snippets'), tableData: bigqueryApi?.tabledata);
324 await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing'), tableData: bigqueryApi?.tabledata);
325 await _runFlutterTest(path.join(flutterRoot, 'dev', 'manual_tests'), tableData: bigqueryApi?.tabledata);
326 await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'vitool'), tableData: bigqueryApi?.tabledata);
327 await _runFlutterTest(path.join(flutterRoot, 'examples', 'hello_world'), tableData: bigqueryApi?.tabledata);
328 await _runFlutterTest(path.join(flutterRoot, 'examples', 'layers'), tableData: bigqueryApi?.tabledata);
329 await _runFlutterTest(path.join(flutterRoot, 'examples', 'stocks'), tableData: bigqueryApi?.tabledata);
330 await _runFlutterTest(path.join(flutterRoot, 'examples', 'flutter_gallery'), tableData: bigqueryApi?.tabledata);
Jacob Richmanb21fb8c2018-10-23 14:10:53 -0700331 // Regression test to ensure that code outside of package:flutter can run
332 // with --track-widget-creation.
Dan Field20e0f132019-03-06 13:13:45 -0800333 await _runFlutterTest(path.join(flutterRoot, 'examples', 'flutter_gallery'), options: <String>['--track-widget-creation'], tableData: bigqueryApi?.tabledata);
334 await _runFlutterTest(path.join(flutterRoot, 'examples', 'catalog'), tableData: bigqueryApi?.tabledata);
Jonah Williams7bed3782019-03-14 17:12:40 -0700335 // Smoke test for code generation.
336 await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'codegen'), tableData: bigqueryApi?.tabledata, environment: <String, String>{
337 'FLUTTER_EXPERIMENTAL_BUILD': 'true',
338 });
Todd Volkertbd679262017-06-15 17:54:45 -0700339
340 print('${bold}DONE: All tests successful.$reset');
341}
342
Alexandre Ardhuind340e2f2018-10-04 18:44:23 +0200343Future<void> _runCoverage() async {
Sivacf18d012018-10-19 06:19:24 -0700344 final File coverageFile = File(path.join(flutterRoot, 'packages', 'flutter', 'coverage', 'lcov.info'));
345 if (!coverageFile.existsSync()) {
346 print('${red}Coverage file not found.$reset');
347 print('Expected to find: ${coverageFile.absolute}');
348 print('This file is normally obtained by running `flutter update-packages`.');
349 exit(1);
350 }
351 coverageFile.deleteSync();
352 await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter'),
353 options: const <String>['--coverage'],
354 );
355 if (!coverageFile.existsSync()) {
356 print('${red}Coverage file not found.$reset');
357 print('Expected to find: ${coverageFile.absolute}');
358 print('This file should have been generated by the `flutter test --coverage` script, but was not.');
359 exit(1);
360 }
Todd Volkertbd679262017-06-15 17:54:45 -0700361
Sivacf18d012018-10-19 06:19:24 -0700362 print('${bold}DONE: Coverage collection successful.$reset');
Michael Goderbauer76964622017-02-14 10:21:33 -0800363}
364
Jonah Williamsd91cfff2019-03-01 19:18:38 -0800365Future<void> _buildRunnerTest(
366 String workingDirectory,
367 String flutterRoot, {
Dan Field20e0f132019-03-06 13:13:45 -0800368 String testPath,
369 bool enableFlutterToolAsserts = false,
370 bq.TabledataResourceApi tableData,
371}) async {
Dan Field97a81202019-03-14 08:26:59 -0700372 final List<String> args = <String>['run', 'build_runner', 'test', '--', useFlutterTestFormatter ? '-rjson' : '-rcompact', '-j1'];
Jonah Williamsd91cfff2019-03-01 19:18:38 -0800373 if (!hasColor) {
374 args.add('--no-color');
375 }
376 if (testPath != null) {
377 args.add(testPath);
378 }
379 final Map<String, String> pubEnvironment = <String, String>{
380 'FLUTTER_ROOT': flutterRoot,
381 };
382 if (Directory(pubCache).existsSync()) {
383 pubEnvironment['PUB_CACHE'] = pubCache;
384 }
385 if (enableFlutterToolAsserts) {
386 // If an existing env variable exists append to it, but only if
387 // it doesn't appear to already include enable-asserts.
388 String toolsArgs = Platform.environment['FLUTTER_TOOL_ARGS'] ?? '';
389 if (!toolsArgs.contains('--enable-asserts'))
390 toolsArgs += ' --enable-asserts';
391 pubEnvironment['FLUTTER_TOOL_ARGS'] = toolsArgs.trim();
392 }
Dan Field20e0f132019-03-06 13:13:45 -0800393
Dan Field97a81202019-03-14 08:26:59 -0700394 if (useFlutterTestFormatter) {
395 final FlutterCompactFormatter formatter = FlutterCompactFormatter();
396 final Stream<String> testOutput = runAndGetStdout(
397 pub,
398 args,
399 workingDirectory: workingDirectory,
400 environment: pubEnvironment,
401 beforeExit: formatter.finish
402 );
403 await _processTestOutput(formatter, testOutput, tableData);
404 } else {
405 await runCommand(
406 pub,
407 args,
408 workingDirectory:workingDirectory,
409 environment:pubEnvironment,
410 );
411 }
Jonah Williamsd91cfff2019-03-01 19:18:38 -0800412}
413
Alexandre Ardhuind340e2f2018-10-04 18:44:23 +0200414Future<void> _pubRunTest(
Todd Volkert1ada1322017-03-09 12:58:31 -0800415 String workingDirectory, {
416 String testPath,
Alexandre Ardhuin387f8852019-03-01 08:17:55 +0100417 bool enableFlutterToolAsserts = false,
Dan Field20e0f132019-03-06 13:13:45 -0800418 bq.TabledataResourceApi tableData,
419}) async {
Dan Field97a81202019-03-14 08:26:59 -0700420 final List<String> args = <String>['run', 'test', useFlutterTestFormatter ? '-rjson' : '-rcompact', '-j1'];
Ian Hicksone3427552018-06-04 15:22:19 -0700421 if (!hasColor)
422 args.add('--no-color');
Todd Volkert1ada1322017-03-09 12:58:31 -0800423 if (testPath != null)
424 args.add(testPath);
Leaf Petersenab874da2018-01-10 14:31:28 -0800425 final Map<String, String> pubEnvironment = <String, String>{};
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200426 if (Directory(pubCache).existsSync()) {
Greg Spencerf29ecba2017-12-05 14:46:39 -0800427 pubEnvironment['PUB_CACHE'] = pubCache;
428 }
Danny Tuppenydc5a5c12018-08-23 20:49:59 +0100429 if (enableFlutterToolAsserts) {
430 // If an existing env variable exists append to it, but only if
431 // it doesn't appear to already include enable-asserts.
432 String toolsArgs = Platform.environment['FLUTTER_TOOL_ARGS'] ?? '';
433 if (!toolsArgs.contains('--enable-asserts'))
434 toolsArgs += ' --enable-asserts';
435 pubEnvironment['FLUTTER_TOOL_ARGS'] = toolsArgs.trim();
436 }
Dan Field97a81202019-03-14 08:26:59 -0700437 if (useFlutterTestFormatter) {
438 final FlutterCompactFormatter formatter = FlutterCompactFormatter();
439 final Stream<String> testOutput = runAndGetStdout(
440 pub,
441 args,
442 workingDirectory: workingDirectory,
443 beforeExit: formatter.finish,
444 );
445 await _processTestOutput(formatter, testOutput, tableData);
446 } else {
447 await runCommand(
448 pub,
449 args,
450 workingDirectory:workingDirectory,
451 );
452 }
Dan Field20e0f132019-03-06 13:13:45 -0800453}
454
455enum CiProviders {
456 cirrus,
457 luci,
458}
459
460CiProviders _getCiProvider() {
461 if (Platform.environment['CIRRUS_CI'] == 'true') {
462 return CiProviders.cirrus;
463 }
464 if (Platform.environment['LUCI_CONTEXT'] != null) {
465 return CiProviders.luci;
466 }
467 return null;
468}
469
470String _getCiProviderName() {
471 switch(_getCiProvider()) {
472 case CiProviders.cirrus:
473 return 'cirrusci';
474 case CiProviders.luci:
475 return 'luci';
476 }
477 return 'unknown';
478}
479
480int _getPrNumber() {
481 switch(_getCiProvider()) {
482 case CiProviders.cirrus:
Dan Fielde5b1ed72019-03-06 14:23:37 -0800483 return Platform.environment['CIRRUS_PR'] == null
484 ? -1
485 : int.tryParse(Platform.environment['CIRRUS_PR']);
Dan Field20e0f132019-03-06 13:13:45 -0800486 case CiProviders.luci:
487 return -1; // LUCI doesn't know about this.
488 }
489 return -1;
490}
491
492Future<String> _getAuthors() async {
493 final String exe = Platform.isWindows ? '.exe' : '';
494 final String author = await runAndGetStdout(
495 'git$exe', <String>['log', _getGitHash(), '--pretty="%an <%ae>"'],
496 workingDirectory: flutterRoot,
497 ).first;
498 return author;
499}
500
501String _getCiUrl() {
502 switch(_getCiProvider()) {
503 case CiProviders.cirrus:
504 return 'https://cirrus-ci.com/task/${Platform.environment['CIRRUS_TASK_ID']}';
505 case CiProviders.luci:
506 return 'https://ci.chromium.org/p/flutter/g/framework/console'; // TODO(dnfield): can we get a direct link to the actual build?
507 }
508 return '';
509}
510
511String _getGitHash() {
512 switch(_getCiProvider()) {
513 case CiProviders.cirrus:
514 return Platform.environment['CIRRUS_CHANGE_IN_REPO'];
515 case CiProviders.luci:
516 return 'HEAD'; // TODO(dnfield): Set this in the env for LUCI.
517 }
518 return '';
519}
520
Dan Fieldf67a5292019-03-07 11:31:35 -0800521Future<void> _processTestOutput(
522 FlutterCompactFormatter formatter,
523 Stream<String> testOutput,
524 bq.TabledataResourceApi tableData,
525) async {
Dan Field3af88c52019-03-08 16:12:27 -0800526 final Timer heartbeat = Timer.periodic(const Duration(seconds: 30), (Timer timer) {
527 print('Processing...');
528 });
529
Dan Field20e0f132019-03-06 13:13:45 -0800530 await testOutput.forEach(formatter.processRawOutput);
Dan Field3af88c52019-03-08 16:12:27 -0800531 heartbeat.cancel();
Dan Fieldf67a5292019-03-07 11:31:35 -0800532 formatter.finish();
Dan Field20e0f132019-03-06 13:13:45 -0800533 if (tableData == null || formatter.tests.isEmpty) {
534 return;
535 }
536 final bq.TableDataInsertAllRequest request = bq.TableDataInsertAllRequest();
537 final String authors = await _getAuthors();
538 request.rows = List<bq.TableDataInsertAllRequestRows>.from(
539 formatter.tests.map<bq.TableDataInsertAllRequestRows>((TestResult result) =>
540 bq.TableDataInsertAllRequestRows.fromJson(<String, dynamic> {
541 'json': <String, dynamic>{
542 'source': <String, dynamic>{
543 'provider': _getCiProviderName(),
544 'url': _getCiUrl(),
545 'platform': <String, dynamic>{
546 'os': Platform.operatingSystem,
547 'version': Platform.operatingSystemVersion,
548 },
549 },
550 'test': <String, dynamic>{
551 'name': result.name,
552 'result': result.status.toString(),
553 'file': result.path,
554 'line': result.line,
555 'column': result.column,
556 'time': result.totalTime,
557 },
558 'git': <String, dynamic>{
559 'author': authors,
560 'pull_request': _getPrNumber(),
561 'commit': _getGitHash(),
562 'organization': 'flutter',
563 'repository': 'flutter',
564 },
565 'error': result.status != TestStatus.failed ? null : <String, dynamic>{
566 'message': result.errorMessage,
567 'stack_trace': result.stackTrace,
568 },
569 'information': result.messages,
570 },
571 }),
572 ),
573 growable: false,
574 );
575 final bq.TableDataInsertAllResponse response = await tableData.insertAll(request, 'flutter-infra', 'tests', 'ci');
576 if (response.insertErrors != null && response.insertErrors.isNotEmpty) {
577 print('${red}BigQuery insert errors:');
578 print(response.toJson());
579 print(reset);
580 }
Todd Volkert1ada1322017-03-09 12:58:31 -0800581}
582
Yegorf4f20c22017-09-22 12:26:47 -0700583class EvalResult {
584 EvalResult({
585 this.stdout,
586 this.stderr,
Greg Spencer1a414992018-07-25 15:19:57 -0700587 this.exitCode = 0,
Yegorf4f20c22017-09-22 12:26:47 -0700588 });
589
590 final String stdout;
591 final String stderr;
Greg Spencer1a414992018-07-25 15:19:57 -0700592 final int exitCode;
Yegorf4f20c22017-09-22 12:26:47 -0700593}
594
Alexandre Ardhuind340e2f2018-10-04 18:44:23 +0200595Future<void> _runFlutterTest(String workingDirectory, {
Todd Volkert58d34932018-04-17 10:00:06 -0700596 String script,
Alexandre Ardhuin09276be2018-06-05 08:50:40 +0200597 bool expectFailure = false,
598 bool printOutput = true,
599 List<String> options = const <String>[],
600 bool skip = false,
601 Duration timeout = _kLongTimeout,
Dan Field20e0f132019-03-06 13:13:45 -0800602 bq.TabledataResourceApi tableData,
Jonah Williams7bed3782019-03-14 17:12:40 -0700603 Map<String, String> environment,
Dan Field20e0f132019-03-06 13:13:45 -0800604}) async {
Chris Bracken6c97dd22017-03-03 18:06:08 -0800605 final List<String> args = <String>['test']..addAll(options);
Ian Hicksonaaa0a1c2017-04-13 10:22:41 -0700606 if (flutterTestArgs != null && flutterTestArgs.isNotEmpty)
Alexander Thomas425bd5a2017-12-09 00:49:29 +0100607 args.addAll(flutterTestArgs);
Dan Field20e0f132019-03-06 13:13:45 -0800608
Dan Field97a81202019-03-14 08:26:59 -0700609 final bool shouldProcessOutput = useFlutterTestFormatter && !expectFailure && !options.contains('--coverage');
Dan Fieldf67a5292019-03-07 11:31:35 -0800610 if (shouldProcessOutput) {
Dan Field20e0f132019-03-06 13:13:45 -0800611 args.add('--machine');
612 }
613
Ian Hicksondac2ebf2018-06-11 16:28:01 -0700614 if (script != null) {
615 final String fullScriptPath = path.join(workingDirectory, script);
616 if (!FileSystemEntity.isFileSync(fullScriptPath)) {
617 print('Could not find test: $fullScriptPath');
618 print('Working directory: $workingDirectory');
619 print('Script: $script');
620 if (!printOutput)
621 print('This is one of the tests that does not normally print output.');
622 if (skip)
623 print('This is one of the tests that is normally skipped in this configuration.');
624 exit(1);
625 }
Michael Goderbauer76964622017-02-14 10:21:33 -0800626 args.add(script);
Ian Hicksondac2ebf2018-06-11 16:28:01 -0700627 }
Dan Fieldf67a5292019-03-07 11:31:35 -0800628 if (!shouldProcessOutput) {
Dan Field20e0f132019-03-06 13:13:45 -0800629 return runCommand(flutter, args,
630 workingDirectory: workingDirectory,
Dan Fielda3b484d2019-03-07 18:59:43 -0800631 expectNonZeroExit: expectFailure,
Dan Field20e0f132019-03-06 13:13:45 -0800632 printOutput: printOutput,
633 skip: skip,
634 timeout: timeout,
Jonah Williams7bed3782019-03-14 17:12:40 -0700635 environment: environment,
Dan Field20e0f132019-03-06 13:13:45 -0800636 );
637 }
Dan Field97a81202019-03-14 08:26:59 -0700638
639 if (useFlutterTestFormatter) {
Dan Fieldf67a5292019-03-07 11:31:35 -0800640 final FlutterCompactFormatter formatter = FlutterCompactFormatter();
Dan Field97a81202019-03-14 08:26:59 -0700641 final Stream<String> testOutput = runAndGetStdout(
642 flutter,
643 args,
Adam Bartha44d3b72017-02-16 20:40:07 -0800644 workingDirectory: workingDirectory,
Greg Spencerf44f6252018-07-23 15:41:31 -0700645 expectNonZeroExit: expectFailure,
Todd Volkert58d34932018-04-17 10:00:06 -0700646 timeout: timeout,
Dan Fieldf67a5292019-03-07 11:31:35 -0800647 beforeExit: formatter.finish,
Jonah Williams7bed3782019-03-14 17:12:40 -0700648 environment: environment,
Michael Goderbauer76964622017-02-14 10:21:33 -0800649 );
Dan Fieldf67a5292019-03-07 11:31:35 -0800650 await _processTestOutput(formatter, testOutput, tableData);
Dan Field97a81202019-03-14 08:26:59 -0700651 } else {
652 await runCommand(
653 flutter,
654 args,
655 workingDirectory: workingDirectory,
656 expectNonZeroExit: expectFailure,
657 timeout: timeout,
658 );
659 }
Michael Goderbauer76964622017-02-14 10:21:33 -0800660}
661
Alexandre Ardhuind340e2f2018-10-04 18:44:23 +0200662Future<void> _verifyVersion(String filename) async {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200663 if (!File(filename).existsSync()) {
Greg Spencer977da4f2018-08-24 14:06:25 -0700664 print('$redLine');
Ian Hickson64e2e002018-01-26 16:59:56 -0800665 print('The version logic failed to create the Flutter version file.');
Greg Spencer977da4f2018-08-24 14:06:25 -0700666 print('$redLine');
Ian Hickson64e2e002018-01-26 16:59:56 -0800667 exit(1);
668 }
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200669 final String version = await File(filename).readAsString();
Ian Hickson64e2e002018-01-26 16:59:56 -0800670 if (version == '0.0.0-unknown') {
Greg Spencer977da4f2018-08-24 14:06:25 -0700671 print('$redLine');
Ian Hickson64e2e002018-01-26 16:59:56 -0800672 print('The version logic failed to determine the Flutter version.');
Greg Spencer977da4f2018-08-24 14:06:25 -0700673 print('$redLine');
Ian Hickson64e2e002018-01-26 16:59:56 -0800674 exit(1);
675 }
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200676 final RegExp pattern = RegExp(r'^[0-9]+\.[0-9]+\.[0-9]+(-pre\.[0-9]+)?$');
Ian Hickson64e2e002018-01-26 16:59:56 -0800677 if (!version.contains(pattern)) {
Greg Spencer977da4f2018-08-24 14:06:25 -0700678 print('$redLine');
Ian Hickson64e2e002018-01-26 16:59:56 -0800679 print('The version logic generated an invalid version string.');
Greg Spencer977da4f2018-08-24 14:06:25 -0700680 print('$redLine');
Ian Hickson64e2e002018-01-26 16:59:56 -0800681 exit(1);
682 }
Vyacheslav Egorov71b2cfb2018-03-16 20:29:38 +0100683}
Dan Field2a644f32019-03-10 07:52:44 -0700684
685Future<void> _runIntegrationTests() async {
686 print('Platform env vars:');
687
688 await _runDevicelabTest('dartdocs');
689
690 if (Platform.isLinux) {
691 await _runDevicelabTest('flutter_create_offline_test_linux');
692 } else if (Platform.isWindows) {
693 await _runDevicelabTest('flutter_create_offline_test_windows');
694 } else if (Platform.isMacOS) {
695 await _runDevicelabTest('flutter_create_offline_test_mac');
696 await _runDevicelabTest('module_test_ios');
697 }
698 await _integrationTestsAndroidSdk();
699}
700
701Future<void> _runDevicelabTest(String testName, {Map<String, String> env}) async {
702 await runCommand(
703 dart,
704 <String>['bin/run.dart', '-t', testName],
705 workingDirectory: path.join(flutterRoot, 'dev', 'devicelab'),
706 environment: env,
707 );
708}
709
710Future<void> _integrationTestsAndroidSdk() async {
711 final String androidSdkRoot = (Platform.environment['ANDROID_HOME']?.isEmpty ?? true)
712 ? Platform.environment['ANDROID_SDK_ROOT']
713 : Platform.environment['ANDROID_HOME'];
714 if (androidSdkRoot == null || androidSdkRoot.isEmpty) {
715 print('No Android SDK detected, skipping Android Integration Tests');
716 return;
717 }
718
719 final Map<String, String> env = <String, String> {
720 'ANDROID_HOME': androidSdkRoot,
721 'ANDROID_SDK_ROOT': androidSdkRoot,
722 };
723
724 // TODO(dnfield): gradlew is crashing on the cirrus image and it's not clear why.
725 if (!Platform.isWindows) {
726 await _runDevicelabTest('gradle_plugin_test', env: env);
727 await _runDevicelabTest('module_test', env: env);
728 }
729 // note: this also covers plugin_test_win as long as Windows has an Android SDK available.
730 await _runDevicelabTest('plugin_test', env: env);
Jonah Williams72605d22019-03-15 19:33:24 -0700731}