| 1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | // Runs the tests for the flutter/flutter repository. |
| 6 | // |
| 7 | // By default, test output is filtered and only errors are shown. (If a |
| 8 | // particular test takes longer than _quietTimeout in utils.dart, the output is |
| 9 | // shown then also, in case something has hung.) |
| 10 | // |
| 11 | // --verbose stops the output cleanup and just outputs everything verbatim. |
| 12 | // |
| 13 | // By default, errors are non-fatal; all tests are executed and the output |
| 14 | // ends with a summary of the errors that were detected. |
| 15 | // |
| 16 | // Exit code is 1 if there was an error. |
| 17 | // |
| 18 | // --abort-on-error causes the script to exit immediately when hitting an error. |
| 19 | // |
| 20 | // By default, all tests are run. However, the tests support being split by |
| 21 | // shard and subshard. Inspect the code to see what shards and subshards are |
| 22 | // supported, or run with `--dry-run` to get a list of tests that _would_ have |
| 23 | // been executed. |
| 24 | // |
| 25 | // For local testing you can just set the SHARD and SUBSHARD environment |
| 26 | // variables. For example, to run all the framework tests you can just set |
| 27 | // SHARD=framework_tests. Some shards support named subshards, like |
| 28 | // SHARD=framework_tests SUBSHARD=widgets. Others support arbitrary numbered |
| 29 | // subsharding, like SHARD=build_tests SUBSHARD=1_2 (where 1_2 means "one of |
| 30 | // two" as in run the first half of the tests). |
| 31 | // |
| 32 | // So for example to run specifically the third subshard of the Web tests you |
| 33 | // would set SHARD=web_tests SUBSHARD=2 (it's zero-based). |
| 34 | // |
| 35 | // By default, where supported, tests within a shard are executed in a random |
| 36 | // order to (eventually) catch inter-test dependencies. |
| 37 | // |
| 38 | // --test-randomize-ordering-seed= sets the shuffle seed for reproducing runs. |
| 39 | // |
| 40 | // All other arguments are treated as arguments to pass to the flutter tool when |
| 41 | // running tests. |
| 42 | |
| 43 | import 'dart:convert'; |
| 44 | import 'dart:core' as system show print; |
| 45 | import 'dart:core' hide print; |
| 46 | import 'dart:io' as system show exit; |
| 47 | import 'dart:io' hide exit; |
| 48 | import 'dart:math' as math; |
| 49 | |
| 50 | import 'package:path/path.dart' as path; |
| 51 | |
| 52 | import 'run_command.dart'; |
| 53 | import 'suite_runners/run_add_to_app_life_cycle_tests.dart'; |
| 54 | import 'suite_runners/run_analyze_tests.dart'; |
| 55 | import 'suite_runners/run_android_engine_tests.dart'; |
| 56 | import 'suite_runners/run_android_java11_integration_tool_tests.dart'; |
| 57 | import 'suite_runners/run_android_preview_integration_tool_tests.dart'; |
| 58 | import 'suite_runners/run_customer_testing_tests.dart'; |
| 59 | import 'suite_runners/run_docs_tests.dart'; |
| 60 | import 'suite_runners/run_flutter_packages_tests.dart'; |
| 61 | import 'suite_runners/run_framework_coverage_tests.dart'; |
| 62 | import 'suite_runners/run_framework_tests.dart'; |
| 63 | import 'suite_runners/run_fuchsia_precache.dart'; |
| 64 | import 'suite_runners/run_skp_generator_tests.dart'; |
| 65 | import 'suite_runners/run_test_harness_tests.dart'; |
| 66 | import 'suite_runners/run_verify_binaries_codesigned_tests.dart'; |
| 67 | import 'suite_runners/run_web_tests.dart'; |
| 68 | import 'utils.dart'; |
| 69 | |
| 70 | typedef ShardRunner = Future<void> Function(); |
| 71 | |
| 72 | /// Environment variables to override the local engine when running `pub test`, |
| 73 | /// if such flags are provided to `test.dart`. |
| 74 | final Map<String, String> localEngineEnv = <String, String>{}; |
| 75 | |
| 76 | /// When you call this, you can pass additional arguments to pass custom |
| 77 | /// arguments to flutter test. For example, you might want to call this |
| 78 | /// script with the parameter --local-engine=host_debug_unopt to |
| 79 | /// use your own build of the engine. |
| 80 | /// |
| 81 | /// To run the tool_tests part, run it with SHARD=tool_tests |
| 82 | /// |
| 83 | /// Examples: |
| 84 | /// SHARD=tool_tests bin/cache/dart-sdk/bin/dart dev/bots/test.dart |
| 85 | /// bin/cache/dart-sdk/bin/dart dev/bots/test.dart --local-engine=host_debug_unopt --local-engine-host=host_debug_unopt |
| 86 | Future<void> main(List<String> args) async { |
| 87 | try { |
| 88 | printProgress('STARTING ANALYSIS' ); |
| 89 | bool dryRunArgSet = false; |
| 90 | for (final String arg in args) { |
| 91 | if (arg.startsWith('--local-engine=' )) { |
| 92 | localEngineEnv['FLUTTER_LOCAL_ENGINE' ] = arg.substring('--local-engine=' .length); |
| 93 | flutterTestArgs.add(arg); |
| 94 | } else if (arg.startsWith('--local-engine-host=' )) { |
| 95 | localEngineEnv['FLUTTER_LOCAL_ENGINE_HOST' ] = arg.substring('--local-engine-host=' .length); |
| 96 | flutterTestArgs.add(arg); |
| 97 | } else if (arg.startsWith('--local-engine-src-path=' )) { |
| 98 | localEngineEnv['FLUTTER_LOCAL_ENGINE_SRC_PATH' ] = arg.substring( |
| 99 | '--local-engine-src-path=' .length, |
| 100 | ); |
| 101 | flutterTestArgs.add(arg); |
| 102 | } else if (arg.startsWith('--test-randomize-ordering-seed=' )) { |
| 103 | shuffleSeed = arg.substring('--test-randomize-ordering-seed=' .length); |
| 104 | } else if (arg.startsWith('--verbose' )) { |
| 105 | print = (Object? message) { |
| 106 | system.print(message); |
| 107 | }; |
| 108 | } else if (arg.startsWith('--abort-on-error' )) { |
| 109 | onError = () { |
| 110 | system.exit(1); |
| 111 | }; |
| 112 | } else if (arg == '--dry-run' ) { |
| 113 | dryRunArgSet = true; |
| 114 | printProgress('--dry-run enabled. Tests will not actually be executed.' ); |
| 115 | } else { |
| 116 | flutterTestArgs.add(arg); |
| 117 | } |
| 118 | } |
| 119 | if (dryRunArgSet) { |
| 120 | enableDryRun(); |
| 121 | } |
| 122 | final WebTestsSuite webTestsSuite = WebTestsSuite(flutterTestArgs); |
| 123 | await selectShard(<String, ShardRunner>{ |
| 124 | 'add_to_app_life_cycle_tests' : addToAppLifeCycleRunner, |
| 125 | 'build_tests' : _runBuildTests, |
| 126 | 'framework_coverage' : frameworkCoverageRunner, |
| 127 | 'framework_tests' : frameworkTestsRunner, |
| 128 | 'tool_tests' : _runToolTests, |
| 129 | 'web_tool_tests' : _runWebToolTests, |
| 130 | 'tool_integration_tests' : _runIntegrationToolTests, |
| 131 | 'android_preview_tool_integration_tests' : androidPreviewIntegrationToolTestsRunner, |
| 132 | 'android_java11_tool_integration_tests' : androidJava11IntegrationToolTestsRunner, |
| 133 | 'tool_host_cross_arch_tests' : _runToolHostCrossArchTests, |
| 134 | // All the unit/widget tests run using `flutter test --platform=chrome` |
| 135 | 'web_canvaskit_tests' : webTestsSuite.runWebCanvasKitUnitTests, |
| 136 | // All the unit/widget tests run using `flutter test --platform=chrome --wasm` |
| 137 | 'web_skwasm_tests' : webTestsSuite.runWebSkwasmUnitTests, |
| 138 | // All web integration tests |
| 139 | 'web_long_running_tests' : webTestsSuite.webLongRunningTestsRunner, |
| 140 | 'android_engine_vulkan_tests' : () => |
| 141 | runAndroidEngineTests(impellerBackend: ImpellerBackend.vulkan), |
| 142 | 'android_engine_opengles_tests' : () => |
| 143 | runAndroidEngineTests(impellerBackend: ImpellerBackend.opengles), |
| 144 | 'flutter_plugins' : flutterPackagesRunner, |
| 145 | 'skp_generator' : skpGeneratorTestsRunner, |
| 146 | 'customer_testing' : customerTestingRunner, |
| 147 | 'analyze' : analyzeRunner, |
| 148 | 'fuchsia_precache' : fuchsiaPrecacheRunner, |
| 149 | 'snippets' : _runSnippetsTests, |
| 150 | 'docs' : docsRunner, |
| 151 | 'verify_binaries_codesigned' : verifyCodesignedTestRunner, |
| 152 | 'verify_binaries_pre_codesigned' : verifyPreCodesignedTestRunner, |
| 153 | kTestHarnessShardName: |
| 154 | testHarnessTestsRunner, // Used for testing this script; also run as part of SHARD=framework_tests, SUBSHARD=misc. |
| 155 | }); |
| 156 | } catch (error, stackTrace) { |
| 157 | foundError(<String>[ |
| 158 | 'UNEXPECTED ERROR!' , |
| 159 | error.toString(), |
| 160 | ...stackTrace.toString().split('\n' ), |
| 161 | 'The test.dart script should be corrected to catch this error and call foundError().' , |
| 162 | ' ${yellow}Some tests are likely to have been skipped. $reset' , |
| 163 | ]); |
| 164 | system.exit(255); |
| 165 | } |
| 166 | if (hasError) { |
| 167 | reportErrorsAndExit(' ${bold}Test failed. $reset' ); |
| 168 | } |
| 169 | reportSuccessAndExit(' ${bold}Test successful. $reset' ); |
| 170 | } |
| 171 | |
| 172 | final String _toolsPath = path.join(flutterRoot, 'packages' , 'flutter_tools' ); |
| 173 | |
| 174 | Future<void> _runGeneralToolTests() async { |
| 175 | await runDartTest( |
| 176 | _toolsPath, |
| 177 | testPaths: <String>[path.join('test' , 'general.shard' )], |
| 178 | enableFlutterToolAsserts: false, |
| 179 | |
| 180 | // Detect unit test time regressions (poor time delay handling, etc). |
| 181 | // This overrides the 15 minute default for tools tests. |
| 182 | // See the README.md and dart_test.yaml files in the flutter_tools package. |
| 183 | perTestTimeout: const Duration(seconds: 2), |
| 184 | ); |
| 185 | } |
| 186 | |
| 187 | Future<void> _runCommandsToolTests() async { |
| 188 | await runDartTest( |
| 189 | _toolsPath, |
| 190 | forceSingleCore: true, |
| 191 | testPaths: <String>[path.join('test' , 'commands.shard' )], |
| 192 | ); |
| 193 | } |
| 194 | |
| 195 | Future<void> _runWebToolTests() async { |
| 196 | final List<File> allFiles = Directory( |
| 197 | path.join(_toolsPath, 'test' , 'web.shard' ), |
| 198 | ).listSync(recursive: true).whereType<File>().toList(); |
| 199 | final List<String> allTests = <String>[]; |
| 200 | for (final File file in allFiles) { |
| 201 | if (file.path.endsWith('_test.dart' )) { |
| 202 | allTests.add(file.path); |
| 203 | } |
| 204 | } |
| 205 | await runDartTest( |
| 206 | _toolsPath, |
| 207 | forceSingleCore: true, |
| 208 | testPaths: selectIndexOfTotalSubshard<String>(allTests), |
| 209 | includeLocalEngineEnv: true, |
| 210 | ); |
| 211 | } |
| 212 | |
| 213 | Future<void> _runToolHostCrossArchTests() { |
| 214 | return runDartTest( |
| 215 | _toolsPath, |
| 216 | // These are integration tests |
| 217 | forceSingleCore: true, |
| 218 | testPaths: <String>[path.join('test' , 'host_cross_arch.shard' )], |
| 219 | ); |
| 220 | } |
| 221 | |
| 222 | Future<void> _runIntegrationToolTests() async { |
| 223 | final List<String> allTests = Directory(path.join(_toolsPath, 'test' , 'integration.shard' )) |
| 224 | .listSync(recursive: true) |
| 225 | .whereType<File>() |
| 226 | .map<String>((FileSystemEntity entry) => path.relative(entry.path, from: _toolsPath)) |
| 227 | .where((String testPath) => path.basename(testPath).endsWith('_test.dart' )) |
| 228 | .toList(); |
| 229 | |
| 230 | await runDartTest( |
| 231 | _toolsPath, |
| 232 | forceSingleCore: true, |
| 233 | testPaths: selectIndexOfTotalSubshard<String>(allTests), |
| 234 | collectMetrics: true, |
| 235 | ); |
| 236 | } |
| 237 | |
| 238 | Future<void> _runWidgetPreviewScaffoldToolTests() async { |
| 239 | await runFlutterTest( |
| 240 | path.join(_toolsPath, 'test' , 'widget_preview_scaffold.shard' , 'widget_preview_scaffold' ), |
| 241 | ); |
| 242 | } |
| 243 | |
| 244 | Future<void> _runToolTests() async { |
| 245 | await selectSubshard(<String, ShardRunner>{ |
| 246 | 'general' : _runGeneralToolTests, |
| 247 | 'commands' : _runCommandsToolTests, |
| 248 | 'widget_preview_scaffold' : _runWidgetPreviewScaffoldToolTests, |
| 249 | }); |
| 250 | } |
| 251 | |
| 252 | Future<void> _runSnippetsTests() async { |
| 253 | final String snippetsPath = path.join(flutterRoot, 'dev' , 'snippets' ); |
| 254 | final List<String> allTests = Directory(path.join(snippetsPath, 'test' )) |
| 255 | .listSync(recursive: true) |
| 256 | .whereType<File>() |
| 257 | .map<String>((FileSystemEntity entry) => path.relative(entry.path, from: _toolsPath)) |
| 258 | .where((String testPath) => path.basename(testPath).endsWith('_test.dart' )) |
| 259 | .toList(); |
| 260 | |
| 261 | await runDartTest( |
| 262 | snippetsPath, |
| 263 | forceSingleCore: true, |
| 264 | testPaths: selectIndexOfTotalSubshard<String>(allTests), |
| 265 | collectMetrics: true, |
| 266 | ); |
| 267 | } |
| 268 | |
| 269 | Future<void> runForbiddenFromReleaseTests() async { |
| 270 | // Build a release APK to get the snapshot json. |
| 271 | final Directory tempDirectory = Directory.systemTemp.createTempSync('flutter_forbidden_imports.' ); |
| 272 | final List<String> command = <String>[ |
| 273 | 'build' , |
| 274 | 'apk' , |
| 275 | '--target-platform' , |
| 276 | 'android-arm64' , |
| 277 | '--release' , |
| 278 | '--analyze-size' , |
| 279 | '--code-size-directory' , |
| 280 | tempDirectory.path, |
| 281 | '-v' , |
| 282 | ]; |
| 283 | |
| 284 | await runCommand( |
| 285 | flutter, |
| 286 | command, |
| 287 | workingDirectory: path.join(flutterRoot, 'examples' , 'hello_world' ), |
| 288 | ); |
| 289 | |
| 290 | // First, a smoke test. |
| 291 | final List<String> smokeTestArgs = <String>[ |
| 292 | path.join(flutterRoot, 'dev' , 'forbidden_from_release_tests' , 'bin' , 'main.dart' ), |
| 293 | '--snapshot' , |
| 294 | path.join(tempDirectory.path, 'snapshot.arm64-v8a.json' ), |
| 295 | '--package-config' , |
| 296 | path.join(flutterRoot, '.dart_tool' , 'package_config.json' ), |
| 297 | '--forbidden-type' , |
| 298 | 'package:flutter/src/widgets/framework.dart::Widget' , |
| 299 | ]; |
| 300 | await runCommand(dart, smokeTestArgs, workingDirectory: flutterRoot, expectNonZeroExit: true); |
| 301 | |
| 302 | // Actual test. |
| 303 | final List<String> args = <String>[ |
| 304 | path.join(flutterRoot, 'dev' , 'forbidden_from_release_tests' , 'bin' , 'main.dart' ), |
| 305 | '--snapshot' , |
| 306 | path.join(tempDirectory.path, 'snapshot.arm64-v8a.json' ), |
| 307 | '--package-config' , |
| 308 | path.join(flutterRoot, '.dart_tool' , 'package_config.json' ), |
| 309 | '--forbidden-type' , |
| 310 | 'package:flutter/src/widgets/widget_inspector.dart::WidgetInspectorService' , |
| 311 | '--forbidden-type' , |
| 312 | 'package:flutter/src/widgets/framework.dart::DebugCreator' , |
| 313 | '--forbidden-type' , |
| 314 | 'package:flutter/src/foundation/print.dart::debugPrint' , |
| 315 | ]; |
| 316 | await runCommand(dart, args, workingDirectory: flutterRoot); |
| 317 | } |
| 318 | |
| 319 | /// Verifies that APK, and IPA (if on macOS), and native desktop builds the |
| 320 | /// examples apps without crashing. It does not actually launch the apps. That |
| 321 | /// happens later in the devicelab. This is just a smoke-test. In particular, |
| 322 | /// this will verify we can build when there are spaces in the path name for the |
| 323 | /// Flutter SDK and target app. |
| 324 | /// |
| 325 | /// Also does some checking about types included in hello_world. |
| 326 | Future<void> _runBuildTests() async { |
| 327 | final List<Directory> exampleDirectories = |
| 328 | Directory(path.join(flutterRoot, 'examples' )) |
| 329 | .listSync() |
| 330 | // API example builds will be tested in a separate shard. |
| 331 | .where( |
| 332 | (FileSystemEntity entity) => entity is Directory && path.basename(entity.path) != 'api' , |
| 333 | ) |
| 334 | .cast<Directory>() |
| 335 | .toList() |
| 336 | ..add(Directory(path.join(flutterRoot, 'packages' , 'integration_test' , 'example' ))) |
| 337 | ..add( |
| 338 | Directory( |
| 339 | path.join(flutterRoot, 'dev' , 'integration_tests' , 'android_semantics_testing' ), |
| 340 | ), |
| 341 | ) |
| 342 | ..add(Directory(path.join(flutterRoot, 'dev' , 'integration_tests' , 'android_views' ))) |
| 343 | ..add(Directory(path.join(flutterRoot, 'dev' , 'integration_tests' , 'channels' ))) |
| 344 | ..add(Directory(path.join(flutterRoot, 'dev' , 'integration_tests' , 'hybrid_android_views' ))) |
| 345 | ..add(Directory(path.join(flutterRoot, 'dev' , 'integration_tests' , 'flutter_gallery' ))) |
| 346 | ..add( |
| 347 | Directory(path.join(flutterRoot, 'dev' , 'integration_tests' , 'ios_platform_view_tests' )), |
| 348 | ) |
| 349 | ..add( |
| 350 | Directory(path.join(flutterRoot, 'dev' , 'integration_tests' , 'ios_app_with_extensions' )), |
| 351 | ) |
| 352 | ..add(Directory(path.join(flutterRoot, 'dev' , 'integration_tests' , 'platform_interaction' ))) |
| 353 | ..add(Directory(path.join(flutterRoot, 'dev' , 'integration_tests' , 'spell_check' ))) |
| 354 | ..add(Directory(path.join(flutterRoot, 'dev' , 'integration_tests' , 'ui' ))); |
| 355 | |
| 356 | // The tests are randomly distributed into subshards so as to get a uniform |
| 357 | // distribution of costs, but the seed is fixed so that issues are reproducible. |
| 358 | final List<ShardRunner> tests = <ShardRunner>[ |
| 359 | for (final Directory exampleDirectory in exampleDirectories) |
| 360 | () => _runExampleProjectBuildTests(exampleDirectory), |
| 361 | ...<ShardRunner>[ |
| 362 | // Web compilation tests. |
| 363 | () => _flutterBuildDart2js( |
| 364 | path.join('dev' , 'integration_tests' , 'web' ), |
| 365 | path.join('lib' , 'main.dart' ), |
| 366 | ), |
| 367 | // Should not fail to compile with dart:io. |
| 368 | () => _flutterBuildDart2js( |
| 369 | path.join('dev' , 'integration_tests' , 'web_compile_tests' ), |
| 370 | path.join('lib' , 'dart_io_import.dart' ), |
| 371 | ), |
| 372 | // Should be able to compile with a call to: |
| 373 | // BackgroundIsolateBinaryMessenger.ensureInitialized. |
| 374 | () => _flutterBuildDart2js( |
| 375 | path.join('dev' , 'integration_tests' , 'web_compile_tests' ), |
| 376 | path.join('lib' , 'background_isolate_binary_messenger.dart' ), |
| 377 | ), |
| 378 | ], |
| 379 | runForbiddenFromReleaseTests, |
| 380 | ]..shuffle(math.Random(0)); |
| 381 | |
| 382 | await runShardRunnerIndexOfTotalSubshard(tests); |
| 383 | } |
| 384 | |
| 385 | Future<void> _runExampleProjectBuildTests(Directory exampleDirectory, [File? mainFile]) async { |
| 386 | // Only verify caching with flutter gallery. |
| 387 | final bool verifyCaching = exampleDirectory.path.contains('flutter_gallery' ); |
| 388 | final String examplePath = path.relative(exampleDirectory.path, from: Directory.current.path); |
| 389 | final List<String> additionalArgs = <String>[ |
| 390 | if (mainFile != null) path.relative(mainFile.path, from: exampleDirectory.absolute.path), |
| 391 | ]; |
| 392 | if (Directory(path.join(examplePath, 'android' )).existsSync()) { |
| 393 | await _flutterBuildApk( |
| 394 | examplePath, |
| 395 | release: false, |
| 396 | additionalArgs: additionalArgs, |
| 397 | verifyCaching: verifyCaching, |
| 398 | ); |
| 399 | await _flutterBuildApk( |
| 400 | examplePath, |
| 401 | release: true, |
| 402 | additionalArgs: additionalArgs, |
| 403 | verifyCaching: verifyCaching, |
| 404 | ); |
| 405 | } else { |
| 406 | print('Example project ${path.basename(examplePath)} has no android directory, skipping apk' ); |
| 407 | } |
| 408 | if (Platform.isMacOS) { |
| 409 | if (Directory(path.join(examplePath, 'ios' )).existsSync()) { |
| 410 | await _flutterBuildIpa( |
| 411 | examplePath, |
| 412 | release: false, |
| 413 | additionalArgs: additionalArgs, |
| 414 | verifyCaching: verifyCaching, |
| 415 | ); |
| 416 | await _flutterBuildIpa( |
| 417 | examplePath, |
| 418 | release: true, |
| 419 | additionalArgs: additionalArgs, |
| 420 | verifyCaching: verifyCaching, |
| 421 | ); |
| 422 | } else { |
| 423 | print('Example project ${path.basename(examplePath)} has no ios directory, skipping ipa' ); |
| 424 | } |
| 425 | } |
| 426 | if (Platform.isLinux) { |
| 427 | if (Directory(path.join(examplePath, 'linux' )).existsSync()) { |
| 428 | await _flutterBuildLinux( |
| 429 | examplePath, |
| 430 | release: false, |
| 431 | additionalArgs: additionalArgs, |
| 432 | verifyCaching: verifyCaching, |
| 433 | ); |
| 434 | await _flutterBuildLinux( |
| 435 | examplePath, |
| 436 | release: true, |
| 437 | additionalArgs: additionalArgs, |
| 438 | verifyCaching: verifyCaching, |
| 439 | ); |
| 440 | } else { |
| 441 | print('Example project ${path.basename(examplePath)} has no linux directory, skipping Linux' ); |
| 442 | } |
| 443 | } |
| 444 | if (Platform.isMacOS) { |
| 445 | if (Directory(path.join(examplePath, 'macos' )).existsSync()) { |
| 446 | await _flutterBuildMacOS( |
| 447 | examplePath, |
| 448 | release: false, |
| 449 | additionalArgs: additionalArgs, |
| 450 | verifyCaching: verifyCaching, |
| 451 | ); |
| 452 | await _flutterBuildMacOS( |
| 453 | examplePath, |
| 454 | release: true, |
| 455 | additionalArgs: additionalArgs, |
| 456 | verifyCaching: verifyCaching, |
| 457 | ); |
| 458 | } else { |
| 459 | print('Example project ${path.basename(examplePath)} has no macos directory, skipping macOS' ); |
| 460 | } |
| 461 | } |
| 462 | if (Platform.isWindows) { |
| 463 | if (Directory(path.join(examplePath, 'windows' )).existsSync()) { |
| 464 | await _flutterBuildWin32( |
| 465 | examplePath, |
| 466 | release: false, |
| 467 | additionalArgs: additionalArgs, |
| 468 | verifyCaching: verifyCaching, |
| 469 | ); |
| 470 | await _flutterBuildWin32( |
| 471 | examplePath, |
| 472 | release: true, |
| 473 | additionalArgs: additionalArgs, |
| 474 | verifyCaching: verifyCaching, |
| 475 | ); |
| 476 | } else { |
| 477 | print( |
| 478 | 'Example project ${path.basename(examplePath)} has no windows directory, skipping Win32' , |
| 479 | ); |
| 480 | } |
| 481 | } |
| 482 | } |
| 483 | |
| 484 | Future<void> _flutterBuildApk( |
| 485 | String relativePathToApplication, { |
| 486 | required bool release, |
| 487 | bool verifyCaching = false, |
| 488 | List<String> additionalArgs = const <String>[], |
| 489 | }) async { |
| 490 | printProgress( |
| 491 | ' ${green}Testing APK ${release ? 'release' : 'debug' } build $reset for $cyan$relativePathToApplication$reset...' , |
| 492 | );
|
| 493 | await _flutterBuild(
|
| 494 | relativePathToApplication,
|
| 495 | 'APK' ,
|
| 496 | 'apk' ,
|
| 497 | release: release,
|
| 498 | verifyCaching: verifyCaching,
|
| 499 | additionalArgs: additionalArgs,
|
| 500 | );
|
| 501 | }
|
| 502 |
|
| 503 | Future<void> _flutterBuildIpa(
|
| 504 | String relativePathToApplication, {
|
| 505 | required bool release,
|
| 506 | List<String> additionalArgs = const <String>[],
|
| 507 | bool verifyCaching = false,
|
| 508 | }) async {
|
| 509 | assert(Platform.isMacOS);
|
| 510 | printProgress(
|
| 511 | ' ${green}Testing IPA ${release ? 'release' : 'debug' } build $reset for $cyan$relativePathToApplication$reset...' ,
|
| 512 | );
|
| 513 | await _flutterBuild(
|
| 514 | relativePathToApplication,
|
| 515 | 'IPA' ,
|
| 516 | 'ios' ,
|
| 517 | release: release,
|
| 518 | verifyCaching: verifyCaching,
|
| 519 | additionalArgs: <String>[...additionalArgs, '--no-codesign' ],
|
| 520 | );
|
| 521 | }
|
| 522 |
|
| 523 | Future<void> _flutterBuildLinux(
|
| 524 | String relativePathToApplication, {
|
| 525 | required bool release,
|
| 526 | bool verifyCaching = false,
|
| 527 | List<String> additionalArgs = const <String>[],
|
| 528 | }) async {
|
| 529 | assert(Platform.isLinux);
|
| 530 | await runCommand(flutter, <String>['config' , '--enable-linux-desktop' ]);
|
| 531 | printProgress(
|
| 532 | ' ${green}Testing Linux ${release ? 'release' : 'debug' } build $reset for $cyan$relativePathToApplication$reset...' ,
|
| 533 | );
|
| 534 | await _flutterBuild(
|
| 535 | relativePathToApplication,
|
| 536 | 'Linux' ,
|
| 537 | 'linux' ,
|
| 538 | release: release,
|
| 539 | verifyCaching: verifyCaching,
|
| 540 | additionalArgs: additionalArgs,
|
| 541 | );
|
| 542 | }
|
| 543 |
|
| 544 | Future<void> _flutterBuildMacOS(
|
| 545 | String relativePathToApplication, {
|
| 546 | required bool release,
|
| 547 | bool verifyCaching = false,
|
| 548 | List<String> additionalArgs = const <String>[],
|
| 549 | }) async {
|
| 550 | assert(Platform.isMacOS);
|
| 551 | await runCommand(flutter, <String>['config' , '--enable-macos-desktop' ]);
|
| 552 | printProgress(
|
| 553 | ' ${green}Testing macOS ${release ? 'release' : 'debug' } build $reset for $cyan$relativePathToApplication$reset...' ,
|
| 554 | );
|
| 555 | await _flutterBuild(
|
| 556 | relativePathToApplication,
|
| 557 | 'macOS' ,
|
| 558 | 'macos' ,
|
| 559 | release: release,
|
| 560 | verifyCaching: verifyCaching,
|
| 561 | additionalArgs: additionalArgs,
|
| 562 | );
|
| 563 | }
|
| 564 |
|
| 565 | Future<void> _flutterBuildWin32(
|
| 566 | String relativePathToApplication, {
|
| 567 | required bool release,
|
| 568 | bool verifyCaching = false,
|
| 569 | List<String> additionalArgs = const <String>[],
|
| 570 | }) async {
|
| 571 | assert(Platform.isWindows);
|
| 572 | printProgress(
|
| 573 | ' ${green}Testing ${release ? 'release' : 'debug' } Windows build $reset for $cyan$relativePathToApplication$reset...' ,
|
| 574 | );
|
| 575 | await _flutterBuild(
|
| 576 | relativePathToApplication,
|
| 577 | 'Windows' ,
|
| 578 | 'windows' ,
|
| 579 | release: release,
|
| 580 | verifyCaching: verifyCaching,
|
| 581 | additionalArgs: additionalArgs,
|
| 582 | );
|
| 583 | }
|
| 584 |
|
| 585 | Future<void> _flutterBuild(
|
| 586 | String relativePathToApplication,
|
| 587 | String platformLabel,
|
| 588 | String platformBuildName, {
|
| 589 | required bool release,
|
| 590 | bool verifyCaching = false,
|
| 591 | List<String> additionalArgs = const <String>[],
|
| 592 | }) async {
|
| 593 | await runCommand(flutter, <String>[
|
| 594 | 'build' ,
|
| 595 | platformBuildName,
|
| 596 | if (verifyCaching) '--performance-measurement-file=perf.json' ,
|
| 597 | ...additionalArgs,
|
| 598 | if (release) '--release' else '--debug' ,
|
| 599 | '-v' ,
|
| 600 | ], workingDirectory: path.join(flutterRoot, relativePathToApplication));
|
| 601 |
|
| 602 | if (verifyCaching) {
|
| 603 | printProgress(
|
| 604 | ' ${green}Testing $platformLabel cache $reset for $cyan$relativePathToApplication$reset...' ,
|
| 605 | );
|
| 606 | await runCommand(flutter, <String>[
|
| 607 | 'build' ,
|
| 608 | platformBuildName,
|
| 609 | '--performance-measurement-file=perf.json' ,
|
| 610 | ...additionalArgs,
|
| 611 | if (release) '--release' else '--debug' ,
|
| 612 | '-v' ,
|
| 613 | ], workingDirectory: path.join(flutterRoot, relativePathToApplication));
|
| 614 | final File file = File(path.join(flutterRoot, relativePathToApplication, 'perf.json' ));
|
| 615 | if (!_allTargetsCached(file)) {
|
| 616 | foundError(<String>[
|
| 617 | ' ${red}Not all build targets cached after second run. $reset' ,
|
| 618 | 'The target performance data was: ${file.readAsStringSync().replaceAll('},' , '},\n' )}' ,
|
| 619 | ]);
|
| 620 | }
|
| 621 | }
|
| 622 | }
|
| 623 |
|
| 624 | bool _allTargetsCached(File performanceFile) {
|
| 625 | if (dryRun) {
|
| 626 | return true;
|
| 627 | }
|
| 628 | final Map<String, Object?> data =
|
| 629 | json.decode(performanceFile.readAsStringSync()) as Map<String, Object?>;
|
| 630 | final List<Map<String, Object?>> targets = (data['targets' ]! as List<Object?>)
|
| 631 | .cast<Map<String, Object?>>();
|
| 632 | return targets.every((Map<String, Object?> element) => element['skipped' ] == true);
|
| 633 | }
|
| 634 |
|
| 635 | Future<void> _flutterBuildDart2js(
|
| 636 | String relativePathToApplication,
|
| 637 | String target, {
|
| 638 | bool expectNonZeroExit = false,
|
| 639 | }) async {
|
| 640 | printProgress(' ${green}Testing Dart2JS build $reset for $cyan$relativePathToApplication$reset...' );
|
| 641 | await runCommand(
|
| 642 | flutter,
|
| 643 | <String>['build' , 'web' , '-v' , '--target= $target' ],
|
| 644 | workingDirectory: path.join(flutterRoot, relativePathToApplication),
|
| 645 | expectNonZeroExit: expectNonZeroExit,
|
| 646 | environment: <String, String>{'FLUTTER_WEB' : 'true' },
|
| 647 | );
|
| 648 | }
|
| 649 |
|