From de72c525880d567931ab309a9489594481c98012 Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Thu, 6 Aug 2026 16:34:44 -0700 Subject: [PATCH 01/16] Barebones command copied from the test command. --- script/tool/lib/src/main.dart | 2 + script/tool/lib/src/test_dart_fixes.dart | 205 +++++++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 script/tool/lib/src/test_dart_fixes.dart diff --git a/script/tool/lib/src/main.dart b/script/tool/lib/src/main.dart index 89c7bf9525b0..55b3e9d5e204 100644 --- a/script/tool/lib/src/main.dart +++ b/script/tool/lib/src/main.dart @@ -30,6 +30,7 @@ import 'podspec_check_command.dart'; import 'publish_check_command.dart'; import 'publish_command.dart'; import 'remove_dev_dependencies_command.dart'; +import 'test_dart_fixes.dart'; import 'update_dependency_command.dart'; import 'update_excerpts_command.dart'; import 'update_min_sdk_command.dart'; @@ -75,6 +76,7 @@ void main(List args) { ..addCommand(PublishCheckCommand(packagesDir)) ..addCommand(PublishCommand(packagesDir)) ..addCommand(RemoveDevDependenciesCommand(packagesDir)) + ..addCommand(TestDartFixes(packagesDir)) ..addCommand(UpdateDependencyCommand(packagesDir)) ..addCommand(UpdateExcerptsCommand(packagesDir)) ..addCommand(UpdateMinSdkCommand(packagesDir)) diff --git a/script/tool/lib/src/test_dart_fixes.dart b/script/tool/lib/src/test_dart_fixes.dart new file mode 100644 index 000000000000..e1a5144e0fe2 --- /dev/null +++ b/script/tool/lib/src/test_dart_fixes.dart @@ -0,0 +1,205 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:file/file.dart'; + +import 'common/core.dart'; +import 'common/file_filters.dart'; +import 'common/output_utils.dart'; +import 'common/package_looping_command.dart'; +import 'common/plugin_utils.dart'; +import 'common/pub_utils.dart'; +import 'common/repository_package.dart'; + +// TODO(justinmc): Possibly not relevant. +const int _exitUnknownTestPlatform = 3; + +// TODO(justinmc): Possibly not relevant. +enum _TestPlatform { + // Must run in the command-line VM. + vm, + // Must run in a browser. + browser, +} + +/// A command to run dart fix tests for packages that have a test_fixes +/// directory. +class TestDartFixes extends PackageLoopingCommand { + /// Creates an instance of the test dart fixes command. + TestDartFixes(super.packagesDir, {super.processRunner, super.platform, super.gitDir}) { + // TODO(justinmc): Possibly not relevant. + argParser.addOption( + _platformFlag, + help: + 'Runs tests on the given platform instead of the default platform ' + '("vm" in most cases, "chrome" for web plugin implementations).', + ); + } + + static const String _platformFlag = 'platform'; + + @override + final String name = 'test-dart-fixes'; + + @override + List get aliases => []; + + @override + final String description = + 'Runs the Dart fix tests for all packages.\n\n' + 'This command requires "flutter" to be in your path.'; + + @override + PackageLoopingType get packageLoopingType => PackageLoopingType.includeAllSubpackages; + + // TODO(justinmc): Revisit. + @override + bool shouldIgnoreFile(String path) { + return isRepoLevelNonCodeImpactingFile(path) || + isNativeCodeFile(path) || + isPackageSupportFile(path); + } + + @override + Future runForPackage(RepositoryPackage package) async { + // TODO(justinmc): Should check for test_fix directory. + if (!package.testDirectory.existsSync()) { + return PackageResult.skip('No test/ directory.'); + } + + return PackageResult.skip('Not yet implemented.'); + // TODO(justinmc): See packages/go_router/tool/run_tests.dart. + String? platform = getNullableStringArg(_platformFlag); + + // Skip running plugin tests for non-web-supporting plugins (or non-web + // federated plugin implementations) on web, since there's no reason to + // expect them to work. + final bool webPlatform = platform != null && platform != 'vm'; + final explicitVMPlatform = platform == 'vm'; + final bool isWebOnlyPluginImplementation = + pluginSupportsPlatform(platformWeb, package, requiredMode: PlatformSupport.inline) && + package.directory.basename.endsWith('_web'); + if (webPlatform) { + if (isFlutterPlugin(package) && !pluginSupportsPlatform(platformWeb, package)) { + return PackageResult.skip("Non-web plugin tests don't need web testing."); + } + if (_testOnTarget(package) == _TestPlatform.vm) { + // This explict skip is necessary because trying to run tests in a mode + // that the package has opted out of returns a non-zero exit code. + return PackageResult.skip('Package has opted out of non-vm testing.'); + } + } else if (explicitVMPlatform) { + if (isWebOnlyPluginImplementation) { + return PackageResult.skip("Web plugin tests don't need vm testing."); + } + final _TestPlatform? target = _testOnTarget(package); + if (target != null && _testOnTarget(package) != _TestPlatform.vm) { + // This explict skip is necessary because trying to run tests in a mode + // that the package has opted out of returns a non-zero exit code. + return PackageResult.skip('Package has opted out of vm testing.'); + } + } else if (platform == null && isWebOnlyPluginImplementation) { + // If no explicit mode is requested, run web plugin implementations in + // Chrome since their tests are not expected to work in vm mode. This + // allows easily running all unit tests locally, without having to run + // both modes. + platform = 'chrome'; + } + + // Whether to run web tests compiled to wasm. + final bool wasm = platform != 'vm' && getBoolArg(kWebWasmFlag); + + bool passed; + if (package.requiresFlutter()) { + passed = await _runFlutterTests(package, platform: platform, wasm: wasm); + } else { + passed = await _runDartTests(package, platform: platform, wasm: wasm); + } + return passed ? PackageResult.success() : PackageResult.fail(); + } + + /// Runs the Dart tests for a Flutter package, returning true on success. + Future _runFlutterTests( + RepositoryPackage package, { + String? platform, + bool wasm = false, + }) async { + final String experiment = getStringArg(kEnableExperiment); + + final int exitCode = await processRunner.runAndStream(flutterCommand, [ + 'test', + '--color', + if (experiment.isNotEmpty) '--enable-experiment=$experiment', + // Flutter defaults to VM mode (under a different name) and explicitly + // setting it is deprecated, so pass nothing in that case. + if (platform != null && platform != 'vm') '--platform=$platform', + if (wasm) '--wasm', + ], workingDir: package.directory); + return exitCode == 0; + } + + /// Runs the Dart tests for a non-Flutter package, returning true on success. + Future _runDartTests( + RepositoryPackage package, { + String? platform, + bool wasm = false, + }) async { + // Unlike `flutter test`, `dart run test` does not automatically get + // packages + if (!await runPubGet(package, processRunner, super.platform)) { + printError('Unable to fetch dependencies.'); + return false; + } + + final String experiment = getStringArg(kEnableExperiment); + + final int exitCode = await processRunner.runAndStream('dart', [ + 'run', + if (experiment.isNotEmpty) '--enable-experiment=$experiment', + 'test', + if (platform != null) '--platform=$platform', + if (wasm) '--compiler=dart2wasm', + ], workingDir: package.directory); + + return exitCode == 0; + } + + /// Returns the required test environment, or null if none is specified. + /// + /// Throws if the target is not recognized. + _TestPlatform? _testOnTarget(RepositoryPackage package) { + final File testConfig = package.directory.childFile('dart_test.yaml'); + if (!testConfig.existsSync()) { + return null; + } + final testOnRegex = RegExp(r'^test_on:\s*([a-z].*[a-z])\s*$'); + for (final String line in testConfig.readAsLinesSync()) { + final RegExpMatch? match = testOnRegex.firstMatch(line); + if (match != null) { + final String targetFilter = match.group(1)!; + // test_on lines can be very complex, but in pratice the packages in + // this repo currently only need the ability to require vm or not, so a + // simple one-target directive is all that's supported currently. + // Making it deliberately strict avoids the possibility of accidentally + // skipping vm coverage due to a complex expression that's not handled + // correctly. + switch (targetFilter) { + case 'vm': + return _TestPlatform.vm; + case 'browser': + return _TestPlatform.browser; + default: + printError( + 'Unknown "test_on" value: "$targetFilter"\n' + "If this value needs to be supported for this package's tests, " + 'please update the repository tooling to support more test_on ' + 'modes.', + ); + throw ToolExit(_exitUnknownTestPlatform); + } + } + } + return null; + } +} From dfa7553ac7f78a6278bf12e8a1e09617c9ce44cb Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Fri, 7 Aug 2026 12:34:24 -0700 Subject: [PATCH 02/16] Works for running dart fix tests on go_router, cupertino, and material. Adapted from go_router's run_tests script. --- .../lib/src/common/repository_package.dart | 3 + script/tool/lib/src/test_dart_fixes.dart | 246 +++++++----------- script/tool/pubspec.yaml | 1 + 3 files changed, 99 insertions(+), 151 deletions(-) diff --git a/script/tool/lib/src/common/repository_package.dart b/script/tool/lib/src/common/repository_package.dart index 92f775917c1f..861058af0e18 100644 --- a/script/tool/lib/src/common/repository_package.dart +++ b/script/tool/lib/src/common/repository_package.dart @@ -74,6 +74,9 @@ class RepositoryPackage { /// The test directory containing the package's Dart tests. Directory get testDirectory => directory.childDirectory('test'); + /// The test directory containing the tests for the package's dart fixes. + Directory get dartFixTestDirectory => directory.childDirectory('test_fixes'); + /// The path to the script that is run by the `custom-test` command. File get customTestScript => directory.childDirectory('tool').childFile('run_tests.dart'); diff --git a/script/tool/lib/src/test_dart_fixes.dart b/script/tool/lib/src/test_dart_fixes.dart index e1a5144e0fe2..44cc04ed8bf1 100644 --- a/script/tool/lib/src/test_dart_fixes.dart +++ b/script/tool/lib/src/test_dart_fixes.dart @@ -2,42 +2,23 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; +import 'dart:io'; + import 'package:file/file.dart'; +import 'package:file/local.dart'; +import 'package:io/io.dart' as io; +import 'package:path/path.dart' as p; -import 'common/core.dart'; import 'common/file_filters.dart'; -import 'common/output_utils.dart'; import 'common/package_looping_command.dart'; -import 'common/plugin_utils.dart'; -import 'common/pub_utils.dart'; import 'common/repository_package.dart'; -// TODO(justinmc): Possibly not relevant. -const int _exitUnknownTestPlatform = 3; - -// TODO(justinmc): Possibly not relevant. -enum _TestPlatform { - // Must run in the command-line VM. - vm, - // Must run in a browser. - browser, -} - /// A command to run dart fix tests for packages that have a test_fixes /// directory. class TestDartFixes extends PackageLoopingCommand { /// Creates an instance of the test dart fixes command. - TestDartFixes(super.packagesDir, {super.processRunner, super.platform, super.gitDir}) { - // TODO(justinmc): Possibly not relevant. - argParser.addOption( - _platformFlag, - help: - 'Runs tests on the given platform instead of the default platform ' - '("vm" in most cases, "chrome" for web plugin implementations).', - ); - } - - static const String _platformFlag = 'platform'; + TestDartFixes(super.packagesDir, {super.processRunner, super.platform, super.gitDir}); @override final String name = 'test-dart-fixes'; @@ -53,7 +34,6 @@ class TestDartFixes extends PackageLoopingCommand { @override PackageLoopingType get packageLoopingType => PackageLoopingType.includeAllSubpackages; - // TODO(justinmc): Revisit. @override bool shouldIgnoreFile(String path) { return isRepoLevelNonCodeImpactingFile(path) || @@ -63,143 +43,107 @@ class TestDartFixes extends PackageLoopingCommand { @override Future runForPackage(RepositoryPackage package) async { - // TODO(justinmc): Should check for test_fix directory. - if (!package.testDirectory.existsSync()) { - return PackageResult.skip('No test/ directory.'); + // Only run for packages that have a fix_tests directory. + if (!package.dartFixTestDirectory.existsSync()) { + return PackageResult.skip('No ${package.dartFixTestDirectory} directory.'); } - return PackageResult.skip('Not yet implemented.'); - // TODO(justinmc): See packages/go_router/tool/run_tests.dart. - String? platform = getNullableStringArg(_platformFlag); - - // Skip running plugin tests for non-web-supporting plugins (or non-web - // federated plugin implementations) on web, since there's no reason to - // expect them to work. - final bool webPlatform = platform != null && platform != 'vm'; - final explicitVMPlatform = platform == 'vm'; - final bool isWebOnlyPluginImplementation = - pluginSupportsPlatform(platformWeb, package, requiredMode: PlatformSupport.inline) && - package.directory.basename.endsWith('_web'); - if (webPlatform) { - if (isFlutterPlugin(package) && !pluginSupportsPlatform(platformWeb, package)) { - return PackageResult.skip("Non-web plugin tests don't need web testing."); - } - if (_testOnTarget(package) == _TestPlatform.vm) { - // This explict skip is necessary because trying to run tests in a mode - // that the package has opted out of returns a non-zero exit code. - return PackageResult.skip('Package has opted out of non-vm testing.'); - } - } else if (explicitVMPlatform) { - if (isWebOnlyPluginImplementation) { - return PackageResult.skip("Web plugin tests don't need vm testing."); - } - final _TestPlatform? target = _testOnTarget(package); - if (target != null && _testOnTarget(package) != _TestPlatform.vm) { - // This explict skip is necessary because trying to run tests in a mode - // that the package has opted out of returns a non-zero exit code. - return PackageResult.skip('Package has opted out of vm testing.'); + // Create a temporary directory to run the tests in. + const fileSystem = LocalFileSystem(); + final Directory testTempDir = await fileSystem.systemTempDirectory.createTemp(); + + late final PackageResult result; + try { + final int statusCode = await _runDartFixTests(package, testTempDir); + if (statusCode != 0) { + throw Exception('Status code $statusCode'); } - } else if (platform == null && isWebOnlyPluginImplementation) { - // If no explicit mode is requested, run web plugin implementations in - // Chrome since their tests are not expected to work in vm mode. This - // allows easily running all unit tests locally, without having to run - // both modes. - platform = 'chrome'; + result = PackageResult.success(); + } catch (error) { + result = PackageResult.fail(['Dart fix tests failed: $error}']); } + if (testTempDir.existsSync()) { + await testTempDir.delete(recursive: true); + } + return result; + } - // Whether to run web tests compiled to wasm. - final bool wasm = platform != 'vm' && getBoolArg(kWebWasmFlag); - - bool passed; - if (package.requiresFlutter()) { - passed = await _runFlutterTests(package, platform: platform, wasm: wasm); - } else { - passed = await _runDartTests(package, platform: platform, wasm: wasm); + /// Run the dart fix tests for the package in the given temporary directory. + /// + /// Resolves with the status code of the command. + Future _runDartFixTests(RepositoryPackage package, Directory testTempDir) async { + // Copy the test_fixes folder to the temporary testTempDir. + // + // This also creates the proper pubspec.yaml in the temp directory. + await _prepareTemplate(package: package, testTempDir: testTempDir); + + // Run dart pub get in the temp directory to set it up. + final int pubGetStatusCode = await _runProcess('dart', [ + 'pub', + 'get', + ], workingDirectory: testTempDir.path); + + if (pubGetStatusCode != 0) { + await testTempDir.delete(recursive: true); + return pubGetStatusCode; } - return passed ? PackageResult.success() : PackageResult.fail(); + + // Run dart fix --compare-to-golden in the temp directory. + final int dartFixStatusCode = await _runProcess('dart', [ + 'fix', + '--compare-to-golden', + ], workingDirectory: testTempDir.path); + + await testTempDir.delete(recursive: true); + return dartFixStatusCode; } - /// Runs the Dart tests for a Flutter package, returning true on success. - Future _runFlutterTests( - RepositoryPackage package, { - String? platform, - bool wasm = false, + Future _prepareTemplate({ + required RepositoryPackage package, + required Directory testTempDir, }) async { - final String experiment = getStringArg(kEnableExperiment); - - final int exitCode = await processRunner.runAndStream(flutterCommand, [ - 'test', - '--color', - if (experiment.isNotEmpty) '--enable-experiment=$experiment', - // Flutter defaults to VM mode (under a different name) and explicitly - // setting it is deprecated, so pass nothing in that case. - if (platform != null && platform != 'vm') '--platform=$platform', - if (wasm) '--wasm', - ], workingDir: package.directory); - return exitCode == 0; + // Copy from src `test_fixes/` to the temp directory. + await io.copyPath(package.dartFixTestDirectory.path, testTempDir.path); + + // The pubspec.yaml file to create. + const fileSystem = LocalFileSystem(); + final File targetPubspecFile = fileSystem.file(p.join(testTempDir.path, 'pubspec.yaml')); + + final targetYaml = + ''' + name: test_fixes + publish_to: "none" + version: 1.0.0 + + environment: + sdk: ">=2.18.0 <4.0.0" + flutter: ">=3.3.0" + + dependencies: + flutter: + sdk: flutter + ${package.directory.basename}: + path: ${package.directory.path} + '''; + + await targetPubspecFile.writeAsString(targetYaml); } - /// Runs the Dart tests for a non-Flutter package, returning true on success. - Future _runDartTests( - RepositoryPackage package, { - String? platform, - bool wasm = false, + Future _runProcess( + String command, + List arguments, { + String? workingDirectory, }) async { - // Unlike `flutter test`, `dart run test` does not automatically get - // packages - if (!await runPubGet(package, processRunner, super.platform)) { - printError('Unable to fetch dependencies.'); - return false; - } - - final String experiment = getStringArg(kEnableExperiment); - - final int exitCode = await processRunner.runAndStream('dart', [ - 'run', - if (experiment.isNotEmpty) '--enable-experiment=$experiment', - 'test', - if (platform != null) '--platform=$platform', - if (wasm) '--compiler=dart2wasm', - ], workingDir: package.directory); - - return exitCode == 0; + final Process process = await _streamOutput( + Process.start(command, arguments, workingDirectory: workingDirectory), + ); + return process.exitCode; } - /// Returns the required test environment, or null if none is specified. - /// - /// Throws if the target is not recognized. - _TestPlatform? _testOnTarget(RepositoryPackage package) { - final File testConfig = package.directory.childFile('dart_test.yaml'); - if (!testConfig.existsSync()) { - return null; - } - final testOnRegex = RegExp(r'^test_on:\s*([a-z].*[a-z])\s*$'); - for (final String line in testConfig.readAsLinesSync()) { - final RegExpMatch? match = testOnRegex.firstMatch(line); - if (match != null) { - final String targetFilter = match.group(1)!; - // test_on lines can be very complex, but in pratice the packages in - // this repo currently only need the ability to require vm or not, so a - // simple one-target directive is all that's supported currently. - // Making it deliberately strict avoids the possibility of accidentally - // skipping vm coverage due to a complex expression that's not handled - // correctly. - switch (targetFilter) { - case 'vm': - return _TestPlatform.vm; - case 'browser': - return _TestPlatform.browser; - default: - printError( - 'Unknown "test_on" value: "$targetFilter"\n' - "If this value needs to be supported for this package's tests, " - 'please update the repository tooling to support more test_on ' - 'modes.', - ); - throw ToolExit(_exitUnknownTestPlatform); - } - } - } - return null; + Future _streamOutput(Future processFuture) async { + final Process process = await processFuture; + unawaited(stdout.addStream(process.stdout)); + unawaited(stderr.addStream(process.stderr)); + return process; } } diff --git a/script/tool/pubspec.yaml b/script/tool/pubspec.yaml index 957b1ebe343d..94286da51efa 100644 --- a/script/tool/pubspec.yaml +++ b/script/tool/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: glob: ^2.1.3 http: ^1.0.0 http_multi_server: ^3.0.1 + io: ^1.0.5 meta: ^1.10.0 path: ^1.8.3 platform: ^3.0.2 From e280734a79b0e4b074cba2acb207185152ee6b17 Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Fri, 7 Aug 2026 12:37:21 -0700 Subject: [PATCH 03/16] This is covered by the global script now. --- packages/go_router/tool/run_tests.dart | 101 ------------------------- 1 file changed, 101 deletions(-) delete mode 100644 packages/go_router/tool/run_tests.dart diff --git a/packages/go_router/tool/run_tests.dart b/packages/go_router/tool/run_tests.dart deleted file mode 100644 index ca09bed0f7cd..000000000000 --- a/packages/go_router/tool/run_tests.dart +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2013 The Flutter Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// Called from the custom-tests CI action. -// -// usage: dart run tool/run_tests.dart - -// ignore_for_file: avoid_print - -import 'dart:async'; -import 'dart:io'; -import 'package:io/io.dart' as io; -import 'package:path/path.dart' as p; - -// This test runner simulates a consumption of go_router that checks if -// the dart fixes are applied correctly. -// This is done by copying the `test_fixes/` directory to a temp directory -// that references `go_router`, and running `dart fix --compare-to-golden` -// on the temp directory. -Future main(List args) async { - final Directory goRouterPackageRoot = File.fromUri(Platform.script).parent.parent; - - final Directory testTempDir = await Directory.systemTemp.createTemp(); - - // Cleans up the temp directory and exits with a given statusCode. - Future cleanUpAndExit(int statusCode) async { - await testTempDir.delete(recursive: true); - exit(statusCode); - } - - // Copy the test_fixes folder to the temporary testFixesTargetDir. - // - // This also creates the proper pubspec.yaml in the temp directory. - await _prepareTemplate(packageRoot: goRouterPackageRoot, testTempDir: testTempDir); - - // Run dart pub get in the temp directory to set it up. - final int pubGetStatusCode = await _runProcess('dart', [ - 'pub', - 'get', - ], workingDirectory: testTempDir.path); - - if (pubGetStatusCode != 0) { - await cleanUpAndExit(pubGetStatusCode); - } - - // Run dart fix --compare-to-golden in the temp directory. - final int dartFixStatusCode = await _runProcess('dart', [ - 'fix', - '--compare-to-golden', - ], workingDirectory: testTempDir.path); - - await cleanUpAndExit(dartFixStatusCode); -} - -Future _prepareTemplate({ - required Directory packageRoot, - required Directory testTempDir, -}) async { - // The src test_fixes directory. - final testFixesSrcDir = Directory(p.join(packageRoot.path, 'test_fixes')); - - // Copy from src `test_fixes/` to the temp directory. - await io.copyPath(testFixesSrcDir.path, testTempDir.path); - - // The pubspec.yaml file to create. - final targetPubspecFile = File(p.join(testTempDir.path, 'pubspec.yaml')); - - final targetYaml = - ''' -name: test_fixes -publish_to: "none" -version: 1.0.0 - -environment: - sdk: ">=2.18.0 <4.0.0" - flutter: ">=3.3.0" - -dependencies: - flutter: - sdk: flutter - go_router: - path: ${packageRoot.path} -'''; - - await targetPubspecFile.writeAsString(targetYaml); -} - -Future _streamOutput(Future processFuture) async { - final Process process = await processFuture; - unawaited(stdout.addStream(process.stdout)); - unawaited(stderr.addStream(process.stderr)); - return process; -} - -Future _runProcess(String command, List arguments, {String? workingDirectory}) async { - final Process process = await _streamOutput( - Process.start(command, arguments, workingDirectory: workingDirectory), - ); - return process.exitCode; -} From 20b78424605e5f5688ff8a330bc4f55d7d9f4d2a Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Fri, 7 Aug 2026 12:50:57 -0700 Subject: [PATCH 04/16] Code cleanup --- script/tool/lib/src/test_dart_fixes.dart | 103 +++++++++++------------ 1 file changed, 51 insertions(+), 52 deletions(-) diff --git a/script/tool/lib/src/test_dart_fixes.dart b/script/tool/lib/src/test_dart_fixes.dart index 44cc04ed8bf1..f8f8e7d0c091 100644 --- a/script/tool/lib/src/test_dart_fixes.dart +++ b/script/tool/lib/src/test_dart_fixes.dart @@ -48,13 +48,16 @@ class TestDartFixes extends PackageLoopingCommand { return PackageResult.skip('No ${package.dartFixTestDirectory} directory.'); } - // Create a temporary directory to run the tests in. - const fileSystem = LocalFileSystem(); - final Directory testTempDir = await fileSystem.systemTempDirectory.createTemp(); + final Directory testDirectory; + try { + testDirectory = await _createTestDirectory(package); + } catch (error) { + return PackageResult.fail(['Failed to create temporary test directory: $error}']); + } late final PackageResult result; try { - final int statusCode = await _runDartFixTests(package, testTempDir); + final int statusCode = await _runDartFixTests(package, testDirectory); if (statusCode != 0) { throw Exception('Status code $statusCode'); } @@ -62,74 +65,70 @@ class TestDartFixes extends PackageLoopingCommand { } catch (error) { result = PackageResult.fail(['Dart fix tests failed: $error}']); } - if (testTempDir.existsSync()) { - await testTempDir.delete(recursive: true); + if (testDirectory.existsSync()) { + await testDirectory.delete(recursive: true); } return result; } + /// Create and prepare a temporary directory in which to run the dart fix + /// tests. + /// + /// It is the responsibility of the caller to delete this directory and its + /// contents when done. + static Future _createTestDirectory(RepositoryPackage package) async { + const fileSystem = LocalFileSystem(); + final Directory testTempDirectory = await fileSystem.systemTempDirectory.createTemp(); + + // Copy from `test_fixes/` to the temp directory. + await io.copyPath(package.dartFixTestDirectory.path, testTempDirectory.path); + + // The pubspec.yaml file to create. + final File targetPubspecFile = fileSystem.file(p.join(testTempDirectory.path, 'pubspec.yaml')); + + final targetYaml = + ''' +name: test_fixes +publish_to: "none" +version: 1.0.0 + +environment: + sdk: ">=2.18.0 <4.0.0" + flutter: ">=3.3.0" + +dependencies: + flutter: + sdk: flutter + ${package.directory.basename}: + path: ${package.directory.path} +'''; + + await targetPubspecFile.writeAsString(targetYaml); + return testTempDirectory; + } + /// Run the dart fix tests for the package in the given temporary directory. /// /// Resolves with the status code of the command. - Future _runDartFixTests(RepositoryPackage package, Directory testTempDir) async { - // Copy the test_fixes folder to the temporary testTempDir. - // - // This also creates the proper pubspec.yaml in the temp directory. - await _prepareTemplate(package: package, testTempDir: testTempDir); - + static Future _runDartFixTests(RepositoryPackage package, Directory testDirectory) async { // Run dart pub get in the temp directory to set it up. final int pubGetStatusCode = await _runProcess('dart', [ 'pub', 'get', - ], workingDirectory: testTempDir.path); + ], workingDirectory: testDirectory.path); if (pubGetStatusCode != 0) { - await testTempDir.delete(recursive: true); return pubGetStatusCode; } // Run dart fix --compare-to-golden in the temp directory. - final int dartFixStatusCode = await _runProcess('dart', [ + return _runProcess('dart', [ 'fix', '--compare-to-golden', - ], workingDirectory: testTempDir.path); - - await testTempDir.delete(recursive: true); - return dartFixStatusCode; - } - - Future _prepareTemplate({ - required RepositoryPackage package, - required Directory testTempDir, - }) async { - // Copy from src `test_fixes/` to the temp directory. - await io.copyPath(package.dartFixTestDirectory.path, testTempDir.path); - - // The pubspec.yaml file to create. - const fileSystem = LocalFileSystem(); - final File targetPubspecFile = fileSystem.file(p.join(testTempDir.path, 'pubspec.yaml')); - - final targetYaml = - ''' - name: test_fixes - publish_to: "none" - version: 1.0.0 - - environment: - sdk: ">=2.18.0 <4.0.0" - flutter: ">=3.3.0" - - dependencies: - flutter: - sdk: flutter - ${package.directory.basename}: - path: ${package.directory.path} - '''; - - await targetPubspecFile.writeAsString(targetYaml); + ], workingDirectory: testDirectory.path); } - Future _runProcess( + static Future _runProcess( String command, List arguments, { String? workingDirectory, @@ -140,7 +139,7 @@ class TestDartFixes extends PackageLoopingCommand { return process.exitCode; } - Future _streamOutput(Future processFuture) async { + static Future _streamOutput(Future processFuture) async { final Process process = await processFuture; unawaited(stdout.addStream(process.stdout)); unawaited(stderr.addStream(process.stderr)); From 52517d3f783c295c381062b4957e229373fad8f9 Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Fri, 7 Aug 2026 15:47:54 -0700 Subject: [PATCH 05/16] Gemini code review --- script/tool/lib/src/test_dart_fixes.dart | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/script/tool/lib/src/test_dart_fixes.dart b/script/tool/lib/src/test_dart_fixes.dart index f8f8e7d0c091..4b1bd29633d1 100644 --- a/script/tool/lib/src/test_dart_fixes.dart +++ b/script/tool/lib/src/test_dart_fixes.dart @@ -6,7 +6,6 @@ import 'dart:async'; import 'dart:io'; import 'package:file/file.dart'; -import 'package:file/local.dart'; import 'package:io/io.dart' as io; import 'package:path/path.dart' as p; @@ -52,7 +51,7 @@ class TestDartFixes extends PackageLoopingCommand { try { testDirectory = await _createTestDirectory(package); } catch (error) { - return PackageResult.fail(['Failed to create temporary test directory: $error}']); + return PackageResult.fail(['Failed to create temporary test directory: $error']); } late final PackageResult result; @@ -63,7 +62,7 @@ class TestDartFixes extends PackageLoopingCommand { } result = PackageResult.success(); } catch (error) { - result = PackageResult.fail(['Dart fix tests failed: $error}']); + result = PackageResult.fail(['Dart fix tests failed: $error']); } if (testDirectory.existsSync()) { await testDirectory.delete(recursive: true); @@ -77,7 +76,7 @@ class TestDartFixes extends PackageLoopingCommand { /// It is the responsibility of the caller to delete this directory and its /// contents when done. static Future _createTestDirectory(RepositoryPackage package) async { - const fileSystem = LocalFileSystem(); + final FileSystem fileSystem = package.directory.fileSystem; final Directory testTempDirectory = await fileSystem.systemTempDirectory.createTemp(); // Copy from `test_fixes/` to the temp directory. @@ -110,12 +109,12 @@ dependencies: /// Run the dart fix tests for the package in the given temporary directory. /// /// Resolves with the status code of the command. - static Future _runDartFixTests(RepositoryPackage package, Directory testDirectory) async { + Future _runDartFixTests(RepositoryPackage package, Directory testDirectory) async { // Run dart pub get in the temp directory to set it up. final int pubGetStatusCode = await _runProcess('dart', [ 'pub', 'get', - ], workingDirectory: testDirectory.path); + ], workingDirectory: testDirectory); if (pubGetStatusCode != 0) { return pubGetStatusCode; @@ -125,16 +124,16 @@ dependencies: return _runProcess('dart', [ 'fix', '--compare-to-golden', - ], workingDirectory: testDirectory.path); + ], workingDirectory: testDirectory); } - static Future _runProcess( + Future _runProcess( String command, List arguments, { - String? workingDirectory, + Directory? workingDirectory, }) async { final Process process = await _streamOutput( - Process.start(command, arguments, workingDirectory: workingDirectory), + processRunner.start(command, arguments, workingDirectory: workingDirectory), ); return process.exitCode; } From 91670248605c901c5af1069d9a117d2ce7f6b70e Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Wed, 12 Aug 2026 13:59:22 -0700 Subject: [PATCH 06/16] flutter pub get, not dart --- script/tool/lib/src/test_dart_fixes.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/script/tool/lib/src/test_dart_fixes.dart b/script/tool/lib/src/test_dart_fixes.dart index 4b1bd29633d1..48ef62371193 100644 --- a/script/tool/lib/src/test_dart_fixes.dart +++ b/script/tool/lib/src/test_dart_fixes.dart @@ -110,8 +110,8 @@ dependencies: /// /// Resolves with the status code of the command. Future _runDartFixTests(RepositoryPackage package, Directory testDirectory) async { - // Run dart pub get in the temp directory to set it up. - final int pubGetStatusCode = await _runProcess('dart', [ + // Run flutter pub get in the temp directory to set it up. + final int pubGetStatusCode = await _runProcess('flutter', [ 'pub', 'get', ], workingDirectory: testDirectory); From f7232b73748b041d0fa935f510f549947013094f Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Wed, 12 Aug 2026 14:06:55 -0700 Subject: [PATCH 07/16] Add to CI unit tests, under the assumption that the ~40s running time is not too long --- .ci/targets/dart_unit_tests.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.ci/targets/dart_unit_tests.yaml b/.ci/targets/dart_unit_tests.yaml index 6c5d6c4288d6..c2a1281d2f39 100644 --- a/.ci/targets/dart_unit_tests.yaml +++ b/.ci/targets/dart_unit_tests.yaml @@ -11,3 +11,6 @@ tasks: - name: Dart unit tests - pathified script: .ci/scripts/dart_unit_tests_pathified.sh args: ["--platform=vm"] + - name: Dart fix tests + script: script/tool/bin/flutter_plugin_tools.dart + args: ["test-dart-fixes"] From de6d383360f2677a0f5853b8a2c97cb51ae9c6e8 Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Wed, 12 Aug 2026 14:20:11 -0700 Subject: [PATCH 08/16] Command in name --- script/tool/lib/src/main.dart | 2 +- .../{test_dart_fixes.dart => test_dart_fixes_command.dart} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename script/tool/lib/src/{test_dart_fixes.dart => test_dart_fixes_command.dart} (96%) diff --git a/script/tool/lib/src/main.dart b/script/tool/lib/src/main.dart index 55b3e9d5e204..22018b18e342 100644 --- a/script/tool/lib/src/main.dart +++ b/script/tool/lib/src/main.dart @@ -76,7 +76,7 @@ void main(List args) { ..addCommand(PublishCheckCommand(packagesDir)) ..addCommand(PublishCommand(packagesDir)) ..addCommand(RemoveDevDependenciesCommand(packagesDir)) - ..addCommand(TestDartFixes(packagesDir)) + ..addCommand(TestDartFixesCommand(packagesDir)) ..addCommand(UpdateDependencyCommand(packagesDir)) ..addCommand(UpdateExcerptsCommand(packagesDir)) ..addCommand(UpdateMinSdkCommand(packagesDir)) diff --git a/script/tool/lib/src/test_dart_fixes.dart b/script/tool/lib/src/test_dart_fixes_command.dart similarity index 96% rename from script/tool/lib/src/test_dart_fixes.dart rename to script/tool/lib/src/test_dart_fixes_command.dart index 48ef62371193..5dc69c47e9de 100644 --- a/script/tool/lib/src/test_dart_fixes.dart +++ b/script/tool/lib/src/test_dart_fixes_command.dart @@ -15,9 +15,9 @@ import 'common/repository_package.dart'; /// A command to run dart fix tests for packages that have a test_fixes /// directory. -class TestDartFixes extends PackageLoopingCommand { +class TestDartFixesCommand extends PackageLoopingCommand { /// Creates an instance of the test dart fixes command. - TestDartFixes(super.packagesDir, {super.processRunner, super.platform, super.gitDir}); + TestDartFixesCommand(super.packagesDir, {super.processRunner, super.platform, super.gitDir}); @override final String name = 'test-dart-fixes'; From 08a076fc728179e3dd194479db053c2d1bc886ef Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Wed, 12 Aug 2026 14:49:28 -0700 Subject: [PATCH 09/16] Copy in memory temp directory properly. --- script/tool/lib/src/test_dart_fixes_command.dart | 12 ++++++++++-- script/tool/pubspec.yaml | 1 - 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/script/tool/lib/src/test_dart_fixes_command.dart b/script/tool/lib/src/test_dart_fixes_command.dart index 5dc69c47e9de..46293cf1a82b 100644 --- a/script/tool/lib/src/test_dart_fixes_command.dart +++ b/script/tool/lib/src/test_dart_fixes_command.dart @@ -6,7 +6,6 @@ import 'dart:async'; import 'dart:io'; import 'package:file/file.dart'; -import 'package:io/io.dart' as io; import 'package:path/path.dart' as p; import 'common/file_filters.dart'; @@ -80,7 +79,16 @@ class TestDartFixesCommand extends PackageLoopingCommand { final Directory testTempDirectory = await fileSystem.systemTempDirectory.createTemp(); // Copy from `test_fixes/` to the temp directory. - await io.copyPath(package.dartFixTestDirectory.path, testTempDirectory.path); + for (final FileSystemEntity entity in package.dartFixTestDirectory.listSync(recursive: true)) { + final String relativePath = p.relative(entity.path, from: package.dartFixTestDirectory.path); + final String destPath = p.join(testTempDirectory.path, relativePath); + if (entity is Directory) { + fileSystem.directory(destPath).createSync(recursive: true); + } else if (entity is File) { + fileSystem.file(destPath).parent.createSync(recursive: true); + entity.copySync(destPath); + } + } // The pubspec.yaml file to create. final File targetPubspecFile = fileSystem.file(p.join(testTempDirectory.path, 'pubspec.yaml')); diff --git a/script/tool/pubspec.yaml b/script/tool/pubspec.yaml index 94286da51efa..957b1ebe343d 100644 --- a/script/tool/pubspec.yaml +++ b/script/tool/pubspec.yaml @@ -13,7 +13,6 @@ dependencies: glob: ^2.1.3 http: ^1.0.0 http_multi_server: ^3.0.1 - io: ^1.0.5 meta: ^1.10.0 path: ^1.8.3 platform: ^3.0.2 From 9173808290701620fca0e0f00cfe5b293c3fd86c Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Wed, 12 Aug 2026 14:57:21 -0700 Subject: [PATCH 10/16] Variable name consistency --- script/tool/lib/src/test_dart_fixes_command.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/script/tool/lib/src/test_dart_fixes_command.dart b/script/tool/lib/src/test_dart_fixes_command.dart index 46293cf1a82b..b7c5d521ba11 100644 --- a/script/tool/lib/src/test_dart_fixes_command.dart +++ b/script/tool/lib/src/test_dart_fixes_command.dart @@ -76,12 +76,12 @@ class TestDartFixesCommand extends PackageLoopingCommand { /// contents when done. static Future _createTestDirectory(RepositoryPackage package) async { final FileSystem fileSystem = package.directory.fileSystem; - final Directory testTempDirectory = await fileSystem.systemTempDirectory.createTemp(); + final Directory testDirectory = await fileSystem.systemTempDirectory.createTemp(); // Copy from `test_fixes/` to the temp directory. for (final FileSystemEntity entity in package.dartFixTestDirectory.listSync(recursive: true)) { final String relativePath = p.relative(entity.path, from: package.dartFixTestDirectory.path); - final String destPath = p.join(testTempDirectory.path, relativePath); + final String destPath = p.join(testDirectory.path, relativePath); if (entity is Directory) { fileSystem.directory(destPath).createSync(recursive: true); } else if (entity is File) { @@ -91,7 +91,7 @@ class TestDartFixesCommand extends PackageLoopingCommand { } // The pubspec.yaml file to create. - final File targetPubspecFile = fileSystem.file(p.join(testTempDirectory.path, 'pubspec.yaml')); + final File targetPubspecFile = fileSystem.file(p.join(testDirectory.path, 'pubspec.yaml')); final targetYaml = ''' @@ -111,7 +111,7 @@ dependencies: '''; await targetPubspecFile.writeAsString(targetYaml); - return testTempDirectory; + return testDirectory; } /// Run the dart fix tests for the package in the given temporary directory. From 1e588048572cda83c641485bbd7cad623c2551e9 Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Wed, 12 Aug 2026 15:47:42 -0700 Subject: [PATCH 11/16] Working tests --- .../tool/lib/src/test_dart_fixes_command.dart | 6 + .../test/test_dart_fixes_command_test.dart | 160 ++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 script/tool/test/test_dart_fixes_command_test.dart diff --git a/script/tool/lib/src/test_dart_fixes_command.dart b/script/tool/lib/src/test_dart_fixes_command.dart index b7c5d521ba11..ef31234a1d26 100644 --- a/script/tool/lib/src/test_dart_fixes_command.dart +++ b/script/tool/lib/src/test_dart_fixes_command.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:io'; import 'package:file/file.dart'; +import 'package:meta/meta.dart'; import 'package:path/path.dart' as p; import 'common/file_filters.dart'; @@ -18,6 +19,10 @@ class TestDartFixesCommand extends PackageLoopingCommand { /// Creates an instance of the test dart fixes command. TestDartFixesCommand(super.packagesDir, {super.processRunner, super.platform, super.gitDir}); + /// A map of the test directory used for each package passed to runForPackage. + @visibleForTesting + final testDirectories = {}; + @override final String name = 'test-dart-fixes'; @@ -66,6 +71,7 @@ class TestDartFixesCommand extends PackageLoopingCommand { if (testDirectory.existsSync()) { await testDirectory.delete(recursive: true); } + testDirectories[package.displayName] = testDirectory; return result; } diff --git a/script/tool/test/test_dart_fixes_command_test.dart b/script/tool/test/test_dart_fixes_command_test.dart new file mode 100644 index 000000000000..7794a3a261e4 --- /dev/null +++ b/script/tool/test/test_dart_fixes_command_test.dart @@ -0,0 +1,160 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:args/command_runner.dart'; +import 'package:file/file.dart'; +import 'package:flutter_plugin_tools/src/common/core.dart'; +import 'package:flutter_plugin_tools/src/test_dart_fixes_command.dart'; +import 'package:git/git.dart'; +import 'package:platform/platform.dart'; +import 'package:test/test.dart'; + +import 'mocks.dart'; +import 'util.dart'; + +void main() { + group('TestDartFixesCommand', () { + late Platform mockPlatform; + late Directory packagesDir; + late CommandRunner runner; + late RecordingProcessRunner processRunner; + late TestDartFixesCommand command; + + setUp(() { + mockPlatform = MockPlatform(); + final GitDir gitDir; + (:packagesDir, :processRunner, gitProcessRunner: _, :gitDir) = configureBaseCommandMocks( + platform: mockPlatform, + ); + command = TestDartFixesCommand( + packagesDir, + processRunner: processRunner, + platform: mockPlatform, + gitDir: gitDir, + ); + + runner = CommandRunner('test_dart_fixes', 'Test for $TestDartFixesCommand'); + runner.addCommand(command); + }); + + test('runs on each package with a test_fixes directory', () async { + final RepositoryPackage package1 = createFakePackage( + 'package1', + packagesDir, + examples: [], + extraFiles: ['test_fixes/empty.dart', 'test_fixes/empty.dart.expect'], + ); + final RepositoryPackage package2 = createFakePackage( + 'package2', + packagesDir, + examples: [], + extraFiles: ['test_fixes/empty.dart', 'test_fixes/empty.dart.expect'], + ); + createFakePackage('package3', packagesDir, examples: []); + + final List output = await runCapturingPrint(runner, ['test-dart-fixes']); + + expect( + output, + containsAllInOrder([ + contains('Running for package1'), + contains('Running for package2'), + contains('Running for package3'), + contains("SKIPPING: No MemoryDirectory: '/packages/package3/test_fixes' directory."), + ]), + ); + + expect( + processRunner.recordedCalls, + orderedEquals([ + ProcessCall(getFlutterCommand(mockPlatform), const [ + 'pub', + 'get', + ], command.testDirectories[package1.displayName]!.path), + ProcessCall('dart', const [ + 'fix', + '--compare-to-golden', + ], command.testDirectories[package1.displayName]!.path), + ProcessCall(getFlutterCommand(mockPlatform), const [ + 'pub', + 'get', + ], command.testDirectories[package2.displayName]!.path), + ProcessCall('dart', const [ + 'fix', + '--compare-to-golden', + ], command.testDirectories[package2.displayName]!.path), + ]), + ); + }); + + test('fails when dart fix test fails', () async { + final RepositoryPackage packageThatFails = createFakePackage( + 'package1', + packagesDir, + examples: [], + extraFiles: ['test_fixes/fix.dart', 'test_fixes/fix.dart.expect'], + ); + packageThatFails.dartFixTestDirectory.childFile('fix.dart').writeAsStringSync('a'); + packageThatFails.dartFixTestDirectory.childFile('fix.dart.expect').writeAsStringSync('b'); + + final RepositoryPackage packageThatSucceeds = createFakePackage( + 'package2', + packagesDir, + examples: [], + extraFiles: ['test_fixes/fix.dart', 'test_fixes/fix.dart.expect'], + ); + packageThatSucceeds.dartFixTestDirectory.childFile('fix.dart').writeAsStringSync('c'); + packageThatSucceeds.dartFixTestDirectory.childFile('fix.dart.expect').writeAsStringSync('c'); + + processRunner.mockProcessesForExecutable['dart'] = [ + FakeProcessInfo(_MockDartFixProcess(processRunner, packagesDir.fileSystem), const [ + 'fix', + '--compare-to-golden', + ]), + FakeProcessInfo(_MockDartFixProcess(processRunner, packagesDir.fileSystem), const [ + 'fix', + '--compare-to-golden', + ]), + ]; + + Error? commandError; + final List output = await runCapturingPrint( + runner, + ['test-dart-fixes'], + errorHandler: (e) { + commandError = e; + }, + ); + + expect(commandError, isA()); + expect( + output, + containsAllInOrder([ + contains('The following packages had errors:'), + contains(' package1'), + isNot(contains('package2')), + ]), + ); + }); + }); +} + +/// Fails when fix.dart does not equal fix.dart.expect. +class _MockDartFixProcess extends MockProcess { + _MockDartFixProcess(this.processRunner, this.fileSystem); + final RecordingProcessRunner processRunner; + final FileSystem fileSystem; + + @override + Future get exitCode async { + final ProcessCall call = processRunner.recordedCalls.last; + final String? workingDir = call.workingDir; + final File fixDart = fileSystem.file(fileSystem.path.join(workingDir!, 'fix.dart')); + final File fixExpect = fileSystem.file(fileSystem.path.join(workingDir, 'fix.dart.expect')); + if (!fixDart.existsSync() || !fixExpect.existsSync()) { + return 1; + } + return fixDart.readAsStringSync() == fixExpect.readAsStringSync() ? 0 : 1; + } +} From e553f85a67f4ef2add6babc99578b5f3b7f6d017 Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Wed, 12 Aug 2026 16:02:19 -0700 Subject: [PATCH 12/16] Fixes from stuart's code review --- script/tool/lib/src/main.dart | 2 +- .../tool/lib/src/test_dart_fixes_command.dart | 44 ++++++------------- 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/script/tool/lib/src/main.dart b/script/tool/lib/src/main.dart index 22018b18e342..48bc4bf4f6bb 100644 --- a/script/tool/lib/src/main.dart +++ b/script/tool/lib/src/main.dart @@ -30,7 +30,7 @@ import 'podspec_check_command.dart'; import 'publish_check_command.dart'; import 'publish_command.dart'; import 'remove_dev_dependencies_command.dart'; -import 'test_dart_fixes.dart'; +import 'test_dart_fixes_command.dart'; import 'update_dependency_command.dart'; import 'update_excerpts_command.dart'; import 'update_min_sdk_command.dart'; diff --git a/script/tool/lib/src/test_dart_fixes_command.dart b/script/tool/lib/src/test_dart_fixes_command.dart index ef31234a1d26..914dafdfbe32 100644 --- a/script/tool/lib/src/test_dart_fixes_command.dart +++ b/script/tool/lib/src/test_dart_fixes_command.dart @@ -11,6 +11,7 @@ import 'package:path/path.dart' as p; import 'common/file_filters.dart'; import 'common/package_looping_command.dart'; +import 'common/pub_utils.dart'; import 'common/repository_package.dart'; /// A command to run dart fix tests for packages that have a test_fixes @@ -35,7 +36,7 @@ class TestDartFixesCommand extends PackageLoopingCommand { 'This command requires "flutter" to be in your path.'; @override - PackageLoopingType get packageLoopingType => PackageLoopingType.includeAllSubpackages; + PackageLoopingType get packageLoopingType => PackageLoopingType.topLevelOnly; @override bool shouldIgnoreFile(String path) { @@ -58,11 +59,11 @@ class TestDartFixesCommand extends PackageLoopingCommand { return PackageResult.fail(['Failed to create temporary test directory: $error']); } - late final PackageResult result; + PackageResult result; try { - final int statusCode = await _runDartFixTests(package, testDirectory); - if (statusCode != 0) { - throw Exception('Status code $statusCode'); + final bool success = await _runDartFixTests(testDirectory); + if (!success) { + throw Exception('Failed to run dart fix tests.'); } result = PackageResult.success(); } catch (error) { @@ -123,39 +124,20 @@ dependencies: /// Run the dart fix tests for the package in the given temporary directory. /// /// Resolves with the status code of the command. - Future _runDartFixTests(RepositoryPackage package, Directory testDirectory) async { + Future _runDartFixTests(Directory testDirectory) async { // Run flutter pub get in the temp directory to set it up. - final int pubGetStatusCode = await _runProcess('flutter', [ - 'pub', - 'get', - ], workingDirectory: testDirectory); + final bool success = await runPubGet(RepositoryPackage(testDirectory), processRunner, platform); - if (pubGetStatusCode != 0) { - return pubGetStatusCode; + if (!success) { + return success; } // Run dart fix --compare-to-golden in the temp directory. - return _runProcess('dart', [ + final int exitCode = await processRunner.runAndStream('dart', [ 'fix', '--compare-to-golden', - ], workingDirectory: testDirectory); - } - - Future _runProcess( - String command, - List arguments, { - Directory? workingDirectory, - }) async { - final Process process = await _streamOutput( - processRunner.start(command, arguments, workingDirectory: workingDirectory), - ); - return process.exitCode; - } + ], workingDir: testDirectory); - static Future _streamOutput(Future processFuture) async { - final Process process = await processFuture; - unawaited(stdout.addStream(process.stdout)); - unawaited(stderr.addStream(process.stderr)); - return process; + return exitCode == 0; } } From a9cbde22896854504c51986b209b35579bf65615 Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Wed, 12 Aug 2026 16:06:29 -0700 Subject: [PATCH 13/16] Unused import --- script/tool/lib/src/test_dart_fixes_command.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/script/tool/lib/src/test_dart_fixes_command.dart b/script/tool/lib/src/test_dart_fixes_command.dart index 914dafdfbe32..7c4f4061d9c8 100644 --- a/script/tool/lib/src/test_dart_fixes_command.dart +++ b/script/tool/lib/src/test_dart_fixes_command.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:io'; import 'package:file/file.dart'; import 'package:meta/meta.dart'; From 94e7a119da99cbdbc87ca71674b79799f89ffaa9 Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Thu, 13 Aug 2026 09:27:14 -0700 Subject: [PATCH 14/16] Fix CI execution of dart fix tests --- .ci/targets/dart_unit_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/targets/dart_unit_tests.yaml b/.ci/targets/dart_unit_tests.yaml index c2a1281d2f39..291c4e1c1102 100644 --- a/.ci/targets/dart_unit_tests.yaml +++ b/.ci/targets/dart_unit_tests.yaml @@ -12,5 +12,5 @@ tasks: script: .ci/scripts/dart_unit_tests_pathified.sh args: ["--platform=vm"] - name: Dart fix tests - script: script/tool/bin/flutter_plugin_tools.dart + script: .ci/scripts/tool_runner.sh args: ["test-dart-fixes"] From aa09ca3a2a406a1ee339e59cf475ef66cd7f2ed5 Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Thu, 13 Aug 2026 09:34:44 -0700 Subject: [PATCH 15/16] Fix for Windows by using FileSystem not Path --- script/tool/lib/src/test_dart_fixes_command.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/script/tool/lib/src/test_dart_fixes_command.dart b/script/tool/lib/src/test_dart_fixes_command.dart index 7c4f4061d9c8..256cdaafb1e4 100644 --- a/script/tool/lib/src/test_dart_fixes_command.dart +++ b/script/tool/lib/src/test_dart_fixes_command.dart @@ -6,7 +6,6 @@ import 'dart:async'; import 'package:file/file.dart'; import 'package:meta/meta.dart'; -import 'package:path/path.dart' as p; import 'common/file_filters.dart'; import 'common/package_looping_command.dart'; @@ -86,8 +85,8 @@ class TestDartFixesCommand extends PackageLoopingCommand { // Copy from `test_fixes/` to the temp directory. for (final FileSystemEntity entity in package.dartFixTestDirectory.listSync(recursive: true)) { - final String relativePath = p.relative(entity.path, from: package.dartFixTestDirectory.path); - final String destPath = p.join(testDirectory.path, relativePath); + final String relativePath = fileSystem.path.relative(entity.path, from: package.dartFixTestDirectory.path); + final String destPath = fileSystem.path.join(testDirectory.path, relativePath); if (entity is Directory) { fileSystem.directory(destPath).createSync(recursive: true); } else if (entity is File) { @@ -97,7 +96,7 @@ class TestDartFixesCommand extends PackageLoopingCommand { } // The pubspec.yaml file to create. - final File targetPubspecFile = fileSystem.file(p.join(testDirectory.path, 'pubspec.yaml')); + final File targetPubspecFile = fileSystem.file(fileSystem.path.join(testDirectory.path, 'pubspec.yaml')); final targetYaml = ''' From 143f2b9f2301fc5222c230926ed41cd9961d4ade Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Thu, 13 Aug 2026 09:46:06 -0700 Subject: [PATCH 16/16] Formatting --- script/tool/lib/src/test_dart_fixes_command.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/script/tool/lib/src/test_dart_fixes_command.dart b/script/tool/lib/src/test_dart_fixes_command.dart index 256cdaafb1e4..53d2e771dd2c 100644 --- a/script/tool/lib/src/test_dart_fixes_command.dart +++ b/script/tool/lib/src/test_dart_fixes_command.dart @@ -85,7 +85,10 @@ class TestDartFixesCommand extends PackageLoopingCommand { // Copy from `test_fixes/` to the temp directory. for (final FileSystemEntity entity in package.dartFixTestDirectory.listSync(recursive: true)) { - final String relativePath = fileSystem.path.relative(entity.path, from: package.dartFixTestDirectory.path); + final String relativePath = fileSystem.path.relative( + entity.path, + from: package.dartFixTestDirectory.path, + ); final String destPath = fileSystem.path.join(testDirectory.path, relativePath); if (entity is Directory) { fileSystem.directory(destPath).createSync(recursive: true); @@ -96,7 +99,9 @@ class TestDartFixesCommand extends PackageLoopingCommand { } // The pubspec.yaml file to create. - final File targetPubspecFile = fileSystem.file(fileSystem.path.join(testDirectory.path, 'pubspec.yaml')); + final File targetPubspecFile = fileSystem.file( + fileSystem.path.join(testDirectory.path, 'pubspec.yaml'), + ); final targetYaml = '''