From 51f761270899e576f5678a378bc4450ca800945e Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Sat, 20 Apr 2024 09:58:32 -0700 Subject: [PATCH 1/5] [et] Lookup output filesystem path, not label Sets BuildTarget.executable to the `root_out_dir`-relative path of the executable (e.g. `displaylist_unittests`) instead of its label (e.g. `//out/host_debug/displaylist_unittests`). This is required since, in the lines following the output lookup, we assume it to be a path relative to the build output directory. Also breaks out functions for: * `_gnDesc`: returns the JSON output of `gn desc buildDir target` * `_gnOutputs`: returns the output files listed by `gn outputs buildDir target` Noticed while working on: https://github.com/flutter/flutter/issues/147071 --- tools/engine_tool/lib/src/gn_utils.dart | 76 +++++++++++++------ .../engine_tool/test/build_command_test.dart | 20 ++--- tools/engine_tool/test/gn_utils_test.dart | 2 + tools/engine_tool/test/test_command_test.dart | 4 +- 4 files changed, 67 insertions(+), 35 deletions(-) diff --git a/tools/engine_tool/lib/src/gn_utils.dart b/tools/engine_tool/lib/src/gn_utils.dart index dc6887f74afed..8d67843ff5380 100644 --- a/tools/engine_tool/lib/src/gn_utils.dart +++ b/tools/engine_tool/lib/src/gn_utils.dart @@ -77,30 +77,9 @@ final class BuildTarget { Future> findTargets( Environment environment, Directory buildDir) async { final Map r = {}; - final List getBuildInfoCommandLine = [ - gnBinPath(environment), - 'desc', - buildDir.path, - '*', - '--format=json', - ]; - - final ProcessRunnerResult result = await environment.processRunner.runProcess( - getBuildInfoCommandLine, - workingDirectory: environment.engine.srcDir, - failOk: true); - - // Handle any process failures. - fatalIfFailed(environment, getBuildInfoCommandLine, result); - - late final Map jsonResult; - try { - jsonResult = jsonDecode(result.stdout) as Map; - } catch (e) { - environment.logger.fatal( - 'gn desc output could not be parsed:\nE=$e\nIN=${result.stdout}\n'); - } + final Map jsonResult = + await _runGnDesc(buildDir.path, '*', environment); for (final MapEntry targetEntry in jsonResult.entries) { final String label = targetEntry.key; if (targetEntry.value == null) { @@ -120,7 +99,7 @@ Future> findTargets( } final bool testOnly = getBool(properties, 'testonly'); final List outputs = - getListOfString(properties, 'outputs') ?? []; + await _runGnOutputs(buildDir.path, label, environment); File? executable; if (type == BuildTargetType.executable) { if (outputs.isEmpty) { @@ -135,6 +114,55 @@ Future> findTargets( return r; } +/// Returns the JSON output of running `gn desc buildDir label`. +Future> _runGnDesc( + String buildDir, String label, Environment environment) async { + final List commandline = [ + gnBinPath(environment), + 'desc', + buildDir, + label, + '--format=json', + ]; + + final ProcessRunnerResult result = await environment.processRunner.runProcess( + commandline, + workingDirectory: environment.engine.srcDir, + failOk: true); + + // Handle any process failures. + fatalIfFailed(environment, commandline, result); + + late final Map jsonResult; + try { + jsonResult = jsonDecode(result.stdout) as Map; + } catch (e) { + environment.logger.fatal( + 'gn desc output could not be parsed:\nE=$e\nIN=${result.stdout}\n'); + } + return jsonResult; +} + +/// Returns the output paths returned by `gn outputs buildDir label`. +Future> _runGnOutputs( + String buildDir, String label, Environment environment) async { + final List commandline = [ + gnBinPath(environment), + 'outputs', + buildDir, + label, + ]; + final ProcessRunnerResult result = await environment.processRunner.runProcess( + commandline, + workingDirectory: environment.engine.srcDir, + failOk: true); + + // Handle any process failures. + fatalIfFailed(environment, commandline, result); + + return result.stdout.split('\n'); +} + /// Process selectors and filter allTargets for matches. /// /// We support: diff --git a/tools/engine_tool/test/build_command_test.dart b/tools/engine_tool/test/build_command_test.dart index fe1c65f8e1cf5..055767abd3972 100644 --- a/tools/engine_tool/test/build_command_test.dart +++ b/tools/engine_tool/test/build_command_test.dart @@ -360,11 +360,11 @@ void main() { '//flutter/fml:fml_arc_unittests', ]); expect(result, equals(0)); - expect(testEnv.processHistory.length, greaterThanOrEqualTo(2)); - expect(testEnv.processHistory[3].command[0], contains('ninja')); - expect(testEnv.processHistory[3].command[2], endsWith('/host_debug')); + expect(testEnv.processHistory.length, greaterThan(6)); + expect(testEnv.processHistory[6].command[0], contains('ninja')); + expect(testEnv.processHistory[6].command[2], endsWith('/host_debug')); expect( - testEnv.processHistory[3].command[5], + testEnv.processHistory[6].command[5], equals('flutter/fml:fml_arc_unittests'), ); } finally { @@ -389,19 +389,19 @@ void main() { '//flutter/...', ]); expect(result, equals(0)); - expect(testEnv.processHistory.length, greaterThanOrEqualTo(2)); - expect(testEnv.processHistory[3].command[0], contains('ninja')); - expect(testEnv.processHistory[3].command[2], endsWith('/host_debug')); + expect(testEnv.processHistory.length, greaterThan(6)); + expect(testEnv.processHistory[6].command[0], contains('ninja')); + expect(testEnv.processHistory[6].command[2], endsWith('/host_debug')); expect( - testEnv.processHistory[3].command[5], + testEnv.processHistory[6].command[5], equals('flutter/display_list:display_list_unittests'), ); expect( - testEnv.processHistory[3].command[6], + testEnv.processHistory[6].command[6], equals('flutter/flow:flow_unittests'), ); expect( - testEnv.processHistory[3].command[7], + testEnv.processHistory[6].command[7], equals('flutter/fml:fml_arc_unittests'), ); } finally { diff --git a/tools/engine_tool/test/gn_utils_test.dart b/tools/engine_tool/test/gn_utils_test.dart index 462c90703bb17..a24239abb225e 100644 --- a/tools/engine_tool/test/gn_utils_test.dart +++ b/tools/engine_tool/test/gn_utils_test.dart @@ -39,6 +39,8 @@ void main() { final List cannedProcesses = [ CannedProcess((List command) => command.contains('desc'), stdout: fixtures.gnDescOutput()), + CannedProcess((List command) => command.contains('outputs'), + stdout: 'display_list_unittests'), ]; test('find test targets', () async { diff --git a/tools/engine_tool/test/test_command_test.dart b/tools/engine_tool/test/test_command_test.dart index ecc86d3f8b28d..11885a480b8ec 100644 --- a/tools/engine_tool/test/test_command_test.dart +++ b/tools/engine_tool/test/test_command_test.dart @@ -42,6 +42,8 @@ void main() { final List cannedProcesses = [ CannedProcess((List command) => command.contains('desc'), stdout: fixtures.gnDescOutput()), + CannedProcess((List command) => command.contains('outputs'), + stdout: 'display_list_unittests'), ]; test('test command executes test', () async { @@ -83,7 +85,7 @@ void main() { '//third_party/protobuf:protoc', ]); expect(result, equals(1)); - expect(testEnvironment.processHistory.length, lessThan(3)); + expect(testEnvironment.processHistory.length, lessThan(6)); expect(testEnvironment.processHistory.where((ExecutedProcess process) { return process.command[0].contains('protoc'); }), isEmpty); From fdbb05ce170aff6d4a87f3b9eea378f30531ef3e Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Thu, 18 Apr 2024 17:33:16 -0700 Subject: [PATCH 2/5] Add dart_executable gn rule Adds a dart_executable rule that compiles Dart code to an ELF, Mach-O, or PE binary. Can be used to build tools written in Dart, or Dart unit tests. Issue: https://github.com/flutter/flutter/issues/147013 --- BUILD.gn | 1 + build/dart/rules.gni | 98 ++++++++++++++++++++++++++++++++++++++ tools/engine_tool/BUILD.gn | 88 ++++++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 tools/engine_tool/BUILD.gn diff --git a/BUILD.gn b/BUILD.gn index f21342d919a10..b2d336b196b69 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -131,6 +131,7 @@ group("flutter") { public_deps += [ "//flutter/shell/testing", "//flutter/tools/const_finder", + "//flutter/tools/engine_tool", "//flutter/tools/font_subset", ] } diff --git a/build/dart/rules.gni b/build/dart/rules.gni index c8242de63ab0a..9f5e4560141d9 100644 --- a/build/dart/rules.gni +++ b/build/dart/rules.gni @@ -390,3 +390,101 @@ template("application_snapshot") { } } } + +# Generates a Dart executable using `dart compile exe` +# +# Arguments +# main_dart (required): +# The Dart entrypoint file. +# +# package_config (required): +# The packages.json file for the app. +# +# output (optional): +# The output executable name. By default, the target name. On Windows, an +# 'exe' is automatically appended. +# +# deps (optional): +# Additional dependencies. Dependencies on the frontend server and +# Flutter's platform.dill are included by default. This rule creates and +# uses a depfile, so listing all Dart sources is not necessary. +template("dart_executable") { + assert(defined(invoker.main_dart), "Must specify 'main_dart'") + + main_dart = invoker.main_dart + + package_config = rebase_path(".dart_tool/package_config.json") + if (defined(invoker.package_config)) { + package_config = rebase_path(invoker.package_config) + } + name = target_name + + extra_deps = [] + if (defined(invoker.deps)) { + extra_deps += invoker.deps + } + + extra_inputs = [ main_dart ] + if (defined(invoker.inputs)) { + extra_inputs += invoker.inputs + } + extra_inputs += [ package_config ] + + ext = "" + if (is_win) { + ext = ".exe" + } + output = "$target_gen_dir/$name$ext" + if (defined(invoker.output)) { + output = invoker.output + } + + exe_vm_args = [ "--deterministic" ] + if (defined(invoker.vm_args)) { + exe_vm_args += invoker.vm_args + } + + abs_output = rebase_path(output) + exe_vm_args += [ + "compile", + "exe", + "--packages=$package_config", + "--output=$abs_output", + ] + + if (flutter_prebuilt_dart_sdk) { + action(target_name) { + forward_variables_from(invoker, + [ + "testonly", + "visibility", + ]) + deps = extra_deps + script = "//build/gn_run_binary.py" + inputs = extra_inputs + outputs = [ output ] + pool = "//flutter/build/dart:dart_pool" + + dart = rebase_path("$host_prebuilt_dart_sdk/bin/dart$ext", root_build_dir) + + args = [ dart ] + args += exe_vm_args + args += [ rebase_path(main_dart) ] + } + } else { + dart_action(target_name) { + forward_variables_from(invoker, + [ + "testonly", + "visibility", + ]) + script = main_dart + pool = "//flutter/build/dart:dart_pool" + deps = extra_deps + inputs = extra_inputs + outputs = [ output ] + vm_args = exe_vm_args + args = [] + } + } +} diff --git a/tools/engine_tool/BUILD.gn b/tools/engine_tool/BUILD.gn new file mode 100644 index 0000000000000..92998715dec6a --- /dev/null +++ b/tools/engine_tool/BUILD.gn @@ -0,0 +1,88 @@ +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//flutter/build/dart/rules.gni") + +dart_executable("engine_tool") { + main_dart = "bin/et.dart" + output = "$root_out_dir/et" +} + +group("tests") { + testonly = true + public_deps = [ + ":build_command_test", + ":entry_point_test", + ":fetch_command_test", + ":format_command_test", + ":gn_utils_test", + ":lint_command_test", + ":logger_test", + ":proc_utils_test", + ":query_command_test", + ":run_command_test", + ":test_command_test", + ":worker_pool_test", + ] +} + +dart_executable("build_command_test") { + testonly = true + main_dart = "test/build_command_test.dart" +} + +dart_executable("entry_point_test") { + testonly = true + main_dart = "test/entry_point_test.dart" +} + +dart_executable("fetch_command_test") { + testonly = true + main_dart = "test/fetch_command_test.dart" +} + +dart_executable("format_command_test") { + testonly = true + main_dart = "test/format_command_test.dart" +} + +dart_executable("gn_utils_test") { + testonly = true + main_dart = "test/gn_utils_test.dart" +} + +dart_executable("lint_command_test") { + testonly = true + main_dart = "test/lint_command_test.dart" +} + +dart_executable("logger_test") { + testonly = true + main_dart = "test/logger_test.dart" +} + +dart_executable("proc_utils_test") { + testonly = true + main_dart = "test/proc_utils_test.dart" +} + +dart_executable("query_command_test") { + testonly = true + main_dart = "test/query_command_test.dart" +} + +dart_executable("run_command_test") { + testonly = true + main_dart = "test/run_command_test.dart" +} + +dart_executable("test_command_test") { + testonly = true + main_dart = "test/test_command_test.dart" +} + +dart_executable("worker_pool_test") { + testonly = true + main_dart = "test/worker_pool_test.dart" +} From abefd9c87434988df3d5addce6cc1c6a6cf3d108 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Thu, 18 Apr 2024 22:32:01 -0700 Subject: [PATCH 3/5] Add action_type = dart_executable metadata --- build/dart/rules.gni | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build/dart/rules.gni b/build/dart/rules.gni index 9f5e4560141d9..ecd3ad901930a 100644 --- a/build/dart/rules.gni +++ b/build/dart/rules.gni @@ -470,6 +470,9 @@ template("dart_executable") { args = [ dart ] args += exe_vm_args args += [ rebase_path(main_dart) ] + metadata = { + action_type = [ "dart_executable" ] + } } } else { dart_action(target_name) { @@ -485,6 +488,9 @@ template("dart_executable") { outputs = [ output ] vm_args = exe_vm_args args = [] + metadata = { + action_type = [ "dart_executable" ] + } } } } From f184b3e95b69eb3963b816c8bab34545b02b08be Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Thu, 18 Apr 2024 22:47:28 -0700 Subject: [PATCH 4/5] [et] Resolve dart_executable as an executable Resolves dart_executable targets, which are `action`s with metadata specifying `action_type = [ "dart_executable" ]` to `BuildTargetType.executable`. --- tools/engine_tool/lib/src/gn_utils.dart | 20 ++++++++++++++++++-- tools/engine_tool/lib/src/json_utils.dart | 8 ++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/tools/engine_tool/lib/src/gn_utils.dart b/tools/engine_tool/lib/src/gn_utils.dart index 8d67843ff5380..bace3e0e94f33 100644 --- a/tools/engine_tool/lib/src/gn_utils.dart +++ b/tools/engine_tool/lib/src/gn_utils.dart @@ -32,8 +32,13 @@ enum BuildTargetType { staticLibrary, } -BuildTargetType? _buildTargetTypeFromString(String type) { +BuildTargetType? _buildTargetTypeFromString(String type, Map? metadata) { switch (type) { + case 'action': + if (_getActionType(metadata) == 'dart_executable') { + return BuildTargetType.executable; + } + return null; case 'executable': return BuildTargetType.executable; case 'shared_library': @@ -46,6 +51,16 @@ BuildTargetType? _buildTargetTypeFromString(String type) { } } +String? _getActionType(Map? metadata) { + if (metadata != null) { + final List? action_type = getListOfString(metadata, 'action_type'); + if (action_type != null && action_type.isNotEmpty) { + return action_type[0]; + } + } + return null; +} + // TODO(johnmccutchan): What should we do about source_sets and other // output-less targets? Also, what about action targets which are kind of // "internal" build steps? For now we are ignoring them. @@ -92,7 +107,8 @@ Future> findTargets( if (typeString == null) { environment.logger.fatal('gn desc is missing target type: $properties'); } - final BuildTargetType? type = _buildTargetTypeFromString(typeString!); + final Map? metadata = getMap(properties, 'metadata'); + final BuildTargetType? type = _buildTargetTypeFromString(typeString!, metadata); if (type == null) { // Target is a type that we don't support. continue; diff --git a/tools/engine_tool/lib/src/json_utils.dart b/tools/engine_tool/lib/src/json_utils.dart index e813512e7cd02..2cacfcef2ac8a 100644 --- a/tools/engine_tool/lib/src/json_utils.dart +++ b/tools/engine_tool/lib/src/json_utils.dart @@ -117,3 +117,11 @@ List? getListOfString(Map map, String field) { } return (map[field]! as List).cast(); } + +/// Returns the value in map[field] iff it is a Map. null otherwise. +Map? getMap(Map map, String field) { + if (map[field] case final Map value) { + return value; + } + return null; +} From f58c789c4c9fdde7aa5610f89ba2d6b94e7a9404 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Sat, 20 Apr 2024 11:57:47 -0700 Subject: [PATCH 5/5] Add tests --- .../engine_tool/test/build_command_test.dart | 24 ++++++++------ tools/engine_tool/test/fixtures.dart | 17 +++++++++- tools/engine_tool/test/gn_utils_test.dart | 21 ++++++++++-- .../engine_tool/test/query_command_test.dart | 2 +- tools/engine_tool/test/test_command_test.dart | 32 +++++++++++++++++-- 5 files changed, 78 insertions(+), 18 deletions(-) diff --git a/tools/engine_tool/test/build_command_test.dart b/tools/engine_tool/test/build_command_test.dart index 055767abd3972..81a77fdadb276 100644 --- a/tools/engine_tool/test/build_command_test.dart +++ b/tools/engine_tool/test/build_command_test.dart @@ -360,11 +360,11 @@ void main() { '//flutter/fml:fml_arc_unittests', ]); expect(result, equals(0)); - expect(testEnv.processHistory.length, greaterThan(6)); - expect(testEnv.processHistory[6].command[0], contains('ninja')); - expect(testEnv.processHistory[6].command[2], endsWith('/host_debug')); + expect(testEnv.processHistory.length, greaterThan(7)); + expect(testEnv.processHistory[7].command[0], contains('ninja')); + expect(testEnv.processHistory[7].command[2], endsWith('/host_debug')); expect( - testEnv.processHistory[6].command[5], + testEnv.processHistory[7].command[5], equals('flutter/fml:fml_arc_unittests'), ); } finally { @@ -389,21 +389,25 @@ void main() { '//flutter/...', ]); expect(result, equals(0)); - expect(testEnv.processHistory.length, greaterThan(6)); - expect(testEnv.processHistory[6].command[0], contains('ninja')); - expect(testEnv.processHistory[6].command[2], endsWith('/host_debug')); + expect(testEnv.processHistory.length, greaterThan(7)); + expect(testEnv.processHistory[7].command[0], contains('ninja')); + expect(testEnv.processHistory[7].command[2], endsWith('/host_debug')); expect( - testEnv.processHistory[6].command[5], + testEnv.processHistory[7].command[5], equals('flutter/display_list:display_list_unittests'), ); expect( - testEnv.processHistory[6].command[6], + testEnv.processHistory[7].command[6], equals('flutter/flow:flow_unittests'), ); expect( - testEnv.processHistory[6].command[7], + testEnv.processHistory[7].command[7], equals('flutter/fml:fml_arc_unittests'), ); + expect( + testEnv.processHistory[7].command[8], + equals('flutter/tools/engine_tool:build_command_test'), + ); } finally { testEnv.cleanup(); } diff --git a/tools/engine_tool/test/fixtures.dart b/tools/engine_tool/test/fixtures.dart index 53e088d3b213e..6f1f2eab9e7bf 100644 --- a/tools/engine_tool/test/fixtures.dart +++ b/tools/engine_tool/test/fixtures.dart @@ -316,6 +316,21 @@ String gnDescOutput() => ''' "toolchain": "//build/toolchain/mac:clang_x64", "type": "executable", "visibility": [ "*" ] - } + }, + "//flutter/tools/engine_tool:build_command_test": { + "args": [ "../../flutter/prebuilts/macos-x64/dart-sdk/bin/dart", "--deterministic", "compile", "exe", "--packages=/Users/chris/Developer/flutter/engine/src/flutter/tools/engine_tool/.dart_tool/package_config.json", "--output=/Users/chris/Developer/flutter/engine/src/out/host_debug/gen/flutter/tools/engine_tool/build_command_test", "/Users/chris/Developer/flutter/engine/src/flutter/tools/engine_tool/test/build_command_test.dart" ], + "deps": [ ], + "inputs": [ "//flutter/tools/engine_tool/test/build_command_test.dart", "//flutter/tools/engine_tool/.dart_tool/package_config.json" ], + "metadata": { + "action_type": [ "dart_executable" ] + }, + "outputs": [ "//out/host_debug/gen/flutter/tools/engine_tool/build_command_test" ], + "public": "*", + "script": "//build/gn_run_binary.py", + "testonly": true, + "toolchain": "//build/toolchain/mac:clang_x64", + "type": "action", + "visibility": [ "*" ] + } } '''; diff --git a/tools/engine_tool/test/gn_utils_test.dart b/tools/engine_tool/test/gn_utils_test.dart index a24239abb225e..5d021ea35d275 100644 --- a/tools/engine_tool/test/gn_utils_test.dart +++ b/tools/engine_tool/test/gn_utils_test.dart @@ -39,8 +39,18 @@ void main() { final List cannedProcesses = [ CannedProcess((List command) => command.contains('desc'), stdout: fixtures.gnDescOutput()), - CannedProcess((List command) => command.contains('outputs'), + CannedProcess((List command) => + command.contains('outputs') && command.contains('//flutter/display_list:display_list_unittests'), stdout: 'display_list_unittests'), + CannedProcess((List command) => + command.contains('outputs') && command.contains('//flutter/flow:flow_unittests'), + stdout: 'flow_unittests'), + CannedProcess((List command) => + command.contains('outputs') && command.contains('//flutter/fml:fml_arc_unittests'), + stdout: 'fml_arc_unittests'), + CannedProcess((List command) => + command.contains('outputs') && command.contains('//flutter/tools/engine_tool:build_command_test'), + stdout: 'build_command_test'), ]; test('find test targets', () async { @@ -49,7 +59,7 @@ void main() { final Environment env = testEnvironment.environment; final Map testTargets = await findTargets(env, engine.outDir); - expect(testTargets.length, equals(3)); + expect(testTargets.length, equals(4)); expect(testTargets['//flutter/display_list:display_list_unittests'], notEquals(null)); expect( @@ -57,6 +67,11 @@ void main() { .executable! .path, endsWith('display_list_unittests')); + expect( + testTargets['//flutter/tools/engine_tool:build_command_test']! + .executable! + .path, + endsWith('build_command_test')); }); test('process queue failure', () async { @@ -65,7 +80,7 @@ void main() { final Environment env = testEnvironment.environment; final Map testTargets = await findTargets(env, engine.outDir); - expect(selectTargets(['//...'], testTargets).length, equals(3)); + expect(selectTargets(['//...'], testTargets).length, equals(4)); expect( selectTargets(['//flutter/display_list'], testTargets).length, equals(0)); diff --git a/tools/engine_tool/test/query_command_test.dart b/tools/engine_tool/test/query_command_test.dart index f9da60c6c8b0c..e443e2845367b 100644 --- a/tools/engine_tool/test/query_command_test.dart +++ b/tools/engine_tool/test/query_command_test.dart @@ -162,7 +162,7 @@ void main() { expect(result, equals(0)); expect( env.logger.testLogs.length, - equals(4), + equals(5), ); expect(env.logger.testLogs[1].message, startsWith('//flutter/display_list:display_list_unittests')); diff --git a/tools/engine_tool/test/test_command_test.dart b/tools/engine_tool/test/test_command_test.dart index 11885a480b8ec..2345cd978e018 100644 --- a/tools/engine_tool/test/test_command_test.dart +++ b/tools/engine_tool/test/test_command_test.dart @@ -42,11 +42,13 @@ void main() { final List cannedProcesses = [ CannedProcess((List command) => command.contains('desc'), stdout: fixtures.gnDescOutput()), - CannedProcess((List command) => command.contains('outputs'), + CannedProcess((List command) => command.contains('outputs') && command.contains('//flutter/display_list:display_list_unittests'), stdout: 'display_list_unittests'), + CannedProcess((List command) => command.contains('outputs') && command.contains('//flutter/tools/engine_tool:build_command_test'), + stdout: 'build_command_test'), ]; - test('test command executes test', () async { + test('test command executes executable test', () async { final TestEnvironment testEnvironment = TestEnvironment.withTestEngine( cannedProcesses: cannedProcesses, ); @@ -70,6 +72,30 @@ void main() { } }); + test('test command executes dart_executable test', () async { + final TestEnvironment testEnvironment = TestEnvironment.withTestEngine( + cannedProcesses: cannedProcesses, + ); + try { + final Environment env = testEnvironment.environment; + final ToolCommandRunner runner = ToolCommandRunner( + environment: env, + configs: configs, + ); + final int result = await runner.run([ + 'test', + '//flutter/tools/engine_tool:build_command_test', + ]); + expect(result, equals(0)); + expect(testEnvironment.processHistory.length, greaterThan(3)); + final int offset = testEnvironment.processHistory.length - 1; + expect(testEnvironment.processHistory[offset].command[0], + endsWith('build_command_test')); + } finally { + testEnvironment.cleanup(); + } + }); + test('test command skips non-testonly executables', () async { final TestEnvironment testEnvironment = TestEnvironment.withTestEngine( cannedProcesses: cannedProcesses, @@ -85,7 +111,7 @@ void main() { '//third_party/protobuf:protoc', ]); expect(result, equals(1)); - expect(testEnvironment.processHistory.length, lessThan(6)); + expect(testEnvironment.processHistory.length, lessThan(7)); expect(testEnvironment.processHistory.where((ExecutedProcess process) { return process.command[0].contains('protoc'); }), isEmpty);