diff --git a/.ci.yaml b/.ci.yaml index 14d1e16cd7de..6cc325b985fe 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -281,15 +281,6 @@ targets: {"dependency": "vs_build", "version": "version:vs2019"} ] - - name: Windows plugin_tools_tests - recipe: plugins/plugins - timeout: 30 - properties: - add_recipes_cq: "true" - target_file: plugin_tools_tests.yaml - channel: master - version_file: flutter_master.version - - name: Linux ci_yaml plugins roller recipe: infra/ci_yaml timeout: 30 diff --git a/.ci/scripts/build_examples_win32.sh b/.ci/scripts/build_examples_win32.sh index bcf57a4b311f..ff30ca93eec1 100644 --- a/.ci/scripts/build_examples_win32.sh +++ b/.ci/scripts/build_examples_win32.sh @@ -3,5 +3,5 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -dart ./script/tool/bin/flutter_plugin_tools.dart build-examples --windows \ +dart pub global run flutter_plugin_tools build-examples --windows \ --packages-for-branch --log-timing diff --git a/.ci/scripts/create_all_plugins_app.sh b/.ci/scripts/create_all_plugins_app.sh index 8399e5e38a35..8c45a351bef4 100644 --- a/.ci/scripts/create_all_plugins_app.sh +++ b/.ci/scripts/create_all_plugins_app.sh @@ -3,5 +3,5 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -dart ./script/tool/bin/flutter_plugin_tools.dart create-all-packages-app \ +dart pub global run flutter_plugin_tools create-all-packages-app \ --output-dir=. --exclude script/configs/exclude_all_packages_app.yaml diff --git a/.ci/scripts/drive_examples_win32.sh b/.ci/scripts/drive_examples_win32.sh index c3e2e7bc5447..d06c192ab551 100644 --- a/.ci/scripts/drive_examples_win32.sh +++ b/.ci/scripts/drive_examples_win32.sh @@ -3,5 +3,5 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -dart ./script/tool/bin/flutter_plugin_tools.dart drive-examples --windows \ +dart pub global run flutter_plugin_tools drive-examples --windows \ --exclude=script/configs/exclude_integration_win32.yaml --packages-for-branch --log-timing diff --git a/.ci/scripts/native_test_win32.sh b/.ci/scripts/native_test_win32.sh index 37cf54e55c5c..7bfe84022487 100644 --- a/.ci/scripts/native_test_win32.sh +++ b/.ci/scripts/native_test_win32.sh @@ -3,5 +3,5 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -dart ./script/tool/bin/flutter_plugin_tools.dart native-test --windows \ +dart pub global run flutter_plugin_tools native-test --windows \ --no-integration --packages-for-branch --log-timing diff --git a/.ci/scripts/plugin_tools_tests.sh b/.ci/scripts/plugin_tools_tests.sh deleted file mode 100644 index 96eec4349f08..000000000000 --- a/.ci/scripts/plugin_tools_tests.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -# 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. - -cd script/tool -dart pub run test diff --git a/.ci/scripts/prepare_tool.sh b/.ci/scripts/prepare_tool.sh index f93694bf1ff6..aced1517760c 100755 --- a/.ci/scripts/prepare_tool.sh +++ b/.ci/scripts/prepare_tool.sh @@ -6,5 +6,6 @@ # To set FETCH_HEAD for "git merge-base" to work git fetch origin main -cd script/tool -dart pub get +# Pinned version of the plugin tools, to avoid breakage in this repository +# when pushing updates from flutter/packages. +dart pub global activate flutter_plugin_tools 0.13.4+3 diff --git a/.ci/targets/plugin_tools_tests.yaml b/.ci/targets/plugin_tools_tests.yaml deleted file mode 100644 index 265e74bdd06b..000000000000 --- a/.ci/targets/plugin_tools_tests.yaml +++ /dev/null @@ -1,5 +0,0 @@ -tasks: - - name: prepare tool - script: .ci/scripts/prepare_tool.sh - - name: tool unit tests - script: .ci/scripts/plugin_tools_tests.sh diff --git a/.cirrus.yml b/.cirrus.yml index a5292c98f209..e9d513bf5d45 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -4,7 +4,7 @@ gcp_credentials: ENCRYPTED[!3a93d98d7c95a41f5033834ef30e50928fc5d81239dc632b153c only_if: $CIRRUS_TAG == '' && ($CIRRUS_PR != '' || $CIRRUS_BRANCH == 'main') env: CHANNEL: "master" # Default to master when not explicitly set by a task. - PLUGIN_TOOL_COMMAND: "dart ./script/tool/bin/flutter_plugin_tools.dart" + PLUGIN_TOOL_COMMAND: "dart pub global run flutter_plugin_tools" install_chrome_linux_template: &INSTALL_CHROME_LINUX env: @@ -77,10 +77,6 @@ task: namespace: default matrix: ### Platform-agnostic tasks ### - - name: Linux plugin_tools_tests - script: - - cd script/tool - - dart pub run test # Repository rules and best-practice enforcement. # Only channel-agnostic tests should go here since it is only run once # (on Flutter master). @@ -124,9 +120,6 @@ task: matrix: CHANNEL: "master" CHANNEL: "stable" - analyze_tool_script: - - cd script/tool - - dart analyze --fatal-infos analyze_script: # DO NOT change the custom-analysis argument here without changing the Dart repo. # See the comment in script/configs/custom_analysis.yaml for details. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bbb153386ff2..532987f931df 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,8 +31,7 @@ jobs: with: fetch-depth: 0 # Fetch all history so the tool can get all the tags to determine version. - name: Set up tools - run: dart pub get - working-directory: ${{ github.workspace }}/script/tool + run: dart pub global activate flutter_plugin_tools 0.13.4+3 # This workflow should be the last to run. So wait for all the other tests to succeed. - name: Wait on all tests @@ -50,5 +49,5 @@ jobs: run: | git config --global user.name ${{ secrets.USER_NAME }} git config --global user.email ${{ secrets.USER_EMAIL }} - dart ./script/tool/lib/src/main.dart publish --all-changed --base-sha=HEAD~ --skip-confirmation --remote=origin + dart pub global run flutter_plugin_tools publish --all-changed --base-sha=HEAD~ --skip-confirmation --remote=origin env: {PUB_CREDENTIALS: "${{ secrets.PUB_CREDENTIALS }}"} diff --git a/script/tool/README.md b/script/tool/README.md index 9f0ac84145f2..aa4c0517ce71 100644 --- a/script/tool/README.md +++ b/script/tool/README.md @@ -1,191 +1,13 @@ -# Flutter Plugin Tools +# Removed -This is a set of utilities used in the flutter/plugins and flutter/packages -repositories. It is no longer explictily maintained as a general-purpose tool -for multi-package repositories, so your mileage may vary if using it in other -repositories. +See https://github.com/flutter/packages/blob/main/script/tool/README.md for the +current location of this tooling. -Note: The commands in tools are designed to run at the root of the repository or `/packages/`. +## Temporary shim -## Getting Started +This is a temporary, minimal version of the tools sufficient to keep the +following scripts running until the repository merge is complete and they are +updated to use flutter/packages instead: -In flutter/plugins, the tool is run from source. In flutter/packages, the -[published version](https://pub.dev/packages/flutter_plugin_tools) is used -instead. (It is marked as Discontinued since it is no longer maintained as -a general-purpose tool, but updates are still published for use in -flutter/packages.) - -The commands in tools require the Flutter-bundled version of Dart to be the first `dart` loaded in the path. - -### Extra Setup - -When updating sample code excerpts (`update-excerpts`) for the README.md files, -there is some [extra setup for -submodules](#update-readmemd-from-example-sources) that is necessary. - -### From Source (flutter/plugins only) - -Set up: - -```sh -cd ./script/tool && dart pub get && cd ../../ -``` - -Run: - -```sh -dart run ./script/tool/bin/flutter_plugin_tools.dart -``` - -### Published Version - -Set up: - -```sh -dart pub global activate flutter_plugin_tools -``` - -Run: - -```sh -dart pub global run flutter_plugin_tools -``` - -## Commands - -Run with `--help` for a full list of commands and arguments, but the -following shows a number of common commands being run for a specific package. - -All examples assume running from source; see above for running the -published version instead. - -Most commands take a `--packages` argument to control which package(s) the -command is targetting. An package name can be any of: -- The name of a package (e.g., `path_provider_android`). -- The name of a federated plugin (e.g., `path_provider`), in which case all - packages that make up that plugin will be targetted. -- A combination federated_plugin_name/package_name (e.g., - `path_provider/path_provider` for the app-facing package). - -### Format Code - -```sh -cd -dart run ./script/tool/bin/flutter_plugin_tools.dart format --packages package_name -``` - -### Run the Dart Static Analyzer - -```sh -cd -dart run ./script/tool/bin/flutter_plugin_tools.dart analyze --packages package_name -``` - -### Run Dart Unit Tests - -```sh -cd -dart run ./script/tool/bin/flutter_plugin_tools.dart test --packages package_name -``` - -### Run Dart Integration Tests - -```sh -cd -dart run ./script/tool/bin/flutter_plugin_tools.dart build-examples --apk --packages package_name -dart run ./script/tool/bin/flutter_plugin_tools.dart drive-examples --android --packages package_name -``` - -Replace `--apk`/`--android` with the platform you want to test against -(omit it to get a list of valid options). - -### Run Native Tests - -`native-test` takes one or more platform flags to run tests for. By default it -runs both unit tests and (on platforms that support it) integration tests, but -`--no-unit` or `--no-integration` can be used to run just one type. - -Examples: - -```sh -cd -# Run just unit tests for iOS and Android: -dart run ./script/tool/bin/flutter_plugin_tools.dart native-test --ios --android --no-integration --packages package_name -# Run all tests for macOS: -dart run ./script/tool/bin/flutter_plugin_tools.dart native-test --macos --packages package_name -# Run all tests for Windows: -dart run ./script/tool/bin/flutter_plugin_tools.dart native-test --windows --packages package_name -``` - -### Update README.md from Example Sources - -`update-excerpts` requires sources that are in a submodule. If you didn't clone -with submodules, you will need to `git submodule update --init --recursive` -before running this command. - -```sh -cd -dart run ./script/tool/bin/flutter_plugin_tools.dart update-excerpts --packages package_name -``` - -### Update CHANGELOG and Version - -`update-release-info` will automatically update the version and `CHANGELOG.md` -following standard repository style and practice. It can be used for -single-package updates to handle the details of getting the `CHANGELOG.md` -format correct, but is especially useful for bulk updates across multiple packages. - -For instance, if you add a new analysis option that requires production -code changes across many packages: - -```sh -cd -dart run ./script/tool/bin/flutter_plugin_tools.dart update-release-info \ - --version=minimal \ - --changelog="Fixes violations of new analysis option some_new_option." -``` - -The `minimal` option for `--version` will skip unchanged packages, and treat -each changed package as either `bugfix` or `next` depending on the files that -have changed in that package, so it is often the best choice for a bulk change. - -For cases where you know the change time, `minor` or `bugfix` will make the -corresponding version bump, or `next` will update only `CHANGELOG.md` without -changing the version. - -### Publish a Release - -**Releases are automated for `flutter/plugins` and `flutter/packages`.** - -The manual procedure described here is _deprecated_, and should only be used when -the automated process fails. Please, read -[Releasing a Plugin or Package](https://github.com/flutter/flutter/wiki/Releasing-a-Plugin-or-Package) -on the Flutter Wiki first. - -```sh -cd -git checkout -dart run ./script/tool/bin/flutter_plugin_tools.dart publish --packages -``` - -By default the tool tries to push tags to the `upstream` remote, but some -additional settings can be configured. Run `dart run ./script/tool/bin/flutter_plugin_tools.dart -publish --help` for more usage information. - -The tool wraps `pub publish` for pushing the package to pub, and then will -automatically use git to try to create and push tags. It has some additional -safety checking around `pub publish` too. By default `pub publish` publishes -_everything_, including untracked or uncommitted files in version control. -`publish` will first check the status of the local -directory and refuse to publish if there are any mismatched files with version -control present. - -## Updating the Tool - -For flutter/plugins, just changing the source here is all that's needed. - -For changes that are relevant to flutter/packages, you will also need to: -- Update the tool's pubspec.yaml and CHANGELOG -- Publish the tool -- Update the pinned version in - [flutter/packages](https://github.com/flutter/packages/blob/main/.cirrus.yml) +- [dart-lang analysis](https://github.com/dart-lang/sdk/blob/main/tools/bots/flutter/analyze_flutter_plugins.sh) +- [flutter/flutter analysis](https://github.com/flutter/flutter/blob/master/dev/bots/test.dart) diff --git a/script/tool/lib/src/analyze_command.dart b/script/tool/lib/src/analyze_command.dart index c7a953c50cac..3d9e4e5c9802 100644 --- a/script/tool/lib/src/analyze_command.dart +++ b/script/tool/lib/src/analyze_command.dart @@ -32,17 +32,9 @@ class AnalyzeCommand extends PackageLoopingCommand { valueHelp: 'dart-sdk', help: 'An optional path to a Dart SDK; this is used to override the ' 'SDK used to provide analysis.'); - argParser.addFlag(_downgradeFlag, - help: 'Runs "flutter pub downgrade" before analysis to verify that ' - 'the minimum constraints are sufficiently new for APIs used.'); - argParser.addFlag(_libOnlyFlag, - help: 'Only analyze the lib/ directory of the main package, not the ' - 'entire package.'); } static const String _customAnalysisFlag = 'custom-analysis'; - static const String _downgradeFlag = 'downgrade'; - static const String _libOnlyFlag = 'lib-only'; static const String _analysisSdk = 'analysis-sdk'; late String _dartBinaryPath; @@ -111,18 +103,6 @@ class AnalyzeCommand extends PackageLoopingCommand { @override Future runForPackage(RepositoryPackage package) async { - final bool libOnly = getBoolArg(_libOnlyFlag); - - if (libOnly && !package.libDirectory.existsSync()) { - return PackageResult.skip('No lib/ directory.'); - } - - if (getBoolArg(_downgradeFlag)) { - if (!await _runPubCommand(package, 'downgrade')) { - return PackageResult.fail(['Unable to downgrade dependencies']); - } - } - // Analysis runs over the package and all subpackages (unless only lib/ is // being analyzed), so all of them need `flutter pub get` run before // analyzing. `example` packages can be skipped since 'flutter packages get' @@ -130,7 +110,7 @@ class AnalyzeCommand extends PackageLoopingCommand { // directory. final List packagesToGet = [ package, - if (!libOnly) ...await getSubpackages(package).toList(), + ...await getSubpackages(package).toList(), ]; for (final RepositoryPackage packageToGet in packagesToGet) { if (packageToGet.directory.basename != 'example' || @@ -146,8 +126,8 @@ class AnalyzeCommand extends PackageLoopingCommand { if (_hasUnexpecetdAnalysisOptions(package)) { return PackageResult.fail(['Unexpected local analysis options']); } - final int exitCode = await processRunner.runAndStream(_dartBinaryPath, - ['analyze', '--fatal-infos', if (libOnly) 'lib'], + final int exitCode = await processRunner.runAndStream( + _dartBinaryPath, ['analyze', '--fatal-infos'], workingDir: package.directory); if (exitCode != 0) { return PackageResult.fail(); diff --git a/script/tool/lib/src/build_examples_command.dart b/script/tool/lib/src/build_examples_command.dart deleted file mode 100644 index 1aade3575559..000000000000 --- a/script/tool/lib/src/build_examples_command.dart +++ /dev/null @@ -1,314 +0,0 @@ -// 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 'dart:async'; - -import 'package:file/file.dart'; -import 'package:platform/platform.dart'; -import 'package:yaml/yaml.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/plugin_utils.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -/// Key for APK. -const String _platformFlagApk = 'apk'; - -const String _pluginToolsConfigFileName = '.pluginToolsConfig.yaml'; -const String _pluginToolsConfigBuildFlagsKey = 'buildFlags'; -const String _pluginToolsConfigGlobalKey = 'global'; - -const String _pluginToolsConfigExample = ''' -$_pluginToolsConfigBuildFlagsKey: - $_pluginToolsConfigGlobalKey: - - "--no-tree-shake-icons" - - "--dart-define=buildmode=testing" -'''; - -const int _exitNoPlatformFlags = 3; -const int _exitInvalidPluginToolsConfig = 4; - -// Flutter build types. These are the values passed to `flutter build `. -const String _flutterBuildTypeAndroid = 'apk'; -const String _flutterBuildTypeIOS = 'ios'; -const String _flutterBuildTypeLinux = 'linux'; -const String _flutterBuildTypeMacOS = 'macos'; -const String _flutterBuildTypeWeb = 'web'; -const String _flutterBuildTypeWindows = 'windows'; - -/// A command to build the example applications for packages. -class BuildExamplesCommand extends PackageLoopingCommand { - /// Creates an instance of the build command. - BuildExamplesCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : super(packagesDir, processRunner: processRunner, platform: platform) { - argParser.addFlag(platformLinux); - argParser.addFlag(platformMacOS); - argParser.addFlag(platformWeb); - argParser.addFlag(platformWindows); - argParser.addFlag(platformIOS); - argParser.addFlag(_platformFlagApk); - argParser.addOption( - kEnableExperiment, - defaultsTo: '', - help: 'Enables the given Dart SDK experiments.', - ); - } - - // Maps the switch this command uses to identify a platform to information - // about it. - static final Map _platforms = - { - _platformFlagApk: const _PlatformDetails( - 'Android', - pluginPlatform: platformAndroid, - flutterBuildType: _flutterBuildTypeAndroid, - ), - platformIOS: const _PlatformDetails( - 'iOS', - pluginPlatform: platformIOS, - flutterBuildType: _flutterBuildTypeIOS, - extraBuildFlags: ['--no-codesign'], - ), - platformLinux: const _PlatformDetails( - 'Linux', - pluginPlatform: platformLinux, - flutterBuildType: _flutterBuildTypeLinux, - ), - platformMacOS: const _PlatformDetails( - 'macOS', - pluginPlatform: platformMacOS, - flutterBuildType: _flutterBuildTypeMacOS, - ), - platformWeb: const _PlatformDetails( - 'web', - pluginPlatform: platformWeb, - flutterBuildType: _flutterBuildTypeWeb, - ), - platformWindows: const _PlatformDetails( - 'Windows', - pluginPlatform: platformWindows, - flutterBuildType: _flutterBuildTypeWindows, - ), - }; - - @override - final String name = 'build-examples'; - - @override - final String description = - 'Builds all example apps (IPA for iOS and APK for Android).\n\n' - 'This command requires "flutter" to be in your path.\n\n' - 'A $_pluginToolsConfigFileName file can be placed in an example app ' - 'directory to specify additional build arguments. It should be a YAML ' - 'file with a top-level map containing a single key ' - '"$_pluginToolsConfigBuildFlagsKey" containing a map containing a ' - 'single key "$_pluginToolsConfigGlobalKey" containing a list of build ' - 'arguments.'; - - @override - Future initializeRun() async { - final List platformFlags = _platforms.keys.toList(); - platformFlags.sort(); - if (!platformFlags.any((String platform) => getBoolArg(platform))) { - printError( - 'None of ${platformFlags.map((String platform) => '--$platform').join(', ')} ' - 'were specified. At least one platform must be provided.'); - throw ToolExit(_exitNoPlatformFlags); - } - } - - @override - Future runForPackage(RepositoryPackage package) async { - final List errors = []; - - final bool isPlugin = isFlutterPlugin(package); - final Iterable<_PlatformDetails> requestedPlatforms = _platforms.entries - .where( - (MapEntry entry) => getBoolArg(entry.key)) - .map((MapEntry entry) => entry.value); - - // Platform support is checked at the package level for plugins; there is - // no package-level platform information for non-plugin packages. - final Set<_PlatformDetails> buildPlatforms = isPlugin - ? requestedPlatforms - .where((_PlatformDetails platform) => - pluginSupportsPlatform(platform.pluginPlatform, package)) - .toSet() - : requestedPlatforms.toSet(); - - String platformDisplayList(Iterable<_PlatformDetails> platforms) { - return platforms.map((_PlatformDetails p) => p.label).join(', '); - } - - if (buildPlatforms.isEmpty) { - final String unsupported = requestedPlatforms.length == 1 - ? '${requestedPlatforms.first.label} is not supported' - : 'None of [${platformDisplayList(requestedPlatforms)}] are supported'; - return PackageResult.skip('$unsupported by this plugin'); - } - print('Building for: ${platformDisplayList(buildPlatforms)}'); - - final Set<_PlatformDetails> unsupportedPlatforms = - requestedPlatforms.toSet().difference(buildPlatforms); - if (unsupportedPlatforms.isNotEmpty) { - final List skippedPlatforms = unsupportedPlatforms - .map((_PlatformDetails platform) => platform.label) - .toList(); - skippedPlatforms.sort(); - print('Skipping unsupported platform(s): ' - '${skippedPlatforms.join(', ')}'); - } - print(''); - - bool builtSomething = false; - for (final RepositoryPackage example in package.getExamples()) { - final String packageName = - getRelativePosixPath(example.directory, from: packagesDir); - - for (final _PlatformDetails platform in buildPlatforms) { - // Repo policy is that a plugin must have examples configured for all - // supported platforms. For packages, just log and skip any requested - // platform that a package doesn't have set up. - if (!isPlugin && - !example.directory - .childDirectory(platform.flutterPlatformDirectory) - .existsSync()) { - print('Skipping ${platform.label} for $packageName; not supported.'); - continue; - } - - builtSomething = true; - - String buildPlatform = platform.label; - if (platform.label.toLowerCase() != platform.flutterBuildType) { - buildPlatform += ' (${platform.flutterBuildType})'; - } - print('\nBUILDING $packageName for $buildPlatform'); - if (!await _buildExample(example, platform.flutterBuildType, - extraBuildFlags: platform.extraBuildFlags)) { - errors.add('$packageName (${platform.label})'); - } - } - } - - if (!builtSomething) { - if (isPlugin) { - errors.add('No examples found'); - } else { - return PackageResult.skip( - 'No examples found supporting requested platform(s).'); - } - } - - return errors.isEmpty - ? PackageResult.success() - : PackageResult.fail(errors); - } - - Iterable _readExtraBuildFlagsConfiguration( - Directory directory) sync* { - final File pluginToolsConfig = - directory.childFile(_pluginToolsConfigFileName); - if (pluginToolsConfig.existsSync()) { - final Object? configuration = - loadYaml(pluginToolsConfig.readAsStringSync()); - if (configuration is! YamlMap) { - printError('The $_pluginToolsConfigFileName file must be a YAML map.'); - printError( - 'Currently, the key "$_pluginToolsConfigBuildFlagsKey" is the only one that has an effect.'); - printError( - 'It must itself be a map. Currently, in that map only the key "$_pluginToolsConfigGlobalKey"'); - printError( - 'has any effect; it must contain a list of arguments to pass to the'); - printError('flutter tool.'); - printError(_pluginToolsConfigExample); - throw ToolExit(_exitInvalidPluginToolsConfig); - } - if (configuration.containsKey(_pluginToolsConfigBuildFlagsKey)) { - final Object? buildFlagsConfiguration = - configuration[_pluginToolsConfigBuildFlagsKey]; - if (buildFlagsConfiguration is! YamlMap) { - printError( - 'The $_pluginToolsConfigFileName file\'s "$_pluginToolsConfigBuildFlagsKey" key must be a map.'); - printError( - 'Currently, in that map only the key "$_pluginToolsConfigGlobalKey" has any effect; it must '); - printError( - 'contain a list of arguments to pass to the flutter tool.'); - printError(_pluginToolsConfigExample); - throw ToolExit(_exitInvalidPluginToolsConfig); - } - if (buildFlagsConfiguration.containsKey(_pluginToolsConfigGlobalKey)) { - final Object? globalBuildFlagsConfiguration = - buildFlagsConfiguration[_pluginToolsConfigGlobalKey]; - if (globalBuildFlagsConfiguration is! YamlList) { - printError( - 'The $_pluginToolsConfigFileName file\'s "$_pluginToolsConfigBuildFlagsKey" key must be a map'); - printError('whose "$_pluginToolsConfigGlobalKey" key is a list.'); - printError( - 'That list must contain a list of arguments to pass to the flutter tool.'); - printError( - 'For example, the $_pluginToolsConfigFileName file could look like:'); - printError(_pluginToolsConfigExample); - throw ToolExit(_exitInvalidPluginToolsConfig); - } - yield* globalBuildFlagsConfiguration.cast(); - } - } - } - } - - Future _buildExample( - RepositoryPackage example, - String flutterBuildType, { - List extraBuildFlags = const [], - }) async { - final String enableExperiment = getStringArg(kEnableExperiment); - - final int exitCode = await processRunner.runAndStream( - flutterCommand, - [ - 'build', - flutterBuildType, - ...extraBuildFlags, - ..._readExtraBuildFlagsConfiguration(example.directory), - if (enableExperiment.isNotEmpty) - '--enable-experiment=$enableExperiment', - ], - workingDir: example.directory, - ); - return exitCode == 0; - } -} - -/// A collection of information related to a specific platform. -class _PlatformDetails { - const _PlatformDetails( - this.label, { - required this.pluginPlatform, - required this.flutterBuildType, - this.extraBuildFlags = const [], - }); - - /// The name to use in output. - final String label; - - /// The key in a pubspec's platform: entry. - final String pluginPlatform; - - /// The `flutter build` build type. - final String flutterBuildType; - - /// The Flutter platform directory name. - // In practice, this is the same as the plugin platform key for all platforms. - // If that changes, this can be adjusted. - String get flutterPlatformDirectory => pluginPlatform; - - /// Any extra flags to pass to `flutter build`. - final List extraBuildFlags; -} diff --git a/script/tool/lib/src/common/cmake.dart b/script/tool/lib/src/common/cmake.dart deleted file mode 100644 index 3f5d8452bd44..000000000000 --- a/script/tool/lib/src/common/cmake.dart +++ /dev/null @@ -1,118 +0,0 @@ -// 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 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'core.dart'; -import 'process_runner.dart'; - -const String _cacheCommandKey = 'CMAKE_COMMAND:INTERNAL'; - -/// A utility class for interacting with CMake projects. -class CMakeProject { - /// Creates an instance that runs commands for [project] with the given - /// [processRunner]. - CMakeProject( - this.flutterProject, { - required this.buildMode, - this.processRunner = const ProcessRunner(), - this.platform = const LocalPlatform(), - }); - - /// The directory of a Flutter project to run Gradle commands in. - final Directory flutterProject; - - /// The [ProcessRunner] used to run commands. Overridable for testing. - final ProcessRunner processRunner; - - /// The platform that commands are being run on. - final Platform platform; - - /// The build mode (e.g., Debug, Release). - /// - /// This is a constructor paramater because on Linux many properties depend - /// on the build mode since it uses a single-configuration generator. - final String buildMode; - - late final String _cmakeCommand = _determineCmakeCommand(); - - /// The project's platform directory name. - String get _platformDirName => platform.isWindows ? 'windows' : 'linux'; - - /// The project's 'example' build directory for this instance's platform. - Directory get buildDirectory { - Directory buildDir = - flutterProject.childDirectory('build').childDirectory(_platformDirName); - if (platform.isLinux) { - buildDir = buildDir - // TODO(stuartmorgan): Support arm64 if that ever becomes a supported - // CI configuration for the repository. - .childDirectory('x64') - // Linux uses a single-config generator, so the base build directory - // includes the configuration. - .childDirectory(buildMode.toLowerCase()); - } - return buildDir; - } - - File get _cacheFile => buildDirectory.childFile('CMakeCache.txt'); - - /// Returns the CMake command to run build commands for this project. - /// - /// Assumes the project has been built at least once, such that the CMake - /// generation step has run. - String getCmakeCommand() { - return _cmakeCommand; - } - - /// Returns the CMake command to run build commands for this project. This is - /// used to initialize _cmakeCommand, and should not be called directly. - /// - /// Assumes the project has been built at least once, such that the CMake - /// generation step has run. - String _determineCmakeCommand() { - // On Linux 'cmake' is expected to be in the path, so doesn't need to - // be lookup up and cached. - if (platform.isLinux) { - return 'cmake'; - } - final File cacheFile = _cacheFile; - String? command; - for (String line in cacheFile.readAsLinesSync()) { - line = line.trim(); - if (line.startsWith(_cacheCommandKey)) { - command = line.substring(line.indexOf('=') + 1).trim(); - break; - } - } - if (command == null) { - printError('Unable to find CMake command in ${cacheFile.path}'); - throw ToolExit(100); - } - return command; - } - - /// Whether or not the project is ready to have CMake commands run on it - /// (i.e., whether the `flutter` tool has generated the necessary files). - bool isConfigured() => _cacheFile.existsSync(); - - /// Runs a `cmake` command with the given parameters. - Future runBuild( - String target, { - List arguments = const [], - }) { - return processRunner.runAndStream( - getCmakeCommand(), - [ - '--build', - buildDirectory.path, - '--target', - target, - if (platform.isWindows) ...['--config', buildMode], - ...arguments, - ], - ); - } -} diff --git a/script/tool/lib/src/common/file_utils.dart b/script/tool/lib/src/common/file_utils.dart deleted file mode 100644 index 3c2f2f18f954..000000000000 --- a/script/tool/lib/src/common/file_utils.dart +++ /dev/null @@ -1,20 +0,0 @@ -// 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 'package:file/file.dart'; - -/// Returns a [File] created by appending all but the last item in [components] -/// to [base] as subdirectories, then appending the last as a file. -/// -/// Example: -/// childFileWithSubcomponents(rootDir, ['foo', 'bar', 'baz.txt']) -/// creates a File representing /rootDir/foo/bar/baz.txt. -File childFileWithSubcomponents(Directory base, List components) { - Directory dir = base; - final String basename = components.removeLast(); - for (final String directoryName in components) { - dir = dir.childDirectory(directoryName); - } - return dir.childFile(basename); -} diff --git a/script/tool/lib/src/common/gradle.dart b/script/tool/lib/src/common/gradle.dart deleted file mode 100644 index 746536075014..000000000000 --- a/script/tool/lib/src/common/gradle.dart +++ /dev/null @@ -1,56 +0,0 @@ -// 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 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'process_runner.dart'; -import 'repository_package.dart'; - -const String _gradleWrapperWindows = 'gradlew.bat'; -const String _gradleWrapperNonWindows = 'gradlew'; - -/// A utility class for interacting with Gradle projects. -class GradleProject { - /// Creates an instance that runs commands for [project] with the given - /// [processRunner]. - GradleProject( - this.flutterProject, { - this.processRunner = const ProcessRunner(), - this.platform = const LocalPlatform(), - }); - - /// The directory of a Flutter project to run Gradle commands in. - final RepositoryPackage flutterProject; - - /// The [ProcessRunner] used to run commands. Overridable for testing. - final ProcessRunner processRunner; - - /// The platform that commands are being run on. - final Platform platform; - - /// The project's 'android' directory. - Directory get androidDirectory => - flutterProject.platformDirectory(FlutterPlatform.android); - - /// The path to the Gradle wrapper file for the project. - File get gradleWrapper => androidDirectory.childFile( - platform.isWindows ? _gradleWrapperWindows : _gradleWrapperNonWindows); - - /// Whether or not the project is ready to have Gradle commands run on it - /// (i.e., whether the `flutter` tool has generated the necessary files). - bool isConfigured() => gradleWrapper.existsSync(); - - /// Runs a `gradlew` command with the given parameters. - Future runCommand( - String target, { - List arguments = const [], - }) { - return processRunner.runAndStream( - gradleWrapper.path, - [target, ...arguments], - workingDir: androidDirectory, - ); - } -} diff --git a/script/tool/lib/src/common/package_state_utils.dart b/script/tool/lib/src/common/package_state_utils.dart deleted file mode 100644 index fbba75c6116f..000000000000 --- a/script/tool/lib/src/common/package_state_utils.dart +++ /dev/null @@ -1,228 +0,0 @@ -// 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 'package:file/file.dart'; -import 'package:meta/meta.dart'; -import 'package:path/path.dart' as p; - -import 'git_version_finder.dart'; -import 'repository_package.dart'; - -/// The state of a package on disk relative to git state. -@immutable -class PackageChangeState { - /// Creates a new immutable state instance. - const PackageChangeState({ - required this.hasChanges, - required this.hasChangelogChange, - required this.needsChangelogChange, - required this.needsVersionChange, - }); - - /// True if there are any changes to files in the package. - final bool hasChanges; - - /// True if the package's CHANGELOG.md has been changed. - final bool hasChangelogChange; - - /// True if any changes in the package require a version change according - /// to repository policy. - final bool needsVersionChange; - - /// True if any changes in the package require a CHANGELOG change according - /// to repository policy. - final bool needsChangelogChange; -} - -/// Checks [package] against [changedPaths] to determine what changes it has -/// and how those changes relate to repository policy about CHANGELOG and -/// version updates. -/// -/// [changedPaths] should be a list of POSIX-style paths from a common root, -/// and [relativePackagePath] should be the path to [package] from that same -/// root. Commonly these will come from `gitVersionFinder.getChangedFiles()` -/// and `getRelativePosixPath(package.directory, gitDir.path)` respectively; -/// they are arguments mainly to allow for caching the changed paths for an -/// entire command run. -/// -/// If [git] is provided, [changedPaths] must be repository-relative -/// paths, and change type detection can use file diffs in addition to paths. -Future checkPackageChangeState( - RepositoryPackage package, { - required List changedPaths, - required String relativePackagePath, - GitVersionFinder? git, -}) async { - final String packagePrefix = relativePackagePath.endsWith('/') - ? relativePackagePath - : '$relativePackagePath/'; - - bool hasChanges = false; - bool hasChangelogChange = false; - bool needsVersionChange = false; - bool needsChangelogChange = false; - for (final String path in changedPaths) { - // Only consider files within the package. - if (!path.startsWith(packagePrefix)) { - continue; - } - final String packageRelativePath = path.substring(packagePrefix.length); - hasChanges = true; - - final List components = p.posix.split(packageRelativePath); - if (components.isEmpty) { - continue; - } - - if (components.first == 'CHANGELOG.md') { - hasChangelogChange = true; - continue; - } - - if (!needsVersionChange) { - // Developer-only changes don't need version changes or changelog changes. - if (await _isDevChange(components, git: git, repoPath: path)) { - continue; - } - - // Some other changes don't need version changes, but might benefit from - // changelog changes. - needsChangelogChange = true; - if ( - // One of a few special files example will be shown on pub.dev, but - // for anything else in the example publishing has no purpose. - !_isUnpublishedExampleChange(components, package)) { - needsVersionChange = true; - } - } - } - - return PackageChangeState( - hasChanges: hasChanges, - hasChangelogChange: hasChangelogChange, - needsChangelogChange: needsChangelogChange, - needsVersionChange: needsVersionChange); -} - -bool _isTestChange(List pathComponents) { - return pathComponents.contains('test') || - pathComponents.contains('integration_test') || - pathComponents.contains('androidTest') || - pathComponents.contains('RunnerTests') || - pathComponents.contains('RunnerUITests') || - // Pigeon's custom platform tests. - pathComponents.first == 'platform_tests'; -} - -// True if the given file is an example file other than the one that will be -// published according to https://dart.dev/tools/pub/package-layout#examples. -// -// This is not exhastive; it currently only handles variations we actually have -// in our repositories. -bool _isUnpublishedExampleChange( - List pathComponents, RepositoryPackage package) { - if (pathComponents.first != 'example') { - return false; - } - final List exampleComponents = pathComponents.sublist(1); - if (exampleComponents.isEmpty) { - return false; - } - - final Directory exampleDirectory = - package.directory.childDirectory('example'); - - // Check for example.md/EXAMPLE.md first, as that has priority. If it's - // present, any other example file is unpublished. - final bool hasExampleMd = - exampleDirectory.childFile('example.md').existsSync() || - exampleDirectory.childFile('EXAMPLE.md').existsSync(); - if (hasExampleMd) { - return !(exampleComponents.length == 1 && - exampleComponents.first.toLowerCase() == 'example.md'); - } - - // Most packages have an example/lib/main.dart (or occasionally - // example/main.dart), so check for that. The other naming variations aren't - // currently used. - const String mainName = 'main.dart'; - final bool hasExampleCode = - exampleDirectory.childDirectory('lib').childFile(mainName).existsSync() || - exampleDirectory.childFile(mainName).existsSync(); - if (hasExampleCode) { - // If there is an example main, only that example file is published. - return !((exampleComponents.length == 1 && - exampleComponents.first == mainName) || - (exampleComponents.length == 2 && - exampleComponents.first == 'lib' && - exampleComponents[1] == mainName)); - } - - // If there's no example code either, the example README.md, if any, is the - // file that will be published. - return exampleComponents.first.toLowerCase() != 'readme.md'; -} - -// True if the change is only relevant to people working on the package. -Future _isDevChange(List pathComponents, - {GitVersionFinder? git, String? repoPath}) async { - return _isTestChange(pathComponents) || - // The top-level "tool" directory is for non-client-facing utility - // code, such as test scripts. - pathComponents.first == 'tool' || - // The top-level "pigeons" directory is the repo convention for storing - // pigeon input files. - pathComponents.first == 'pigeons' || - // Entry point for the 'custom-test' command, which is only for CI and - // local testing. - pathComponents.first == 'run_tests.sh' || - // Ignoring lints doesn't affect clients. - pathComponents.contains('lint-baseline.xml') || - // Example build files are very unlikely to be interesting to clients. - _isExampleBuildFile(pathComponents) || - // Test-only gradle depenedencies don't affect clients. - await _isGradleTestDependencyChange(pathComponents, - git: git, repoPath: repoPath); -} - -bool _isExampleBuildFile(List pathComponents) { - if (!pathComponents.contains('example')) { - return false; - } - return pathComponents.contains('gradle-wrapper.properties') || - pathComponents.contains('gradle.properties') || - pathComponents.contains('build.gradle') || - pathComponents.contains('Runner.xcodeproj') || - pathComponents.contains('CMakeLists.txt') || - pathComponents.contains('pubspec.yaml'); -} - -Future _isGradleTestDependencyChange(List pathComponents, - {GitVersionFinder? git, String? repoPath}) async { - if (git == null) { - return false; - } - if (pathComponents.last != 'build.gradle') { - return false; - } - final List diff = await git.getDiffContents(targetPath: repoPath); - final RegExp changeLine = RegExp(r'[+-] '); - final RegExp testDependencyLine = - RegExp(r'[+-]\s*(?:androidT|t)estImplementation\s'); - bool foundTestDependencyChange = false; - for (final String line in diff) { - if (!changeLine.hasMatch(line) || - line.startsWith('--- ') || - line.startsWith('+++ ')) { - continue; - } - if (!testDependencyLine.hasMatch(line)) { - return false; - } - foundTestDependencyChange = true; - } - // Only return true if a test dependency change was found, as a failsafe - // against having the wrong (e.g., incorrectly empty) diff output. - return foundTestDependencyChange; -} diff --git a/script/tool/lib/src/common/plugin_utils.dart b/script/tool/lib/src/common/plugin_utils.dart deleted file mode 100644 index 94677fe7e5a3..000000000000 --- a/script/tool/lib/src/common/plugin_utils.dart +++ /dev/null @@ -1,119 +0,0 @@ -// 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 'package:yaml/yaml.dart'; - -import 'core.dart'; -import 'repository_package.dart'; - -/// Possible plugin support options for a platform. -enum PlatformSupport { - /// The platform has an implementation in the package. - inline, - - /// The platform has an endorsed federated implementation in another package. - federated, -} - -/// Returns true if [package] is a Flutter plugin. -bool isFlutterPlugin(RepositoryPackage package) { - return _readPluginPubspecSection(package) != null; -} - -/// Returns true if [package] is a Flutter [platform] plugin. -/// -/// It checks this by looking for the following pattern in the pubspec: -/// -/// flutter: -/// plugin: -/// platforms: -/// [platform]: -/// -/// If [requiredMode] is provided, the plugin must have the given type of -/// implementation in order to return true. -bool pluginSupportsPlatform( - String platform, - RepositoryPackage plugin, { - PlatformSupport? requiredMode, -}) { - assert(platform == platformIOS || - platform == platformAndroid || - platform == platformWeb || - platform == platformMacOS || - platform == platformWindows || - platform == platformLinux); - - final YamlMap? platformEntry = - _readPlatformPubspecSectionForPlugin(platform, plugin); - if (platformEntry == null) { - return false; - } - - // If the platform entry is present, then it supports the platform. Check - // for required mode if specified. - if (requiredMode != null) { - final bool federated = platformEntry.containsKey('default_package'); - if (federated != (requiredMode == PlatformSupport.federated)) { - return false; - } - } - - return true; -} - -/// Returns true if [plugin] includes native code for [platform], as opposed to -/// being implemented entirely in Dart. -bool pluginHasNativeCodeForPlatform(String platform, RepositoryPackage plugin) { - if (platform == platformWeb) { - // Web plugins are always Dart-only. - return false; - } - final YamlMap? platformEntry = - _readPlatformPubspecSectionForPlugin(platform, plugin); - if (platformEntry == null) { - return false; - } - // All other platforms currently use pluginClass for indicating the native - // code in the plugin. - final String? pluginClass = platformEntry['pluginClass'] as String?; - // TODO(stuartmorgan): Remove the check for 'none' once none of the plugins - // in the repository use that workaround. See - // https://github.com/flutter/flutter/issues/57497 for context. - return pluginClass != null && pluginClass != 'none'; -} - -/// Returns the -/// flutter: -/// plugin: -/// platforms: -/// [platform]: -/// section from [plugin]'s pubspec.yaml, or null if either it is not present, -/// or the pubspec couldn't be read. -YamlMap? _readPlatformPubspecSectionForPlugin( - String platform, RepositoryPackage plugin) { - final YamlMap? pluginSection = _readPluginPubspecSection(plugin); - if (pluginSection == null) { - return null; - } - final YamlMap? platforms = pluginSection['platforms'] as YamlMap?; - if (platforms == null) { - return null; - } - return platforms[platform] as YamlMap?; -} - -/// Returns the -/// flutter: -/// plugin: -/// platforms: -/// section from [plugin]'s pubspec.yaml, or null if either it is not present, -/// or the pubspec couldn't be read. -YamlMap? _readPluginPubspecSection(RepositoryPackage package) { - final Pubspec pubspec = package.parsePubspec(); - final Map? flutterSection = pubspec.flutter; - if (flutterSection == null) { - return null; - } - return flutterSection['plugin'] as YamlMap?; -} diff --git a/script/tool/lib/src/common/pub_version_finder.dart b/script/tool/lib/src/common/pub_version_finder.dart deleted file mode 100644 index c24ec429f8a3..000000000000 --- a/script/tool/lib/src/common/pub_version_finder.dart +++ /dev/null @@ -1,105 +0,0 @@ -// 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 'dart:convert'; - -import 'package:http/http.dart' as http; -import 'package:pub_semver/pub_semver.dart'; - -/// Finding version of [package] that is published on pub. -class PubVersionFinder { - /// Constructor. - /// - /// Note: you should manually close the [httpClient] when done using the finder. - PubVersionFinder({this.pubHost = defaultPubHost, required this.httpClient}); - - /// The default pub host to use. - static const String defaultPubHost = 'https://pub.dev'; - - /// The pub host url, defaults to `https://pub.dev`. - final String pubHost; - - /// The http client. - /// - /// You should manually close this client when done using this finder. - final http.Client httpClient; - - /// Get the package version on pub. - Future getPackageVersion( - {required String packageName}) async { - assert(packageName.isNotEmpty); - final Uri pubHostUri = Uri.parse(pubHost); - final Uri url = pubHostUri.replace(path: '/packages/$packageName.json'); - final http.Response response = await httpClient.get(url); - - if (response.statusCode == 404) { - return PubVersionFinderResponse( - versions: [], - result: PubVersionFinderResult.noPackageFound, - httpResponse: response); - } else if (response.statusCode != 200) { - return PubVersionFinderResponse( - versions: [], - result: PubVersionFinderResult.fail, - httpResponse: response); - } - final Map responseBody = - json.decode(response.body) as Map; - final List versions = (responseBody['versions']! as List) - .cast() - .map( - (final String versionString) => Version.parse(versionString)) - .toList(); - - return PubVersionFinderResponse( - versions: versions, - result: PubVersionFinderResult.success, - httpResponse: response); - } -} - -/// Represents a response for [PubVersionFinder]. -class PubVersionFinderResponse { - /// Constructor. - PubVersionFinderResponse( - {required this.versions, - required this.result, - required this.httpResponse}) { - if (versions.isNotEmpty) { - versions.sort((Version a, Version b) { - // TODO(cyanglaz): Think about how to handle pre-release version with [Version.prioritize]. - // https://github.com/flutter/flutter/issues/82222 - return b.compareTo(a); - }); - } - } - - /// The versions found in [PubVersionFinder]. - /// - /// This is sorted by largest to smallest, so the first element in the list is the largest version. - /// Might be `null` if the [result] is not [PubVersionFinderResult.success]. - final List versions; - - /// The result of the version finder. - final PubVersionFinderResult result; - - /// The response object of the http request. - final http.Response httpResponse; -} - -/// An enum representing the result of [PubVersionFinder]. -enum PubVersionFinderResult { - /// The version finder successfully found a version. - success, - - /// The version finder failed to find a valid version. - /// - /// This might due to http connection errors or user errors. - fail, - - /// The version finder failed to locate the package. - /// - /// This indicates the package is new. - noPackageFound, -} diff --git a/script/tool/lib/src/common/xcode.dart b/script/tool/lib/src/common/xcode.dart deleted file mode 100644 index 83f681bcb492..000000000000 --- a/script/tool/lib/src/common/xcode.dart +++ /dev/null @@ -1,159 +0,0 @@ -// 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 'dart:convert'; -import 'dart:io' as io; - -import 'package:file/file.dart'; - -import 'core.dart'; -import 'process_runner.dart'; - -const String _xcodeBuildCommand = 'xcodebuild'; -const String _xcRunCommand = 'xcrun'; - -/// A utility class for interacting with the installed version of Xcode. -class Xcode { - /// Creates an instance that runs commands with the given [processRunner]. - /// - /// If [log] is true, commands run by this instance will long various status - /// messages. - Xcode({ - this.processRunner = const ProcessRunner(), - this.log = false, - }); - - /// The [ProcessRunner] used to run commands. Overridable for testing. - final ProcessRunner processRunner; - - /// Whether or not to log when running commands. - final bool log; - - /// Runs an `xcodebuild` in [directory] with the given parameters. - Future runXcodeBuild( - Directory directory, { - List actions = const ['build'], - required String workspace, - required String scheme, - String? configuration, - List extraFlags = const [], - }) { - final List args = [ - _xcodeBuildCommand, - ...actions, - if (workspace != null) ...['-workspace', workspace], - if (scheme != null) ...['-scheme', scheme], - if (configuration != null) ...['-configuration', configuration], - ...extraFlags, - ]; - final String completeTestCommand = '$_xcRunCommand ${args.join(' ')}'; - if (log) { - print(completeTestCommand); - } - return processRunner.runAndStream(_xcRunCommand, args, - workingDir: directory); - } - - /// Returns true if [project], which should be an .xcodeproj directory, - /// contains a target called [target], false if it does not, and null if the - /// check fails (e.g., if [project] is not an Xcode project). - Future projectHasTarget(Directory project, String target) async { - final io.ProcessResult result = - await processRunner.run(_xcRunCommand, [ - _xcodeBuildCommand, - '-list', - '-json', - '-project', - project.path, - ]); - if (result.exitCode != 0) { - return null; - } - Map? projectInfo; - try { - projectInfo = (jsonDecode(result.stdout as String) - as Map)['project'] as Map?; - } on FormatException { - return null; - } - if (projectInfo == null) { - return null; - } - final List? targets = - (projectInfo['targets'] as List?)?.cast(); - return targets?.contains(target) ?? false; - } - - /// Returns the newest available simulator (highest OS version, with ties - /// broken in favor of newest device), if any. - Future findBestAvailableIphoneSimulator() async { - final List findSimulatorsArguments = [ - 'simctl', - 'list', - 'devices', - 'runtimes', - 'available', - '--json', - ]; - final String findSimulatorCompleteCommand = - '$_xcRunCommand ${findSimulatorsArguments.join(' ')}'; - if (log) { - print('Looking for available simulators...'); - print(findSimulatorCompleteCommand); - } - final io.ProcessResult findSimulatorsResult = - await processRunner.run(_xcRunCommand, findSimulatorsArguments); - if (findSimulatorsResult.exitCode != 0) { - if (log) { - printError( - 'Error occurred while running "$findSimulatorCompleteCommand":\n' - '${findSimulatorsResult.stderr}'); - } - return null; - } - final Map simulatorListJson = - jsonDecode(findSimulatorsResult.stdout as String) - as Map; - final List> runtimes = - (simulatorListJson['runtimes'] as List) - .cast>(); - final Map devices = - (simulatorListJson['devices'] as Map) - .cast(); - if (runtimes.isEmpty || devices.isEmpty) { - return null; - } - String? id; - // Looking for runtimes, trying to find one with highest OS version. - for (final Map rawRuntimeMap in runtimes.reversed) { - final Map runtimeMap = - rawRuntimeMap.cast(); - if ((runtimeMap['name'] as String?)?.contains('iOS') != true) { - continue; - } - final String? runtimeID = runtimeMap['identifier'] as String?; - if (runtimeID == null) { - continue; - } - final List>? devicesForRuntime = - (devices[runtimeID] as List?)?.cast>(); - if (devicesForRuntime == null || devicesForRuntime.isEmpty) { - continue; - } - // Looking for runtimes, trying to find latest version of device. - for (final Map rawDevice in devicesForRuntime.reversed) { - final Map device = rawDevice.cast(); - id = device['udid'] as String?; - if (id == null) { - continue; - } - if (log) { - print('device selected: $device'); - } - return id; - } - } - return null; - } -} diff --git a/script/tool/lib/src/create_all_packages_app_command.dart b/script/tool/lib/src/create_all_packages_app_command.dart deleted file mode 100644 index b56086ac1d0a..000000000000 --- a/script/tool/lib/src/create_all_packages_app_command.dart +++ /dev/null @@ -1,344 +0,0 @@ -// 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 'dart:io' as io; - -import 'package:file/file.dart'; -import 'package:path/path.dart' as p; -import 'package:platform/platform.dart'; -import 'package:pub_semver/pub_semver.dart'; -import 'package:pubspec_parse/pubspec_parse.dart'; - -import 'common/core.dart'; -import 'common/package_command.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -const String _outputDirectoryFlag = 'output-dir'; - -const String _projectName = 'all_packages'; - -const int _exitUpdateMacosPodfileFailed = 3; -const int _exitUpdateMacosPbxprojFailed = 4; -const int _exitGenNativeBuildFilesFailed = 5; - -/// A command to create an application that builds all in a single application. -class CreateAllPackagesAppCommand extends PackageCommand { - /// Creates an instance of the builder command. - CreateAllPackagesAppCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Directory? pluginsRoot, - Platform platform = const LocalPlatform(), - }) : super(packagesDir, processRunner: processRunner, platform: platform) { - final Directory defaultDir = - pluginsRoot ?? packagesDir.fileSystem.currentDirectory; - argParser.addOption(_outputDirectoryFlag, - defaultsTo: defaultDir.path, - help: - 'The path the directory to create the "$_projectName" project in.\n' - 'Defaults to the repository root.'); - } - - /// The location to create the synthesized app project. - Directory get _appDirectory => packagesDir.fileSystem - .directory(getStringArg(_outputDirectoryFlag)) - .childDirectory(_projectName); - - /// The synthesized app project. - RepositoryPackage get app => RepositoryPackage(_appDirectory); - - @override - String get description => - 'Generate Flutter app that includes all target packagas.'; - - @override - String get name => 'create-all-packages-app'; - - @override - Future run() async { - final int exitCode = await _createApp(); - if (exitCode != 0) { - throw ToolExit(exitCode); - } - - final Set excluded = getExcludedPackageNames(); - if (excluded.isNotEmpty) { - print('Exluding the following plugins from the combined build:'); - for (final String plugin in excluded) { - print(' $plugin'); - } - print(''); - } - - await _genPubspecWithAllPlugins(); - - // Run `flutter pub get` to generate all native build files. - // TODO(stuartmorgan): This hangs on Windows for some reason. Since it's - // currently not needed on Windows, skip it there, but we should investigate - // further and/or implement https://github.com/flutter/flutter/issues/93407, - // and remove the need for this conditional. - if (!platform.isWindows) { - if (!await _genNativeBuildFiles()) { - printError( - "Failed to generate native build files via 'flutter pub get'"); - throw ToolExit(_exitGenNativeBuildFilesFailed); - } - } - - await Future.wait(>[ - _updateAppGradle(), - _updateManifest(), - _updateMacosPbxproj(), - // This step requires the native file generation triggered by - // flutter pub get above, so can't currently be run on Windows. - if (!platform.isWindows) _updateMacosPodfile(), - ]); - } - - Future _createApp() async { - final io.ProcessResult result = io.Process.runSync( - flutterCommand, - [ - 'create', - '--template=app', - '--project-name=$_projectName', - '--android-language=java', - _appDirectory.path, - ], - ); - - print(result.stdout); - print(result.stderr); - return result.exitCode; - } - - Future _updateAppGradle() async { - final File gradleFile = app - .platformDirectory(FlutterPlatform.android) - .childDirectory('app') - .childFile('build.gradle'); - if (!gradleFile.existsSync()) { - throw ToolExit(64); - } - - final StringBuffer newGradle = StringBuffer(); - for (final String line in gradleFile.readAsLinesSync()) { - if (line.contains('minSdkVersion')) { - // minSdkVersion 20 is required by Google maps. - // minSdkVersion 19 is required by WebView. - newGradle.writeln('minSdkVersion 20'); - } else if (line.contains('compileSdkVersion')) { - // compileSdkVersion 33 is required by local_auth. - newGradle.writeln('compileSdkVersion 33'); - } else { - newGradle.writeln(line); - } - if (line.contains('defaultConfig {')) { - newGradle.writeln(' multiDexEnabled true'); - } else if (line.contains('dependencies {')) { - // Tests for https://github.com/flutter/flutter/issues/43383 - newGradle.writeln( - " implementation 'androidx.lifecycle:lifecycle-runtime:2.2.0-rc01'\n", - ); - } - } - gradleFile.writeAsStringSync(newGradle.toString()); - } - - Future _updateManifest() async { - final File manifestFile = app - .platformDirectory(FlutterPlatform.android) - .childDirectory('app') - .childDirectory('src') - .childDirectory('main') - .childFile('AndroidManifest.xml'); - if (!manifestFile.existsSync()) { - throw ToolExit(64); - } - - final StringBuffer newManifest = StringBuffer(); - for (final String line in manifestFile.readAsLinesSync()) { - if (line.contains('package="com.example.$_projectName"')) { - newManifest - ..writeln('package="com.example.$_projectName"') - ..writeln('xmlns:tools="http://schemas.android.com/tools">') - ..writeln() - ..writeln( - '', - ); - } else { - newManifest.writeln(line); - } - } - manifestFile.writeAsStringSync(newManifest.toString()); - } - - Future _genPubspecWithAllPlugins() async { - // Read the old pubspec file's Dart SDK version, in order to preserve it - // in the new file. The template sometimes relies on having opted in to - // specific language features via SDK version, so using a different one - // can cause compilation failures. - final Pubspec originalPubspec = app.parsePubspec(); - const String dartSdkKey = 'sdk'; - final VersionConstraint dartSdkConstraint = - originalPubspec.environment?[dartSdkKey] ?? - VersionConstraint.compatibleWith( - Version.parse('2.12.0'), - ); - - final Map pluginDeps = - await _getValidPathDependencies(); - final Pubspec pubspec = Pubspec( - _projectName, - description: 'Flutter app containing all 1st party plugins.', - version: Version.parse('1.0.0+1'), - environment: { - dartSdkKey: dartSdkConstraint, - }, - dependencies: { - 'flutter': SdkDependency('flutter'), - }..addAll(pluginDeps), - devDependencies: { - 'flutter_test': SdkDependency('flutter'), - }, - dependencyOverrides: pluginDeps, - ); - app.pubspecFile.writeAsStringSync(_pubspecToString(pubspec)); - } - - Future> _getValidPathDependencies() async { - final Map pathDependencies = - {}; - - await for (final PackageEnumerationEntry entry in getTargetPackages()) { - final RepositoryPackage package = entry.package; - final Directory pluginDirectory = package.directory; - final String pluginName = pluginDirectory.basename; - final Pubspec pubspec = package.parsePubspec(); - - if (pubspec.publishTo != 'none') { - pathDependencies[pluginName] = PathDependency(pluginDirectory.path); - } - } - return pathDependencies; - } - - String _pubspecToString(Pubspec pubspec) { - return ''' -### Generated file. Do not edit. Run `dart pub global run flutter_plugin_tools gen-pubspec` to update. -name: ${pubspec.name} -description: ${pubspec.description} -publish_to: none - -version: ${pubspec.version} - -environment:${_pubspecMapString(pubspec.environment!)} - -dependencies:${_pubspecMapString(pubspec.dependencies)} - -dependency_overrides:${_pubspecMapString(pubspec.dependencyOverrides)} - -dev_dependencies:${_pubspecMapString(pubspec.devDependencies)} -###'''; - } - - String _pubspecMapString(Map values) { - final StringBuffer buffer = StringBuffer(); - - for (final MapEntry entry in values.entries) { - buffer.writeln(); - final Object? entryValue = entry.value; - if (entryValue is VersionConstraint) { - String value = entryValue.toString(); - // Range constraints require quoting. - if (value.startsWith('>') || value.startsWith('<')) { - value = "'$value'"; - } - buffer.write(' ${entry.key}: $value'); - } else if (entryValue is SdkDependency) { - buffer.write(' ${entry.key}: \n sdk: ${entryValue.sdk}'); - } else if (entryValue is PathDependency) { - String depPath = entryValue.path; - if (path.style == p.Style.windows) { - // Posix-style path separators are preferred in pubspec.yaml (and - // using a consistent format makes unit testing simpler), so convert. - final List components = path.split(depPath); - final String firstComponent = components.first; - // path.split leaves a \ on drive components that isn't necessary, - // and confuses pub, so remove it. - if (firstComponent.endsWith(r':\')) { - components[0] = - firstComponent.substring(0, firstComponent.length - 1); - } - depPath = p.posix.joinAll(components); - } - buffer.write(' ${entry.key}: \n path: $depPath'); - } else { - throw UnimplementedError( - 'Not available for type: ${entryValue.runtimeType}', - ); - } - } - - return buffer.toString(); - } - - Future _genNativeBuildFiles() async { - final int exitCode = await processRunner.runAndStream( - flutterCommand, - ['pub', 'get'], - workingDir: _appDirectory, - ); - return exitCode == 0; - } - - Future _updateMacosPodfile() async { - /// Only change the macOS deployment target if the host platform is macOS. - /// The Podfile is not generated on other platforms. - if (!platform.isMacOS) { - return; - } - - final File podfileFile = - app.platformDirectory(FlutterPlatform.macos).childFile('Podfile'); - if (!podfileFile.existsSync()) { - printError("Can't find Podfile for macOS"); - throw ToolExit(_exitUpdateMacosPodfileFailed); - } - - final StringBuffer newPodfile = StringBuffer(); - for (final String line in podfileFile.readAsLinesSync()) { - if (line.contains('platform :osx')) { - // macOS 10.15 is required by in_app_purchase. - newPodfile.writeln("platform :osx, '10.15'"); - } else { - newPodfile.writeln(line); - } - } - podfileFile.writeAsStringSync(newPodfile.toString()); - } - - Future _updateMacosPbxproj() async { - final File pbxprojFile = app - .platformDirectory(FlutterPlatform.macos) - .childDirectory('Runner.xcodeproj') - .childFile('project.pbxproj'); - if (!pbxprojFile.existsSync()) { - printError("Can't find project.pbxproj for macOS"); - throw ToolExit(_exitUpdateMacosPbxprojFailed); - } - - final StringBuffer newPbxproj = StringBuffer(); - for (final String line in pbxprojFile.readAsLinesSync()) { - if (line.contains('MACOSX_DEPLOYMENT_TARGET')) { - // macOS 10.15 is required by in_app_purchase. - newPbxproj.writeln(' MACOSX_DEPLOYMENT_TARGET = 10.15;'); - } else { - newPbxproj.writeln(line); - } - } - pbxprojFile.writeAsStringSync(newPbxproj.toString()); - } -} diff --git a/script/tool/lib/src/custom_test_command.dart b/script/tool/lib/src/custom_test_command.dart deleted file mode 100644 index 0ef6e602c070..000000000000 --- a/script/tool/lib/src/custom_test_command.dart +++ /dev/null @@ -1,86 +0,0 @@ -// 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 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'common/package_looping_command.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -const String _scriptName = 'run_tests.dart'; -const String _legacyScriptName = 'run_tests.sh'; - -/// A command to run custom, package-local tests on packages. -/// -/// This is an escape hatch for adding tests that this tooling doesn't support. -/// It should be used sparingly; prefer instead to add functionality to this -/// tooling to eliminate the need for bespoke tests. -class CustomTestCommand extends PackageLoopingCommand { - /// Creates a custom test command instance. - CustomTestCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : super(packagesDir, processRunner: processRunner, platform: platform); - - @override - final String name = 'custom-test'; - - @override - final String description = 'Runs package-specific custom tests defined in ' - "a package's tool/$_scriptName file.\n\n" - 'This command requires "dart" to be in your path.'; - - @override - Future runForPackage(RepositoryPackage package) async { - final File script = - package.directory.childDirectory('tool').childFile(_scriptName); - final File legacyScript = package.directory.childFile(_legacyScriptName); - String? customSkipReason; - bool ranTests = false; - - // Run the custom Dart script if presest. - if (script.existsSync()) { - // Ensure that dependencies are available. - final int pubGetExitCode = await processRunner.runAndStream( - 'dart', ['pub', 'get'], - workingDir: package.directory); - if (pubGetExitCode != 0) { - return PackageResult.fail( - ['Unable to get script dependencies']); - } - - final int testExitCode = await processRunner.runAndStream( - 'dart', ['run', 'tool/$_scriptName'], - workingDir: package.directory); - if (testExitCode != 0) { - return PackageResult.fail(); - } - ranTests = true; - } - - // Run the legacy script if present. - if (legacyScript.existsSync()) { - if (platform.isWindows) { - customSkipReason = '$_legacyScriptName is not supported on Windows. ' - 'Please migrate to $_scriptName.'; - } else { - final int exitCode = await processRunner.runAndStream( - legacyScript.path, [], - workingDir: package.directory); - if (exitCode != 0) { - return PackageResult.fail(); - } - ranTests = true; - } - } - - if (!ranTests) { - return PackageResult.skip(customSkipReason ?? 'No custom tests'); - } - - return PackageResult.success(); - } -} diff --git a/script/tool/lib/src/dependabot_check_command.dart b/script/tool/lib/src/dependabot_check_command.dart deleted file mode 100644 index 77b44e11b59e..000000000000 --- a/script/tool/lib/src/dependabot_check_command.dart +++ /dev/null @@ -1,115 +0,0 @@ -// 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 'dart:async'; - -import 'package:file/file.dart'; -import 'package:git/git.dart'; -import 'package:yaml/yaml.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/repository_package.dart'; - -/// A command to verify Dependabot configuration coverage of packages. -class DependabotCheckCommand extends PackageLoopingCommand { - /// Creates Dependabot check command instance. - DependabotCheckCommand(Directory packagesDir, {GitDir? gitDir}) - : super(packagesDir, gitDir: gitDir) { - argParser.addOption(_configPathFlag, - help: 'Path to the Dependabot configuration file', - defaultsTo: '.github/dependabot.yml'); - } - - static const String _configPathFlag = 'config'; - - late Directory _repoRoot; - - // The set of directories covered by "gradle" entries in the config. - Set _gradleDirs = const {}; - - @override - final String name = 'dependabot-check'; - - @override - final String description = - 'Checks that all packages have Dependabot coverage.'; - - @override - final PackageLoopingType packageLoopingType = - PackageLoopingType.includeAllSubpackages; - - @override - final bool hasLongOutput = false; - - @override - Future initializeRun() async { - _repoRoot = packagesDir.fileSystem.directory((await gitDir).path); - - final YamlMap config = loadYaml(_repoRoot - .childFile(getStringArg(_configPathFlag)) - .readAsStringSync()) as YamlMap; - final dynamic entries = config['updates']; - if (entries is! YamlList) { - return; - } - - const String typeKey = 'package-ecosystem'; - const String dirKey = 'directory'; - _gradleDirs = entries - .cast() - .where((YamlMap entry) => entry[typeKey] == 'gradle') - .map((YamlMap entry) => entry[dirKey] as String) - .toSet(); - } - - @override - Future runForPackage(RepositoryPackage package) async { - bool skipped = true; - final List errors = []; - - final RunState gradleState = _validateDependabotGradleCoverage(package); - skipped = skipped && gradleState == RunState.skipped; - if (gradleState == RunState.failed) { - printError('${indentation}Missing Gradle coverage.'); - errors.add('Missing Gradle coverage'); - } - - // TODO(stuartmorgan): Add other ecosystem checks here as more are enabled. - - if (skipped) { - return PackageResult.skip('No supported package ecosystems'); - } - return errors.isEmpty - ? PackageResult.success() - : PackageResult.fail(errors); - } - - /// Returns the state for the Dependabot coverage of the Gradle ecosystem for - /// [package]: - /// - succeeded if it includes gradle and is covered. - /// - failed if it includes gradle and is not covered. - /// - skipped if it doesn't include gradle. - RunState _validateDependabotGradleCoverage(RepositoryPackage package) { - final Directory androidDir = - package.platformDirectory(FlutterPlatform.android); - final Directory appDir = androidDir.childDirectory('app'); - if (appDir.existsSync()) { - // It's an app, so only check for the app directory to be covered. - final String dependabotPath = - '/${getRelativePosixPath(appDir, from: _repoRoot)}'; - return _gradleDirs.contains(dependabotPath) - ? RunState.succeeded - : RunState.failed; - } else if (androidDir.existsSync()) { - // It's a library, so only check for the android directory to be covered. - final String dependabotPath = - '/${getRelativePosixPath(androidDir, from: _repoRoot)}'; - return _gradleDirs.contains(dependabotPath) - ? RunState.succeeded - : RunState.failed; - } - return RunState.skipped; - } -} diff --git a/script/tool/lib/src/drive_examples_command.dart b/script/tool/lib/src/drive_examples_command.dart deleted file mode 100644 index e8fb11b5f289..000000000000 --- a/script/tool/lib/src/drive_examples_command.dart +++ /dev/null @@ -1,380 +0,0 @@ -// 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 'dart:convert'; -import 'dart:io'; - -import 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/plugin_utils.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -const int _exitNoPlatformFlags = 2; -const int _exitNoAvailableDevice = 3; - -/// A command to run the example applications for packages via Flutter driver. -class DriveExamplesCommand extends PackageLoopingCommand { - /// Creates an instance of the drive command. - DriveExamplesCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : super(packagesDir, processRunner: processRunner, platform: platform) { - argParser.addFlag(platformAndroid, - help: 'Runs the Android implementation of the examples'); - argParser.addFlag(platformIOS, - help: 'Runs the iOS implementation of the examples'); - argParser.addFlag(platformLinux, - help: 'Runs the Linux implementation of the examples'); - argParser.addFlag(platformMacOS, - help: 'Runs the macOS implementation of the examples'); - argParser.addFlag(platformWeb, - help: 'Runs the web implementation of the examples'); - argParser.addFlag(platformWindows, - help: 'Runs the Windows implementation of the examples'); - argParser.addOption( - kEnableExperiment, - defaultsTo: '', - help: - 'Runs the driver tests in Dart VM with the given experiments enabled.', - ); - } - - @override - final String name = 'drive-examples'; - - @override - final String description = 'Runs driver tests for package example apps.\n\n' - 'For each *_test.dart in test_driver/ it drives an application with ' - 'either the corresponding test in test_driver (for example, ' - 'test_driver/app_test.dart would match test_driver/app.dart), or the ' - '*_test.dart files in integration_test/.\n\n' - 'This command requires "flutter" to be in your path.'; - - Map> _targetDeviceFlags = const >{}; - - @override - Future initializeRun() async { - final List platformSwitches = [ - platformAndroid, - platformIOS, - platformLinux, - platformMacOS, - platformWeb, - platformWindows, - ]; - final int platformCount = platformSwitches - .where((String platform) => getBoolArg(platform)) - .length; - // The flutter tool currently doesn't accept multiple device arguments: - // https://github.com/flutter/flutter/issues/35733 - // If that is implemented, this check can be relaxed. - if (platformCount != 1) { - printError( - 'Exactly one of ${platformSwitches.map((String platform) => '--$platform').join(', ')} ' - 'must be specified.'); - throw ToolExit(_exitNoPlatformFlags); - } - - String? androidDevice; - if (getBoolArg(platformAndroid)) { - final List devices = await _getDevicesForPlatform('android'); - if (devices.isEmpty) { - printError('No Android devices available'); - throw ToolExit(_exitNoAvailableDevice); - } - androidDevice = devices.first; - } - - String? iOSDevice; - if (getBoolArg(platformIOS)) { - final List devices = await _getDevicesForPlatform('ios'); - if (devices.isEmpty) { - printError('No iOS devices available'); - throw ToolExit(_exitNoAvailableDevice); - } - iOSDevice = devices.first; - } - - _targetDeviceFlags = >{ - if (getBoolArg(platformAndroid)) - platformAndroid: ['-d', androidDevice!], - if (getBoolArg(platformIOS)) platformIOS: ['-d', iOSDevice!], - if (getBoolArg(platformLinux)) platformLinux: ['-d', 'linux'], - if (getBoolArg(platformMacOS)) platformMacOS: ['-d', 'macos'], - if (getBoolArg(platformWeb)) - platformWeb: [ - '-d', - 'web-server', - '--web-port=7357', - '--browser-name=chrome', - if (platform.environment.containsKey('CHROME_EXECUTABLE')) - '--chrome-binary=${platform.environment['CHROME_EXECUTABLE']}', - ], - if (getBoolArg(platformWindows)) - platformWindows: ['-d', 'windows'], - }; - } - - @override - Future runForPackage(RepositoryPackage package) async { - final bool isPlugin = isFlutterPlugin(package); - - if (package.isPlatformInterface && package.getExamples().isEmpty) { - // Platform interface packages generally aren't intended to have - // examples, and don't need integration tests, so skip rather than fail. - return PackageResult.skip( - 'Platform interfaces are not expected to have integration tests.'); - } - - // For plugin packages, skip if the plugin itself doesn't support any - // requested platform(s). - if (isPlugin) { - final Iterable requestedPlatforms = _targetDeviceFlags.keys; - final Iterable unsupportedPlatforms = requestedPlatforms.where( - (String platform) => !pluginSupportsPlatform(platform, package)); - for (final String platform in unsupportedPlatforms) { - print('Skipping unsupported platform $platform...'); - } - if (unsupportedPlatforms.length == requestedPlatforms.length) { - return PackageResult.skip( - '${package.displayName} does not support any requested platform.'); - } - } - - int examplesFound = 0; - int supportedExamplesFound = 0; - bool testsRan = false; - final List errors = []; - for (final RepositoryPackage example in package.getExamples()) { - ++examplesFound; - final String exampleName = - getRelativePosixPath(example.directory, from: packagesDir); - - // Skip examples that don't support any requested platform(s). - final List deviceFlags = _deviceFlagsForExample(example); - if (deviceFlags.isEmpty) { - print( - 'Skipping $exampleName; does not support any requested platforms.'); - continue; - } - ++supportedExamplesFound; - - final List drivers = await _getDrivers(example); - if (drivers.isEmpty) { - print('No driver tests found for $exampleName'); - continue; - } - - for (final File driver in drivers) { - final List testTargets = []; - - // Try to find a matching app to drive without the _test.dart - // TODO(stuartmorgan): Migrate all remaining uses of this legacy - // approach (currently only video_player) and remove support for it: - // https://github.com/flutter/flutter/issues/85224. - final File? legacyTestFile = _getLegacyTestFileForTestDriver(driver); - if (legacyTestFile != null) { - testTargets.add(legacyTestFile); - } else { - for (final File testFile in await _getIntegrationTests(example)) { - // Check files for known problematic patterns. - final bool passesValidation = _validateIntegrationTest(testFile); - if (!passesValidation) { - // Report the issue, but continue with the test as the validation - // errors don't prevent running. - errors.add('${testFile.basename} failed validation'); - } - testTargets.add(testFile); - } - } - - if (testTargets.isEmpty) { - final String driverRelativePath = - getRelativePosixPath(driver, from: package.directory); - printError( - 'Found $driverRelativePath, but no integration_test/*_test.dart files.'); - errors.add('No test files for $driverRelativePath'); - continue; - } - - testsRan = true; - final List failingTargets = await _driveTests( - example, driver, testTargets, - deviceFlags: deviceFlags); - for (final File failingTarget in failingTargets) { - errors.add( - getRelativePosixPath(failingTarget, from: package.directory)); - } - } - } - if (!testsRan) { - // It is an error for a plugin not to have integration tests, because that - // is the only way to test the method channel communication. - if (isPlugin) { - printError( - 'No driver tests were run ($examplesFound example(s) found).'); - errors.add('No tests ran (use --exclude if this is intentional).'); - } else { - return PackageResult.skip(supportedExamplesFound == 0 - ? 'No example supports requested platform(s).' - : 'No example is configured for driver tests.'); - } - } - return errors.isEmpty - ? PackageResult.success() - : PackageResult.fail(errors); - } - - /// Returns the device flags for the intersection of the requested platforms - /// and the platforms supported by [example]. - List _deviceFlagsForExample(RepositoryPackage example) { - final List deviceFlags = []; - for (final MapEntry> entry - in _targetDeviceFlags.entries) { - final String platform = entry.key; - if (example.directory.childDirectory(platform).existsSync()) { - deviceFlags.addAll(entry.value); - } else { - final String exampleName = - getRelativePosixPath(example.directory, from: packagesDir); - print('Skipping unsupported platform $platform for $exampleName'); - } - } - return deviceFlags; - } - - Future> _getDevicesForPlatform(String platform) async { - final List deviceIds = []; - - final ProcessResult result = await processRunner.run( - flutterCommand, ['devices', '--machine'], - stdoutEncoding: utf8); - if (result.exitCode != 0) { - return deviceIds; - } - - String output = result.stdout as String; - // --machine doesn't currently prevent the tool from printing banners; - // see https://github.com/flutter/flutter/issues/86055. This workaround - // can be removed once that is fixed. - output = output.substring(output.indexOf('[')); - - final List> devices = - (jsonDecode(output) as List).cast>(); - for (final Map deviceInfo in devices) { - final String targetPlatform = - (deviceInfo['targetPlatform'] as String?) ?? ''; - if (targetPlatform.startsWith(platform)) { - final String? deviceId = deviceInfo['id'] as String?; - if (deviceId != null) { - deviceIds.add(deviceId); - } - } - } - return deviceIds; - } - - Future> _getDrivers(RepositoryPackage example) async { - final List drivers = []; - - final Directory driverDir = example.directory.childDirectory('test_driver'); - if (driverDir.existsSync()) { - await for (final FileSystemEntity driver in driverDir.list()) { - if (driver is File && driver.basename.endsWith('_test.dart')) { - drivers.add(driver); - } - } - } - return drivers; - } - - File? _getLegacyTestFileForTestDriver(File testDriver) { - final String testName = testDriver.basename.replaceAll( - RegExp(r'_test.dart$'), - '.dart', - ); - final File testFile = testDriver.parent.childFile(testName); - - return testFile.existsSync() ? testFile : null; - } - - Future> _getIntegrationTests(RepositoryPackage example) async { - final List tests = []; - final Directory integrationTestDir = - example.directory.childDirectory('integration_test'); - - if (integrationTestDir.existsSync()) { - await for (final FileSystemEntity file in integrationTestDir.list()) { - if (file is File && file.basename.endsWith('_test.dart')) { - tests.add(file); - } - } - } - return tests; - } - - /// Checks [testFile] for known bad patterns in integration tests, logging - /// any issues. - /// - /// Returns true if the file passes validation without issues. - bool _validateIntegrationTest(File testFile) { - final List lines = testFile.readAsLinesSync(); - - final RegExp badTestPattern = RegExp(r'\s*test\('); - if (lines.any((String line) => line.startsWith(badTestPattern))) { - final String filename = testFile.basename; - printError( - '$filename uses "test", which will not report failures correctly. ' - 'Use testWidgets instead.'); - return false; - } - - return true; - } - - /// For each file in [targets], uses - /// `flutter drive --driver [driver] --target ` - /// to drive [example], returning a list of any failing test targets. - /// - /// [deviceFlags] should contain the flags to run the test on a specific - /// target device (plus any supporting device-specific flags). E.g.: - /// - `['-d', 'macos']` for driving for macOS. - /// - `['-d', 'web-server', '--web-port=', '--browser-name=]` - /// for web - Future> _driveTests( - RepositoryPackage example, - File driver, - List targets, { - required List deviceFlags, - }) async { - final List failures = []; - - final String enableExperiment = getStringArg(kEnableExperiment); - - for (final File target in targets) { - final int exitCode = await processRunner.runAndStream( - flutterCommand, - [ - 'drive', - ...deviceFlags, - if (enableExperiment.isNotEmpty) - '--enable-experiment=$enableExperiment', - '--driver', - getRelativePosixPath(driver, from: example.directory), - '--target', - getRelativePosixPath(target, from: example.directory), - ], - workingDir: example.directory); - if (exitCode != 0) { - failures.add(target); - } - } - return failures; - } -} diff --git a/script/tool/lib/src/federation_safety_check_command.dart b/script/tool/lib/src/federation_safety_check_command.dart deleted file mode 100644 index 93a832eb0e29..000000000000 --- a/script/tool/lib/src/federation_safety_check_command.dart +++ /dev/null @@ -1,199 +0,0 @@ -// 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 'package:file/file.dart'; -import 'package:git/git.dart'; -import 'package:path/path.dart' as p; -import 'package:platform/platform.dart'; -import 'package:pub_semver/pub_semver.dart'; - -import 'common/core.dart'; -import 'common/file_utils.dart'; -import 'common/git_version_finder.dart'; -import 'common/package_looping_command.dart'; -import 'common/plugin_utils.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -/// A command to check that PRs don't violate repository best practices that -/// have been established to avoid breakages that building and testing won't -/// catch. -class FederationSafetyCheckCommand extends PackageLoopingCommand { - /// Creates an instance of the safety check command. - FederationSafetyCheckCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - GitDir? gitDir, - }) : super( - packagesDir, - processRunner: processRunner, - platform: platform, - gitDir: gitDir, - ); - - // A map of package name (as defined by the directory name of the package) - // to a list of changed Dart files in that package, as Posix paths relative to - // the package root. - // - // This only considers top-level packages, not subpackages such as example/. - final Map> _changedDartFiles = >{}; - - // The set of *_platform_interface packages that will have public code changes - // published. - final Set _modifiedAndPublishedPlatformInterfacePackages = {}; - - // The set of conceptual plugins (not packages) that have changes. - final Set _changedPlugins = {}; - - static const String _platformInterfaceSuffix = '_platform_interface'; - - @override - final String name = 'federation-safety-check'; - - @override - final String description = - 'Checks that the change does not violate repository rules around changes ' - 'to federated plugin packages.'; - - @override - bool get hasLongOutput => false; - - @override - Future initializeRun() async { - final GitVersionFinder gitVersionFinder = await retrieveVersionFinder(); - final String baseSha = await gitVersionFinder.getBaseSha(); - print('Validating changes relative to "$baseSha"\n'); - for (final String path in await gitVersionFinder.getChangedFiles()) { - // Git output always uses Posix paths. - final List allComponents = p.posix.split(path); - final int packageIndex = allComponents.indexOf('packages'); - if (packageIndex == -1) { - continue; - } - final List relativeComponents = - allComponents.sublist(packageIndex + 1); - // The package name is either the directory directly under packages/, or - // the directory under that in the case of a federated plugin. - String packageName = relativeComponents.removeAt(0); - // Count the top-level plugin as changed. - _changedPlugins.add(packageName); - if (relativeComponents[0] == packageName || - (relativeComponents.length > 1 && - relativeComponents[0].startsWith('${packageName}_'))) { - packageName = relativeComponents.removeAt(0); - } - - if (relativeComponents.last.endsWith('.dart')) { - _changedDartFiles[packageName] ??= []; - _changedDartFiles[packageName]! - .add(p.posix.joinAll(relativeComponents)); - } - - if (packageName.endsWith(_platformInterfaceSuffix) && - relativeComponents.first == 'pubspec.yaml' && - await _packageWillBePublished(path)) { - _modifiedAndPublishedPlatformInterfacePackages.add(packageName); - } - } - } - - @override - Future runForPackage(RepositoryPackage package) async { - if (!isFlutterPlugin(package)) { - return PackageResult.skip('Not a plugin.'); - } - - if (!package.isFederated) { - return PackageResult.skip('Not a federated plugin.'); - } - - if (package.isPlatformInterface) { - // As the leaf nodes in the graph, a published package interface change is - // assumed to be correct, and other changes are validated against that. - return PackageResult.skip( - 'Platform interface changes are not validated.'); - } - - // Uses basename to match _changedPackageFiles. - final String basePackageName = package.directory.parent.basename; - final String platformInterfacePackageName = - '$basePackageName$_platformInterfaceSuffix'; - final List changedPlatformInterfaceFiles = - _changedDartFiles[platformInterfacePackageName] ?? []; - - if (!_modifiedAndPublishedPlatformInterfacePackages - .contains(platformInterfacePackageName)) { - print('No published changes for $platformInterfacePackageName.'); - return PackageResult.success(); - } - - if (!changedPlatformInterfaceFiles - .any((String path) => path.startsWith('lib/'))) { - print('No public code changes for $platformInterfacePackageName.'); - return PackageResult.success(); - } - - final List changedPackageFiles = - _changedDartFiles[package.directory.basename] ?? []; - if (changedPackageFiles.isEmpty) { - print('No Dart changes.'); - return PackageResult.success(); - } - - // If the change would be flagged, but it appears to be a mass change - // rather than a plugin-specific change, allow it with a warning. - // - // This is a tradeoff between safety and convenience; forcing mass changes - // to be split apart is not ideal, and the assumption is that reviewers are - // unlikely to accidentally approve a PR that is supposed to be changing a - // single plugin, but touches other plugins (vs accidentally approving a - // PR that changes multiple parts of a single plugin, which is a relatively - // easy mistake to make). - // - // 3 is chosen to minimize the chances of accidentally letting something - // through (vs 2, which could be a single-plugin change with one stray - // change to another file accidentally included), while not setting too - // high a bar for detecting mass changes. This can be tuned if there are - // issues with false positives or false negatives. - const int massChangePluginThreshold = 3; - if (_changedPlugins.length >= massChangePluginThreshold) { - logWarning('Ignoring potentially dangerous change, as this appears ' - 'to be a mass change.'); - return PackageResult.success(); - } - - printError('Dart changes are not allowed to other packages in ' - '$basePackageName in the same PR as changes to public Dart code in ' - '$platformInterfacePackageName, as this can cause accidental breaking ' - 'changes to be missed by automated checks. Please split the changes to ' - 'these two packages into separate PRs.\n\n' - 'If you believe that this is a false positive, please file a bug.'); - return PackageResult.fail( - ['$platformInterfacePackageName changed.']); - } - - Future _packageWillBePublished( - String pubspecRepoRelativePosixPath) async { - final File pubspecFile = childFileWithSubcomponents( - packagesDir.parent, p.posix.split(pubspecRepoRelativePosixPath)); - if (!pubspecFile.existsSync()) { - // If the package was deleted, nothing will be published. - return false; - } - final Pubspec pubspec = Pubspec.parse(pubspecFile.readAsStringSync()); - if (pubspec.publishTo == 'none') { - return false; - } - - final GitVersionFinder gitVersionFinder = await retrieveVersionFinder(); - final Version? previousVersion = - await gitVersionFinder.getPackageVersion(pubspecRepoRelativePosixPath); - if (previousVersion == null) { - // The plugin is new, so it will be published. - return true; - } - return pubspec.version != previousVersion; - } -} diff --git a/script/tool/lib/src/firebase_test_lab_command.dart b/script/tool/lib/src/firebase_test_lab_command.dart deleted file mode 100644 index a11284411908..000000000000 --- a/script/tool/lib/src/firebase_test_lab_command.dart +++ /dev/null @@ -1,358 +0,0 @@ -// 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 'dart:async'; -import 'dart:io' as io; - -import 'package:file/file.dart'; -import 'package:platform/platform.dart'; -import 'package:uuid/uuid.dart'; - -import 'common/core.dart'; -import 'common/gradle.dart'; -import 'common/package_looping_command.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -const int _exitGcloudAuthFailed = 2; - -/// A command to run tests via Firebase test lab. -class FirebaseTestLabCommand extends PackageLoopingCommand { - /// Creates an instance of the test runner command. - FirebaseTestLabCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : super(packagesDir, processRunner: processRunner, platform: platform) { - argParser.addOption( - 'project', - defaultsTo: 'flutter-cirrus', - help: 'The Firebase project name.', - ); - final String? homeDir = io.Platform.environment['HOME']; - argParser.addOption('service-key', - defaultsTo: homeDir == null - ? null - : path.join(homeDir, 'gcloud-service-key.json'), - help: 'The path to the service key for gcloud authentication.\n' - r'If not provided, \$HOME/gcloud-service-key.json will be ' - r'assumed if $HOME is set.'); - argParser.addOption('test-run-id', - defaultsTo: const Uuid().v4(), - help: - 'Optional string to append to the results path, to avoid conflicts. ' - 'Randomly chosen on each invocation if none is provided. ' - 'The default shown here is just an example.'); - argParser.addOption('build-id', - defaultsTo: - io.Platform.environment['CIRRUS_BUILD_ID'] ?? 'unknown_build', - help: - 'Optional string to append to the results path, to avoid conflicts. ' - r'Defaults to $CIRRUS_BUILD_ID if that is set.'); - argParser.addMultiOption('device', - splitCommas: false, - defaultsTo: [ - 'model=walleye,version=26', - 'model=redfin,version=30' - ], - help: - 'Device model(s) to test. See https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run for more info'); - argParser.addOption('results-bucket', - defaultsTo: 'gs://flutter_cirrus_testlab'); - argParser.addOption( - kEnableExperiment, - defaultsTo: '', - help: 'Enables the given Dart SDK experiments.', - ); - } - - @override - final String name = 'firebase-test-lab'; - - @override - final String description = 'Runs the instrumentation tests of the example ' - 'apps on Firebase Test Lab.\n\n' - 'Runs tests in test_instrumentation folder using the ' - 'instrumentation_test package.'; - - bool _firebaseProjectConfigured = false; - - Future _configureFirebaseProject() async { - if (_firebaseProjectConfigured) { - return; - } - - final String serviceKey = getStringArg('service-key'); - if (serviceKey.isEmpty) { - print('No --service-key provided; skipping gcloud authorization'); - } else { - final io.ProcessResult result = await processRunner.run( - 'gcloud', - [ - 'auth', - 'activate-service-account', - '--key-file=$serviceKey', - ], - logOnError: true, - ); - if (result.exitCode != 0) { - printError('Unable to activate gcloud account.'); - throw ToolExit(_exitGcloudAuthFailed); - } - final int exitCode = await processRunner.runAndStream('gcloud', [ - 'config', - 'set', - 'project', - getStringArg('project'), - ]); - print(''); - if (exitCode == 0) { - print('Firebase project configured.'); - } else { - logWarning( - 'Warning: gcloud config set returned a non-zero exit code. Continuing anyway.'); - } - } - _firebaseProjectConfigured = true; - } - - @override - Future runForPackage(RepositoryPackage package) async { - final List results = []; - for (final RepositoryPackage example in package.getExamples()) { - results.add(await _runForExample(example, package: package)); - } - - // If all results skipped, report skip overall. - if (results - .every((PackageResult result) => result.state == RunState.skipped)) { - return PackageResult.skip('No examples support Android.'); - } - // Otherwise, report failure if there were any failures. - final List allErrors = results - .map((PackageResult result) => - result.state == RunState.failed ? result.details : []) - .expand((List list) => list) - .toList(); - return allErrors.isEmpty - ? PackageResult.success() - : PackageResult.fail(allErrors); - } - - /// Runs the test for the given example of [package]. - Future _runForExample( - RepositoryPackage example, { - required RepositoryPackage package, - }) async { - final Directory androidDirectory = - example.platformDirectory(FlutterPlatform.android); - if (!androidDirectory.existsSync()) { - return PackageResult.skip( - '${example.displayName} does not support Android.'); - } - - final Directory uiTestDirectory = androidDirectory - .childDirectory('app') - .childDirectory('src') - .childDirectory('androidTest'); - if (!uiTestDirectory.existsSync()) { - printError('No androidTest directory found.'); - return PackageResult.fail( - ['No tests ran (use --exclude if this is intentional).']); - } - - // Ensure that the Dart integration tests will be run, not just native UI - // tests. - if (!await _testsContainDartIntegrationTestRunner(uiTestDirectory)) { - printError('No integration_test runner found. ' - 'See the integration_test package README for setup instructions.'); - return PackageResult.fail(['No integration_test runner.']); - } - - // Ensures that gradle wrapper exists - final GradleProject project = GradleProject(example, - processRunner: processRunner, platform: platform); - if (!await _ensureGradleWrapperExists(project)) { - return PackageResult.fail(['Unable to build example apk']); - } - - await _configureFirebaseProject(); - - if (!await _runGradle(project, 'app:assembleAndroidTest')) { - return PackageResult.fail(['Unable to assemble androidTest']); - } - - final List errors = []; - - // Used within the loop to ensure a unique GCS output location for each - // test file's run. - int resultsCounter = 0; - for (final File test in _findIntegrationTestFiles(example)) { - final String testName = - getRelativePosixPath(test, from: package.directory); - print('Testing $testName...'); - if (!await _runGradle(project, 'app:assembleDebug', testFile: test)) { - printError('Could not build $testName'); - errors.add('$testName failed to build'); - continue; - } - final String buildId = getStringArg('build-id'); - final String testRunId = getStringArg('test-run-id'); - final String resultsDir = - 'plugins_android_test/${package.displayName}/$buildId/$testRunId/' - '${example.directory.basename}/${resultsCounter++}/'; - - // Automatically retry failures; there is significant flake with these - // tests whose cause isn't yet understood, and having to re-run the - // entire shard for a flake in any one test is extremely slow. This should - // be removed once the root cause of the flake is understood. - // See https://github.com/flutter/flutter/issues/95063 - const int maxRetries = 2; - bool passing = false; - for (int i = 1; i <= maxRetries && !passing; ++i) { - if (i > 1) { - logWarning('$testName failed on attempt ${i - 1}. Retrying...'); - } - passing = await _runFirebaseTest(example, test, resultsDir: resultsDir); - } - if (!passing) { - printError('Test failure for $testName after $maxRetries attempts'); - errors.add('$testName failed tests'); - } - } - - if (errors.isEmpty && resultsCounter == 0) { - printError('No integration tests were run.'); - errors.add('No tests ran (use --exclude if this is intentional).'); - } - - return errors.isEmpty - ? PackageResult.success() - : PackageResult.fail(errors); - } - - /// Checks that Gradle has been configured for [project], and if not runs a - /// Flutter build to generate it. - /// - /// Returns true if either gradlew was already present, or the build succeeds. - Future _ensureGradleWrapperExists(GradleProject project) async { - if (!project.isConfigured()) { - print('Running flutter build apk...'); - final String experiment = getStringArg(kEnableExperiment); - final int exitCode = await processRunner.runAndStream( - flutterCommand, - [ - 'build', - 'apk', - if (experiment.isNotEmpty) '--enable-experiment=$experiment', - ], - workingDir: project.androidDirectory); - - if (exitCode != 0) { - return false; - } - } - return true; - } - - /// Runs [test] from [example] as a Firebase Test Lab test, returning true if - /// the test passed. - /// - /// [resultsDir] should be a unique-to-the-test-run directory to store the - /// results on the server. - Future _runFirebaseTest( - RepositoryPackage example, - File test, { - required String resultsDir, - }) async { - final List args = [ - 'firebase', - 'test', - 'android', - 'run', - '--type', - 'instrumentation', - '--app', - 'build/app/outputs/apk/debug/app-debug.apk', - '--test', - 'build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk', - '--timeout', - '7m', - '--results-bucket=${getStringArg('results-bucket')}', - '--results-dir=$resultsDir', - for (final String device in getStringListArg('device')) ...[ - '--device', - device - ], - ]; - final int exitCode = await processRunner.runAndStream('gcloud', args, - workingDir: example.directory); - - return exitCode == 0; - } - - /// Builds [target] using Gradle in the given [project]. Assumes Gradle is - /// already configured. - /// - /// [testFile] optionally does the Flutter build with the given test file as - /// the build target. - /// - /// Returns true if the command succeeds. - Future _runGradle( - GradleProject project, - String target, { - File? testFile, - }) async { - final String experiment = getStringArg(kEnableExperiment); - final String? extraOptions = experiment.isNotEmpty - ? Uri.encodeComponent('--enable-experiment=$experiment') - : null; - - final int exitCode = await project.runCommand( - target, - arguments: [ - '-Pverbose=true', - if (testFile != null) '-Ptarget=${testFile.path}', - if (extraOptions != null) '-Pextra-front-end-options=$extraOptions', - if (extraOptions != null) '-Pextra-gen-snapshot-options=$extraOptions', - ], - ); - - if (exitCode != 0) { - return false; - } - return true; - } - - /// Finds and returns all integration test files for [example]. - Iterable _findIntegrationTestFiles(RepositoryPackage example) sync* { - final Directory integrationTestDir = - example.directory.childDirectory('integration_test'); - - if (!integrationTestDir.existsSync()) { - return; - } - - yield* integrationTestDir - .listSync(recursive: true) - .where((FileSystemEntity file) => - file is File && file.basename.endsWith('_test.dart')) - .cast(); - } - - /// Returns true if any of the test files in [uiTestDirectory] contain the - /// annotation that means that the test will reports the results of running - /// the Dart integration tests. - Future _testsContainDartIntegrationTestRunner( - Directory uiTestDirectory) async { - return uiTestDirectory - .list(recursive: true, followLinks: false) - .where((FileSystemEntity entity) => entity is File) - .cast() - .any((File file) { - return file.basename.endsWith('.java') && - file.readAsStringSync().contains('@RunWith(FlutterTestRunner.class)'); - }); - } -} diff --git a/script/tool/lib/src/fix_command.dart b/script/tool/lib/src/fix_command.dart deleted file mode 100644 index 2819609eabbd..000000000000 --- a/script/tool/lib/src/fix_command.dart +++ /dev/null @@ -1,51 +0,0 @@ -// 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 'dart:async'; - -import 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -/// A command to run Dart's "fix" command on packages. -class FixCommand extends PackageLoopingCommand { - /// Creates a fix command instance. - FixCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : super(packagesDir, processRunner: processRunner, platform: platform); - - @override - final String name = 'fix'; - - @override - final String description = 'Fixes packages using dart fix.\n\n' - 'This command requires "dart" and "flutter" to be in your path, and ' - 'assumes that dependencies have already been fetched (e.g., by running ' - 'the analyze command first).'; - - @override - final bool hasLongOutput = false; - - @override - PackageLoopingType get packageLoopingType => - PackageLoopingType.includeAllSubpackages; - - @override - Future runForPackage(RepositoryPackage package) async { - final int exitCode = await processRunner.runAndStream( - 'dart', ['fix', '--apply'], - workingDir: package.directory); - if (exitCode != 0) { - printError('Unable to automatically fix package.'); - return PackageResult.fail(); - } - return PackageResult.success(); - } -} diff --git a/script/tool/lib/src/format_command.dart b/script/tool/lib/src/format_command.dart deleted file mode 100644 index e4236878658c..000000000000 --- a/script/tool/lib/src/format_command.dart +++ /dev/null @@ -1,369 +0,0 @@ -// 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 'dart:convert'; -import 'dart:io' as io; - -import 'package:file/file.dart'; -import 'package:http/http.dart' as http; -import 'package:meta/meta.dart'; -import 'package:platform/platform.dart'; - -import 'common/core.dart'; -import 'common/package_command.dart'; -import 'common/process_runner.dart'; - -/// In theory this should be 8191, but in practice that was still resulting in -/// "The input line is too long" errors. This was chosen as a value that worked -/// in practice in testing with flutter/plugins, but may need to be adjusted -/// based on further experience. -@visibleForTesting -const int windowsCommandLineMax = 8000; - -/// This value is picked somewhat arbitrarily based on checking `ARG_MAX` on a -/// macOS and Linux machine. If anyone encounters a lower limit in pratice, it -/// can be lowered accordingly. -@visibleForTesting -const int nonWindowsCommandLineMax = 1000000; - -const int _exitClangFormatFailed = 3; -const int _exitFlutterFormatFailed = 4; -const int _exitJavaFormatFailed = 5; -const int _exitGitFailed = 6; -const int _exitDependencyMissing = 7; - -final Uri _googleFormatterUrl = Uri.https('github.com', - '/google/google-java-format/releases/download/google-java-format-1.3/google-java-format-1.3-all-deps.jar'); - -/// A command to format all package code. -class FormatCommand extends PackageCommand { - /// Creates an instance of the format command. - FormatCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : super(packagesDir, processRunner: processRunner, platform: platform) { - argParser.addFlag('fail-on-change', hide: true); - argParser.addOption('clang-format', - defaultsTo: 'clang-format', help: 'Path to "clang-format" executable.'); - argParser.addOption('java', - defaultsTo: 'java', help: 'Path to "java" executable.'); - } - - @override - final String name = 'format'; - - @override - final String description = - 'Formats the code of all packages (Java, Objective-C, C++, and Dart).\n\n' - 'This command requires "git", "flutter" and "clang-format" v5 to be in ' - 'your path.'; - - @override - Future run() async { - final String googleFormatterPath = await _getGoogleFormatterPath(); - - // This class is not based on PackageLoopingCommand because running the - // formatters separately for each package is an order of magnitude slower, - // due to the startup overhead of the formatters. - final Iterable files = - await _getFilteredFilePaths(getFiles(), relativeTo: packagesDir); - await _formatDart(files); - await _formatJava(files, googleFormatterPath); - await _formatCppAndObjectiveC(files); - - if (getBoolArg('fail-on-change')) { - final bool modified = await _didModifyAnything(); - if (modified) { - throw ToolExit(exitCommandFoundErrors); - } - } - } - - Future _didModifyAnything() async { - final io.ProcessResult modifiedFiles = await processRunner.run( - 'git', - ['ls-files', '--modified'], - workingDir: packagesDir, - logOnError: true, - ); - if (modifiedFiles.exitCode != 0) { - printError('Unable to determine changed files.'); - throw ToolExit(_exitGitFailed); - } - - print('\n\n'); - - final String stdout = modifiedFiles.stdout as String; - if (stdout.isEmpty) { - print('All files formatted correctly.'); - return false; - } - - print('These files are not formatted correctly (see diff below):'); - LineSplitter.split(stdout).map((String line) => ' $line').forEach(print); - - print('\nTo fix run "dart pub global activate flutter_plugin_tools && ' - 'dart pub global run flutter_plugin_tools format" or copy-paste ' - 'this command into your terminal:'); - - final io.ProcessResult diff = await processRunner.run( - 'git', - ['diff'], - workingDir: packagesDir, - logOnError: true, - ); - if (diff.exitCode != 0) { - printError('Unable to determine diff.'); - throw ToolExit(_exitGitFailed); - } - print('patch -p1 < _formatCppAndObjectiveC(Iterable files) async { - final Iterable clangFiles = _getPathsWithExtensions( - files, {'.h', '.m', '.mm', '.cc', '.cpp'}); - if (clangFiles.isNotEmpty) { - final String clangFormat = await _findValidClangFormat(); - - print('Formatting .cc, .cpp, .h, .m, and .mm files...'); - final int exitCode = await _runBatched( - clangFormat, ['-i', '--style=file'], - files: clangFiles); - if (exitCode != 0) { - printError( - 'Failed to format C, C++, and Objective-C files: exit code $exitCode.'); - throw ToolExit(_exitClangFormatFailed); - } - } - } - - Future _findValidClangFormat() async { - final String clangFormatArg = getStringArg('clang-format'); - if (await _hasDependency(clangFormatArg)) { - return clangFormatArg; - } - - // There is a known issue where "chromium/depot_tools/clang-format" - // fails with "Problem while looking for clang-format in Chromium source tree". - // Loop through all "clang-format"s in PATH until a working one is found, - // for example "/usr/local/bin/clang-format" or a "brew" installed version. - for (final String clangFormatPath in await _whichAll('clang-format')) { - if (await _hasDependency(clangFormatPath)) { - return clangFormatPath; - } - } - printError('Unable to run "clang-format". Make sure that it is in your ' - 'path, or provide a full path with --clang-format.'); - throw ToolExit(_exitDependencyMissing); - } - - Future _formatJava( - Iterable files, String googleFormatterPath) async { - final Iterable javaFiles = - _getPathsWithExtensions(files, {'.java'}); - if (javaFiles.isNotEmpty) { - final String java = getStringArg('java'); - if (!await _hasDependency(java)) { - printError( - 'Unable to run "java". Make sure that it is in your path, or ' - 'provide a full path with --java.'); - throw ToolExit(_exitDependencyMissing); - } - - print('Formatting .java files...'); - final int exitCode = await _runBatched( - java, ['-jar', googleFormatterPath, '--replace'], - files: javaFiles); - if (exitCode != 0) { - printError('Failed to format Java files: exit code $exitCode.'); - throw ToolExit(_exitJavaFormatFailed); - } - } - } - - Future _formatDart(Iterable files) async { - final Iterable dartFiles = - _getPathsWithExtensions(files, {'.dart'}); - if (dartFiles.isNotEmpty) { - print('Formatting .dart files...'); - final int exitCode = - await _runBatched('dart', ['format'], files: dartFiles); - if (exitCode != 0) { - printError('Failed to format Dart files: exit code $exitCode.'); - throw ToolExit(_exitFlutterFormatFailed); - } - } - } - - /// Given a stream of [files], returns the paths of any that are not in known - /// locations to ignore, relative to [relativeTo]. - Future> _getFilteredFilePaths( - Stream files, { - required Directory relativeTo, - }) async { - // Returns a pattern to check for [directories] as a subset of a file path. - RegExp pathFragmentForDirectories(List directories) { - String s = path.separator; - // Escape the separator for use in the regex. - if (s == r'\') { - s = r'\\'; - } - return RegExp('(?:^|$s)${path.joinAll(directories)}$s'); - } - - final String fromPath = relativeTo.path; - - // Dart files are allowed to have a pragma to disable auto-formatting. This - // was added because Hixie hurts when dealing with what dartfmt does to - // artisanally-formatted Dart, while Stuart gets really frustrated when - // dealing with PRs from newer contributors who don't know how to make Dart - // readable. After much discussion, it was decided that files in the plugins - // and packages repos that really benefit from hand-formatting (e.g. files - // with large blobs of hex literals) could be opted-out of the requirement - // that they be autoformatted, so long as the code's owner was willing to - // bear the cost of this during code reviews. - // In the event that code ownership moves to someone who does not hold the - // same views as the original owner, the pragma can be removed and the file - // auto-formatted. - const String handFormattedExtension = '.dart'; - const String handFormattedPragma = '// This file is hand-formatted.'; - - return files - .where((File file) { - // See comment above near [handFormattedPragma]. - return path.extension(file.path) != handFormattedExtension || - !file.readAsLinesSync().contains(handFormattedPragma); - }) - .map((File file) => path.relative(file.path, from: fromPath)) - .where((String path) => - // Ignore files in build/ directories (e.g., headers of frameworks) - // to avoid useless extra work in local repositories. - !path.contains( - pathFragmentForDirectories(['example', 'build'])) && - // Ignore files in Pods, which are not part of the repository. - !path.contains(pathFragmentForDirectories(['Pods'])) && - // Ignore .dart_tool/, which can have various intermediate files. - !path.contains(pathFragmentForDirectories(['.dart_tool']))) - .toList(); - } - - Iterable _getPathsWithExtensions( - Iterable files, Set extensions) { - return files.where( - (String filePath) => extensions.contains(path.extension(filePath))); - } - - Future _getGoogleFormatterPath() async { - final String javaFormatterPath = path.join( - path.dirname(path.fromUri(platform.script)), - 'google-java-format-1.3-all-deps.jar'); - final File javaFormatterFile = - packagesDir.fileSystem.file(javaFormatterPath); - - if (!javaFormatterFile.existsSync()) { - print('Downloading Google Java Format...'); - final http.Response response = await http.get(_googleFormatterUrl); - javaFormatterFile.writeAsBytesSync(response.bodyBytes); - } - - return javaFormatterPath; - } - - /// Returns true if [command] can be run successfully. - Future _hasDependency(String command) async { - // Some versions of Java accept both -version and --version, but some only - // accept -version. - final String versionFlag = command == 'java' ? '-version' : '--version'; - try { - final io.ProcessResult result = - await processRunner.run(command, [versionFlag]); - if (result.exitCode != 0) { - return false; - } - } on io.ProcessException { - // Thrown when the binary is missing entirely. - return false; - } - return true; - } - - /// Returns all instances of [command] executable found on user path. - Future> _whichAll(String command) async { - try { - final io.ProcessResult result = - await processRunner.run('which', ['-a', command]); - - if (result.exitCode != 0) { - return []; - } - - final String stdout = (result.stdout as String).trim(); - if (stdout.isEmpty) { - return []; - } - return LineSplitter.split(stdout).toList(); - } on io.ProcessException { - return []; - } - } - - /// Runs [command] on [arguments] on all of the files in [files], batched as - /// necessary to avoid OS command-line length limits. - /// - /// Returns the exit code of the first failure, which stops the run, or 0 - /// on success. - Future _runBatched( - String command, - List arguments, { - required Iterable files, - }) async { - final int commandLineMax = - platform.isWindows ? windowsCommandLineMax : nonWindowsCommandLineMax; - - // Compute the max length of the file argument portion of a batch. - // Add one to each argument's length for the space before it. - final int argumentTotalLength = - arguments.fold(0, (int sum, String arg) => sum + arg.length + 1); - final int batchMaxTotalLength = - commandLineMax - command.length - argumentTotalLength; - - // Run the command in batches. - final List> batches = - _partitionFileList(files, maxStringLength: batchMaxTotalLength); - for (final List batch in batches) { - batch.sort(); // For ease of testing. - final int exitCode = await processRunner.runAndStream( - command, [...arguments, ...batch], - workingDir: packagesDir); - if (exitCode != 0) { - return exitCode; - } - } - return 0; - } - - /// Partitions [files] into batches whose max string length as parameters to - /// a command (including the spaces between them, and between the list and - /// the command itself) is no longer than [maxStringLength]. - List> _partitionFileList(Iterable files, - {required int maxStringLength}) { - final List> batches = >[[]]; - int currentBatchTotalLength = 0; - for (final String file in files) { - final int length = file.length + 1 /* for the space */; - if (currentBatchTotalLength + length > maxStringLength) { - // Start a new batch. - batches.add([]); - currentBatchTotalLength = 0; - } - batches.last.add(file); - currentBatchTotalLength += length; - } - return batches; - } -} diff --git a/script/tool/lib/src/license_check_command.dart b/script/tool/lib/src/license_check_command.dart deleted file mode 100644 index 0517bcf43298..000000000000 --- a/script/tool/lib/src/license_check_command.dart +++ /dev/null @@ -1,308 +0,0 @@ -// 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 'package:file/file.dart'; -import 'package:git/git.dart'; -import 'package:path/path.dart' as p; -import 'package:platform/platform.dart'; - -import 'common/core.dart'; -import 'common/package_command.dart'; - -const Set _codeFileExtensions = { - '.c', - '.cc', - '.cpp', - '.dart', - '.h', - '.html', - '.java', - '.kt', - '.m', - '.mm', - '.swift', - '.sh', -}; - -// Basenames without extensions of files to ignore. -const Set _ignoreBasenameList = { - 'flutter_export_environment', - 'GeneratedPluginRegistrant', - 'generated_plugin_registrant', -}; - -// File suffixes that otherwise match _codeFileExtensions to ignore. -const Set _ignoreSuffixList = { - '.g.dart', // Generated API code. - '.mocks.dart', // Generated by Mockito. -}; - -// Full basenames of files to ignore. -const Set _ignoredFullBasenameList = { - 'resource.h', // Generated by VS. -}; - -// Copyright and license regexes for third-party code. -// -// These are intentionally very simple, since there is very little third-party -// code in this repository. Complexity can be added as-needed on a case-by-case -// basis. -// -// When adding license regexes here, include the copyright info to ensure that -// any new additions are flagged for added scrutiny in review. -final List _thirdPartyLicenseBlockRegexes = [ - // Third-party code used in url_launcher_web. - RegExp( - r'^// Copyright 2017 Workiva Inc\..*' - r'^// Licensed under the Apache License, Version 2\.0', - multiLine: true, - dotAll: true, - ), - // Third-party code used in google_maps_flutter_web. - RegExp( - r'^// The MIT License [^C]+ Copyright \(c\) 2008 Krasimir Tsonev', - multiLine: true, - ), - // bsdiff in flutter/packages. - RegExp( - r'// Copyright 2003-2005 Colin Percival\. All rights reserved\.\n' - r'// Use of this source code is governed by a BSD-style license that can be\n' - r'// found in the LICENSE file\.\n', - ), -]; - -// The exact format of the BSD license that our license files should contain. -// Slight variants are not accepted because they may prevent consolidation in -// tools that assemble all licenses used in distributed applications. -// standardized. -const String _fullBsdLicenseText = ''' -Copyright 2013 The Flutter Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -'''; - -/// Validates that code files have copyright and license blocks. -class LicenseCheckCommand extends PackageCommand { - /// Creates a new license check command for [packagesDir]. - LicenseCheckCommand(Directory packagesDir, - {Platform platform = const LocalPlatform(), GitDir? gitDir}) - : super(packagesDir, platform: platform, gitDir: gitDir); - - @override - final String name = 'license-check'; - - @override - final String description = - 'Ensures that all code files have copyright/license blocks.'; - - @override - Future run() async { - // Create a set of absolute paths to submodule directories, with trailing - // separator, to do prefix matching with to test directory inclusion. - final Iterable submodulePaths = (await _getSubmoduleDirectories()) - .map( - (Directory dir) => '${dir.absolute.path}${platform.pathSeparator}'); - - final Iterable allFiles = (await _getAllFiles()).where( - (File file) => !submodulePaths.any(file.absolute.path.startsWith)); - - final Iterable codeFiles = allFiles.where((File file) => - _codeFileExtensions.contains(p.extension(file.path)) && - !_shouldIgnoreFile(file)); - final Iterable firstPartyLicenseFiles = allFiles.where((File file) => - path.basename(file.basename) == 'LICENSE' && !_isThirdParty(file)); - - final List licenseFileFailures = - await _checkLicenseFiles(firstPartyLicenseFiles); - final Map<_LicenseFailureType, List> codeFileFailures = - await _checkCodeLicenses(codeFiles); - - bool passed = true; - - print('\n=======================================\n'); - - if (licenseFileFailures.isNotEmpty) { - passed = false; - printError( - 'The following LICENSE files do not follow the expected format:'); - for (final File file in licenseFileFailures) { - printError(' ${file.path}'); - } - printError('Please ensure that they use the exact format used in this ' - 'repository".\n'); - } - - if (codeFileFailures[_LicenseFailureType.incorrectFirstParty]!.isNotEmpty) { - passed = false; - printError('The license block for these files is missing or incorrect:'); - for (final File file - in codeFileFailures[_LicenseFailureType.incorrectFirstParty]!) { - printError(' ${file.path}'); - } - printError( - 'If this third-party code, move it to a "third_party/" directory, ' - 'otherwise ensure that you are using the exact copyright and license ' - 'text used by all first-party files in this repository.\n'); - } - - if (codeFileFailures[_LicenseFailureType.unknownThirdParty]!.isNotEmpty) { - passed = false; - printError( - 'No recognized license was found for the following third-party files:'); - for (final File file - in codeFileFailures[_LicenseFailureType.unknownThirdParty]!) { - printError(' ${file.path}'); - } - print('Please check that they have a license at the top of the file. ' - 'If they do, the license check needs to be updated to recognize ' - 'the new third-party license block.\n'); - } - - if (!passed) { - throw ToolExit(1); - } - - printSuccess('All files passed validation!'); - } - - // Creates the expected copyright+license block for first-party code. - String _generateLicenseBlock( - String comment, { - String prefix = '', - String suffix = '', - }) { - return '$prefix${comment}Copyright 2013 The Flutter Authors. All rights reserved.\n' - '${comment}Use of this source code is governed by a BSD-style license that can be\n' - '${comment}found in the LICENSE file.$suffix\n'; - } - - /// Checks all license blocks for [codeFiles], returning any that fail - /// validation. - Future>> _checkCodeLicenses( - Iterable codeFiles) async { - final List incorrectFirstPartyFiles = []; - final List unrecognizedThirdPartyFiles = []; - - // Most code file types in the repository use '//' comments. - final String defaultFirstParyLicenseBlock = _generateLicenseBlock('// '); - // A few file types have a different comment structure. - final Map firstPartyLicenseBlockByExtension = - { - '.sh': _generateLicenseBlock('# '), - '.html': _generateLicenseBlock('', prefix: ''), - }; - - for (final File file in codeFiles) { - print('Checking ${file.path}'); - // On Windows, git may auto-convert line endings on checkout; this should - // still pass since they will be converted back on commit. - final String content = - (await file.readAsString()).replaceAll('\r\n', '\n'); - - final String firstParyLicense = - firstPartyLicenseBlockByExtension[p.extension(file.path)] ?? - defaultFirstParyLicenseBlock; - if (_isThirdParty(file)) { - // Third-party directories allow either known third-party licenses, our - // the first-party license, as there may be local additions. - if (!_thirdPartyLicenseBlockRegexes - .any((RegExp regex) => regex.hasMatch(content)) && - !content.contains(firstParyLicense)) { - unrecognizedThirdPartyFiles.add(file); - } - } else { - if (!content.contains(firstParyLicense)) { - incorrectFirstPartyFiles.add(file); - } - } - } - - // Sort by path for more usable output. - int pathCompare(File a, File b) => a.path.compareTo(b.path); - incorrectFirstPartyFiles.sort(pathCompare); - unrecognizedThirdPartyFiles.sort(pathCompare); - - return <_LicenseFailureType, List>{ - _LicenseFailureType.incorrectFirstParty: incorrectFirstPartyFiles, - _LicenseFailureType.unknownThirdParty: unrecognizedThirdPartyFiles, - }; - } - - /// Checks all provided LICENSE [files], returning any that fail validation. - Future> _checkLicenseFiles(Iterable files) async { - final List incorrectLicenseFiles = []; - - for (final File file in files) { - print('Checking ${file.path}'); - // On Windows, git may auto-convert line endings on checkout; this should - // still pass since they will be converted back on commit. - final String contents = file.readAsStringSync().replaceAll('\r\n', '\n'); - if (!contents.contains(_fullBsdLicenseText)) { - incorrectLicenseFiles.add(file); - } - } - - return incorrectLicenseFiles; - } - - bool _shouldIgnoreFile(File file) { - final String path = file.path; - return _ignoreBasenameList.contains(p.basenameWithoutExtension(path)) || - _ignoreSuffixList.any((String suffix) => - path.endsWith(suffix) || - _ignoredFullBasenameList.contains(p.basename(path))); - } - - bool _isThirdParty(File file) { - return path.split(file.path).contains('third_party'); - } - - Future> _getAllFiles() => packagesDir.parent - .list(recursive: true, followLinks: false) - .where((FileSystemEntity entity) => entity is File) - .map((FileSystemEntity file) => file as File) - .toList(); - - // Returns the directories containing mapped submodules, if any. - Future> _getSubmoduleDirectories() async { - final List submodulePaths = []; - final Directory repoRoot = - packagesDir.fileSystem.directory((await gitDir).path); - final File submoduleSpec = repoRoot.childFile('.gitmodules'); - if (submoduleSpec.existsSync()) { - final RegExp pathLine = RegExp(r'path\s*=\s*(.*)'); - for (final String line in submoduleSpec.readAsLinesSync()) { - final RegExpMatch? match = pathLine.firstMatch(line); - if (match != null) { - submodulePaths.add(repoRoot.childDirectory(match.group(1)!.trim())); - } - } - } - return submodulePaths; - } -} - -enum _LicenseFailureType { incorrectFirstParty, unknownThirdParty } diff --git a/script/tool/lib/src/lint_android_command.dart b/script/tool/lib/src/lint_android_command.dart deleted file mode 100644 index eb78ce891685..000000000000 --- a/script/tool/lib/src/lint_android_command.dart +++ /dev/null @@ -1,67 +0,0 @@ -// 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 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'common/core.dart'; -import 'common/gradle.dart'; -import 'common/package_looping_command.dart'; -import 'common/plugin_utils.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -/// Run 'gradlew lint'. -/// -/// See https://developer.android.com/studio/write/lint. -class LintAndroidCommand extends PackageLoopingCommand { - /// Creates an instance of the linter command. - LintAndroidCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : super(packagesDir, processRunner: processRunner, platform: platform); - - @override - final String name = 'lint-android'; - - @override - final String description = 'Runs "gradlew lint" on Android plugins.\n\n' - 'Requires the examples to have been build at least once before running.'; - - @override - Future runForPackage(RepositoryPackage package) async { - if (!pluginSupportsPlatform(platformAndroid, package, - requiredMode: PlatformSupport.inline)) { - return PackageResult.skip( - 'Plugin does not have an Android implementation.'); - } - - bool failed = false; - for (final RepositoryPackage example in package.getExamples()) { - final GradleProject project = GradleProject(example, - processRunner: processRunner, platform: platform); - - if (!project.isConfigured()) { - return PackageResult.fail(['Build examples before linting']); - } - - final String packageName = package.directory.basename; - - // Only lint one build mode to avoid extra work. - // Only lint the plugin project itself, to avoid failing due to errors in - // dependencies. - // - // TODO(stuartmorgan): Consider adding an XML parser to read and summarize - // all results. Currently, only the first three errors will be shown - // inline, and the rest have to be checked via the CI-uploaded artifact. - final int exitCode = await project.runCommand('$packageName:lintDebug'); - if (exitCode != 0) { - failed = true; - } - } - - return failed ? PackageResult.fail() : PackageResult.success(); - } -} diff --git a/script/tool/lib/src/list_command.dart b/script/tool/lib/src/list_command.dart deleted file mode 100644 index b47657e47eff..000000000000 --- a/script/tool/lib/src/list_command.dart +++ /dev/null @@ -1,68 +0,0 @@ -// 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 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'common/package_command.dart'; -import 'common/repository_package.dart'; - -/// A command to list different types of repository content. -class ListCommand extends PackageCommand { - /// Creates an instance of the list command, whose behavior depends on the - /// 'type' argument it provides. - ListCommand( - Directory packagesDir, { - Platform platform = const LocalPlatform(), - }) : super(packagesDir, platform: platform) { - argParser.addOption( - _type, - defaultsTo: _package, - allowed: [_package, _example, _allPackage, _file], - help: 'What type of file system content to list.', - ); - } - - static const String _type = 'type'; - static const String _allPackage = 'package-or-subpackage'; - static const String _example = 'example'; - static const String _package = 'package'; - static const String _file = 'file'; - - @override - final String name = 'list'; - - @override - final String description = 'Lists packages or files'; - - @override - Future run() async { - switch (getStringArg(_type)) { - case _package: - await for (final PackageEnumerationEntry entry in getTargetPackages()) { - print(entry.package.path); - } - break; - case _example: - final Stream examples = getTargetPackages() - .expand( - (PackageEnumerationEntry entry) => entry.package.getExamples()); - await for (final RepositoryPackage package in examples) { - print(package.path); - } - break; - case _allPackage: - await for (final PackageEnumerationEntry entry - in getTargetPackagesAndSubpackages()) { - print(entry.package.path); - } - break; - case _file: - await for (final File file in getFiles()) { - print(file.path); - } - break; - } - } -} diff --git a/script/tool/lib/src/main.dart b/script/tool/lib/src/main.dart index 0083e0cbb8ee..6b421ebaebc0 100644 --- a/script/tool/lib/src/main.dart +++ b/script/tool/lib/src/main.dart @@ -9,34 +9,19 @@ import 'package:file/file.dart'; import 'package:file/local.dart'; import 'analyze_command.dart'; -import 'build_examples_command.dart'; import 'common/core.dart'; -import 'create_all_packages_app_command.dart'; -import 'custom_test_command.dart'; -import 'dependabot_check_command.dart'; -import 'drive_examples_command.dart'; -import 'federation_safety_check_command.dart'; -import 'firebase_test_lab_command.dart'; -import 'fix_command.dart'; -import 'format_command.dart'; -import 'license_check_command.dart'; -import 'lint_android_command.dart'; -import 'list_command.dart'; -import 'make_deps_path_based_command.dart'; -import 'native_test_command.dart'; -import 'podspec_check_command.dart'; -import 'publish_check_command.dart'; -import 'publish_command.dart'; -import 'pubspec_check_command.dart'; -import 'readme_check_command.dart'; -import 'remove_dev_dependencies.dart'; -import 'test_command.dart'; -import 'update_excerpts_command.dart'; -import 'update_release_info_command.dart'; -import 'version_check_command.dart'; -import 'xcode_analyze_command.dart'; void main(List args) { + print(''' +*** WARNING *** +This copy of the tooling is now only here as a shim for scripts in other +repositories that have not yet been updated, and can only run 'analyze'. For +full tooling in this repository, see the updated instructions: +https://github.com/flutter/packages/blob/main/script/tool/README.md +to switch to running the published version. + +'''); + const FileSystem fileSystem = LocalFileSystem(); Directory packagesDir = @@ -54,32 +39,7 @@ void main(List args) { final CommandRunner commandRunner = CommandRunner( 'dart pub global run flutter_plugin_tools', 'Productivity utils for hosting multiple plugins within one repository.') - ..addCommand(AnalyzeCommand(packagesDir)) - ..addCommand(BuildExamplesCommand(packagesDir)) - ..addCommand(CreateAllPackagesAppCommand(packagesDir)) - ..addCommand(CustomTestCommand(packagesDir)) - ..addCommand(DependabotCheckCommand(packagesDir)) - ..addCommand(DriveExamplesCommand(packagesDir)) - ..addCommand(FederationSafetyCheckCommand(packagesDir)) - ..addCommand(FirebaseTestLabCommand(packagesDir)) - ..addCommand(FixCommand(packagesDir)) - ..addCommand(FormatCommand(packagesDir)) - ..addCommand(LicenseCheckCommand(packagesDir)) - ..addCommand(LintAndroidCommand(packagesDir)) - ..addCommand(PodspecCheckCommand(packagesDir)) - ..addCommand(ListCommand(packagesDir)) - ..addCommand(NativeTestCommand(packagesDir)) - ..addCommand(MakeDepsPathBasedCommand(packagesDir)) - ..addCommand(PublishCheckCommand(packagesDir)) - ..addCommand(PublishCommand(packagesDir)) - ..addCommand(PubspecCheckCommand(packagesDir)) - ..addCommand(ReadmeCheckCommand(packagesDir)) - ..addCommand(RemoveDevDependenciesCommand(packagesDir)) - ..addCommand(TestCommand(packagesDir)) - ..addCommand(UpdateExcerptsCommand(packagesDir)) - ..addCommand(UpdateReleaseInfoCommand(packagesDir)) - ..addCommand(VersionCheckCommand(packagesDir)) - ..addCommand(XcodeAnalyzeCommand(packagesDir)); + ..addCommand(AnalyzeCommand(packagesDir)); commandRunner.run(args).catchError((Object e) { final ToolExit toolExit = e as ToolExit; diff --git a/script/tool/lib/src/make_deps_path_based_command.dart b/script/tool/lib/src/make_deps_path_based_command.dart deleted file mode 100644 index 10abcd44ae6e..000000000000 --- a/script/tool/lib/src/make_deps_path_based_command.dart +++ /dev/null @@ -1,283 +0,0 @@ -// 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 'package:file/file.dart'; -import 'package:git/git.dart'; -import 'package:path/path.dart' as p; -import 'package:pub_semver/pub_semver.dart'; - -import 'common/core.dart'; -import 'common/git_version_finder.dart'; -import 'common/package_command.dart'; -import 'common/repository_package.dart'; - -const int _exitPackageNotFound = 3; -const int _exitCannotUpdatePubspec = 4; - -enum _RewriteOutcome { changed, noChangesNeeded, alreadyChanged } - -/// Converts all dependencies on target packages to path-based dependencies. -/// -/// This is to allow for pre-publish testing of changes that could affect other -/// packages in the repository. For instance, this allows for catching cases -/// where a non-breaking change to a platform interface package of a federated -/// plugin would cause post-publish analyzer failures in another package of that -/// plugin. -class MakeDepsPathBasedCommand extends PackageCommand { - /// Creates an instance of the command to convert selected dependencies to - /// path-based. - MakeDepsPathBasedCommand( - Directory packagesDir, { - GitDir? gitDir, - }) : super(packagesDir, gitDir: gitDir) { - argParser.addMultiOption(_targetDependenciesArg, - help: - 'The names of the packages to convert to path-based dependencies.\n' - 'Ignored if --$_targetDependenciesWithNonBreakingUpdatesArg is ' - 'passed.', - valueHelp: 'some_package'); - argParser.addFlag( - _targetDependenciesWithNonBreakingUpdatesArg, - help: 'Causes all packages that have non-breaking version changes ' - 'when compared against the git base to be treated as target ' - 'packages.', - ); - } - - static const String _targetDependenciesArg = 'target-dependencies'; - static const String _targetDependenciesWithNonBreakingUpdatesArg = - 'target-dependencies-with-non-breaking-updates'; - - // The comment to add to temporary dependency overrides. - static const String _dependencyOverrideWarningComment = - '# FOR TESTING ONLY. DO NOT MERGE.'; - - @override - final String name = 'make-deps-path-based'; - - @override - final String description = - 'Converts package dependencies to path-based references.'; - - @override - Future run() async { - final Set targetDependencies = - getBoolArg(_targetDependenciesWithNonBreakingUpdatesArg) - ? await _getNonBreakingUpdatePackages() - : getStringListArg(_targetDependenciesArg).toSet(); - - if (targetDependencies.isEmpty) { - print('No target dependencies; nothing to do.'); - return; - } - print('Rewriting references to: ${targetDependencies.join(', ')}...'); - - final Map localDependencyPackages = - _findLocalPackages(targetDependencies); - - final String repoRootPath = (await gitDir).path; - for (final File pubspec in await _getAllPubspecs()) { - final String displayPath = p.posix.joinAll( - path.split(path.relative(pubspec.absolute.path, from: repoRootPath))); - final _RewriteOutcome outcome = await _addDependencyOverridesIfNecessary( - pubspec, localDependencyPackages); - switch (outcome) { - case _RewriteOutcome.changed: - print(' Modified $displayPath'); - break; - case _RewriteOutcome.alreadyChanged: - print(' Skipped $displayPath - Already rewritten'); - break; - case _RewriteOutcome.noChangesNeeded: - break; - } - } - } - - Map _findLocalPackages(Set packageNames) { - final Map targets = - {}; - for (final String packageName in packageNames) { - final Directory topLevelCandidate = - packagesDir.childDirectory(packageName); - // If packages// exists, then either that directory is the - // package, or packages/// exists and is the - // package (in the case of a federated plugin). - if (topLevelCandidate.existsSync()) { - final Directory appFacingCandidate = - topLevelCandidate.childDirectory(packageName); - targets[packageName] = RepositoryPackage(appFacingCandidate.existsSync() - ? appFacingCandidate - : topLevelCandidate); - continue; - } - // If there is no packages/ directory, then either the - // packages doesn't exist, or it is a sub-package of a federated plugin. - // If it's the latter, it will be a directory whose name is a prefix. - for (final FileSystemEntity entity in packagesDir.listSync()) { - if (entity is Directory && packageName.startsWith(entity.basename)) { - final Directory subPackageCandidate = - entity.childDirectory(packageName); - if (subPackageCandidate.existsSync()) { - targets[packageName] = RepositoryPackage(subPackageCandidate); - break; - } - } - } - - if (!targets.containsKey(packageName)) { - printError('Unable to find package "$packageName"'); - throw ToolExit(_exitPackageNotFound); - } - } - return targets; - } - - /// If [pubspecFile] has any dependencies on packages in [localDependencies], - /// adds dependency_overrides entries to redirect them to the local version - /// using path-based dependencies. - Future<_RewriteOutcome> _addDependencyOverridesIfNecessary(File pubspecFile, - Map localDependencies) async { - final String pubspecContents = pubspecFile.readAsStringSync(); - final Pubspec pubspec = Pubspec.parse(pubspecContents); - // Fail if there are any dependency overrides already, other than ones - // created by this script. If support for that is needed at some point, it - // can be added, but currently it's not and relying on that makes the logic - // here much simpler. - if (pubspec.dependencyOverrides.isNotEmpty) { - if (pubspecContents.contains(_dependencyOverrideWarningComment)) { - return _RewriteOutcome.alreadyChanged; - } - printError( - 'Packages with dependency overrides are not currently supported.'); - throw ToolExit(_exitCannotUpdatePubspec); - } - - final Iterable combinedDependencies = [ - ...pubspec.dependencies.keys, - ...pubspec.devDependencies.keys, - ]; - final List packagesToOverride = combinedDependencies - .where( - (String packageName) => localDependencies.containsKey(packageName)) - .toList(); - // Sort the combined list to avoid sort_pub_dependencies lint violations. - packagesToOverride.sort(); - if (packagesToOverride.isNotEmpty) { - final String commonBasePath = packagesDir.path; - // Find the relative path to the common base. - final int packageDepth = path - .split(path.relative(pubspecFile.parent.absolute.path, - from: commonBasePath)) - .length; - final List relativeBasePathComponents = - List.filled(packageDepth, '..'); - // This is done via strings rather than by manipulating the Pubspec and - // then re-serialiazing so that it's a localized change, rather than - // rewriting the whole file (e.g., destroying comments), which could be - // more disruptive for local use. - String newPubspecContents = ''' -$pubspecContents - -$_dependencyOverrideWarningComment -dependency_overrides: -'''; - for (final String packageName in packagesToOverride) { - // Find the relative path from the common base to the local package. - final List repoRelativePathComponents = path.split( - path.relative(localDependencies[packageName]!.path, - from: commonBasePath)); - newPubspecContents += ''' - $packageName: - path: ${p.posix.joinAll([ - ...relativeBasePathComponents, - ...repoRelativePathComponents, - ])} -'''; - } - pubspecFile.writeAsStringSync(newPubspecContents); - return _RewriteOutcome.changed; - } - return _RewriteOutcome.noChangesNeeded; - } - - /// Returns all pubspecs anywhere under the packages directory. - Future> _getAllPubspecs() => packagesDir.parent - .list(recursive: true, followLinks: false) - .where((FileSystemEntity entity) => - entity is File && p.basename(entity.path) == 'pubspec.yaml') - .map((FileSystemEntity file) => file as File) - .toList(); - - /// Returns all packages that have non-breaking published changes (i.e., a - /// minor or bugfix version change) relative to the git comparison base. - /// - /// Prints status information about what was checked for ease of auditing logs - /// in CI. - Future> _getNonBreakingUpdatePackages() async { - final GitVersionFinder gitVersionFinder = await retrieveVersionFinder(); - final String baseSha = await gitVersionFinder.getBaseSha(); - print('Finding changed packages relative to "$baseSha"...'); - - final Set changedPackages = {}; - for (final String changedPath in await gitVersionFinder.getChangedFiles()) { - // Git output always uses Posix paths. - final List allComponents = p.posix.split(changedPath); - // Only pubspec changes are potential publishing events. - if (allComponents.last != 'pubspec.yaml' || - allComponents.contains('example')) { - continue; - } - if (!allComponents.contains(packagesDir.basename)) { - print(' Skipping $changedPath; not in packages directory.'); - continue; - } - final RepositoryPackage package = - RepositoryPackage(packagesDir.fileSystem.file(changedPath).parent); - // Ignored deleted packages, as they won't be published. - if (!package.pubspecFile.existsSync()) { - final String directoryName = p.posix.joinAll(path.split(path.relative( - package.directory.absolute.path, - from: packagesDir.path))); - print(' Skipping $directoryName; deleted.'); - continue; - } - final String packageName = package.parsePubspec().name; - if (!await _hasNonBreakingVersionChange(package)) { - // Log packages that had pubspec changes but weren't included for ease - // of auditing CI. - print(' Skipping $packageName; no non-breaking version change.'); - continue; - } - changedPackages.add(packageName); - } - return changedPackages; - } - - Future _hasNonBreakingVersionChange(RepositoryPackage package) async { - final Pubspec pubspec = package.parsePubspec(); - if (pubspec.publishTo == 'none') { - return false; - } - - final String pubspecGitPath = p.posix.joinAll(path.split(path.relative( - package.pubspecFile.absolute.path, - from: (await gitDir).path))); - final GitVersionFinder gitVersionFinder = await retrieveVersionFinder(); - final Version? previousVersion = - await gitVersionFinder.getPackageVersion(pubspecGitPath); - if (previousVersion == null) { - // The plugin is new, so nothing can be depending on it yet. - return false; - } - final Version newVersion = pubspec.version!; - if ((newVersion.major > 0 && newVersion.major != previousVersion.major) || - (newVersion.major == 0 && newVersion.minor != previousVersion.minor)) { - // Breaking changes aren't targetted since they won't be picked up - // automatically. - return false; - } - return newVersion != previousVersion; - } -} diff --git a/script/tool/lib/src/native_test_command.dart b/script/tool/lib/src/native_test_command.dart deleted file mode 100644 index af5f4df98e86..000000000000 --- a/script/tool/lib/src/native_test_command.dart +++ /dev/null @@ -1,624 +0,0 @@ -// 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 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'common/cmake.dart'; -import 'common/core.dart'; -import 'common/gradle.dart'; -import 'common/package_looping_command.dart'; -import 'common/plugin_utils.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; -import 'common/xcode.dart'; - -const String _unitTestFlag = 'unit'; -const String _integrationTestFlag = 'integration'; - -const String _iOSDestinationFlag = 'ios-destination'; - -const int _exitNoIOSSimulators = 3; - -/// The command to run native tests for plugins: -/// - iOS and macOS: XCTests (XCUnitTest and XCUITest) -/// - Android: JUnit tests -/// - Windows and Linux: GoogleTest tests -class NativeTestCommand extends PackageLoopingCommand { - /// Creates an instance of the test command. - NativeTestCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : _xcode = Xcode(processRunner: processRunner, log: true), - super(packagesDir, processRunner: processRunner, platform: platform) { - argParser.addOption( - _iOSDestinationFlag, - help: 'Specify the destination when running iOS tests.\n' - 'This is passed to the `-destination` argument in the xcodebuild command.\n' - 'See https://developer.apple.com/library/archive/technotes/tn2339/_index.html#//apple_ref/doc/uid/DTS40014588-CH1-UNIT ' - 'for details on how to specify the destination.', - ); - argParser.addFlag(platformAndroid, help: 'Runs Android tests'); - argParser.addFlag(platformIOS, help: 'Runs iOS tests'); - argParser.addFlag(platformLinux, help: 'Runs Linux tests'); - argParser.addFlag(platformMacOS, help: 'Runs macOS tests'); - argParser.addFlag(platformWindows, help: 'Runs Windows tests'); - - // By default, both unit tests and integration tests are run, but provide - // flags to disable one or the other. - argParser.addFlag(_unitTestFlag, - help: 'Runs native unit tests', defaultsTo: true); - argParser.addFlag(_integrationTestFlag, - help: 'Runs native integration (UI) tests', defaultsTo: true); - } - - // The device destination flags for iOS tests. - List _iOSDestinationFlags = []; - - final Xcode _xcode; - - @override - final String name = 'native-test'; - - @override - final String description = ''' -Runs native unit tests and native integration tests. - -Currently supported platforms: -- Android -- iOS: requires 'xcrun' to be in your path. -- Linux (unit tests only) -- macOS: requires 'xcrun' to be in your path. -- Windows (unit tests only) - -The example app(s) must be built for all targeted platforms before running -this command. -'''; - - Map _platforms = {}; - - List _requestedPlatforms = []; - - @override - Future initializeRun() async { - _platforms = { - platformAndroid: _PlatformDetails('Android', _testAndroid), - platformIOS: _PlatformDetails('iOS', _testIOS), - platformLinux: _PlatformDetails('Linux', _testLinux), - platformMacOS: _PlatformDetails('macOS', _testMacOS), - platformWindows: _PlatformDetails('Windows', _testWindows), - }; - _requestedPlatforms = _platforms.keys - .where((String platform) => getBoolArg(platform)) - .toList(); - _requestedPlatforms.sort(); - - if (_requestedPlatforms.isEmpty) { - printError('At least one platform flag must be provided.'); - throw ToolExit(exitInvalidArguments); - } - - if (!(getBoolArg(_unitTestFlag) || getBoolArg(_integrationTestFlag))) { - printError('At least one test type must be enabled.'); - throw ToolExit(exitInvalidArguments); - } - - if (getBoolArg(platformWindows) && getBoolArg(_integrationTestFlag)) { - logWarning('This command currently only supports unit tests for Windows. ' - 'See https://github.com/flutter/flutter/issues/70233.'); - } - - if (getBoolArg(platformLinux) && getBoolArg(_integrationTestFlag)) { - logWarning('This command currently only supports unit tests for Linux. ' - 'See https://github.com/flutter/flutter/issues/70235.'); - } - - // iOS-specific run-level state. - if (_requestedPlatforms.contains('ios')) { - String destination = getStringArg(_iOSDestinationFlag); - if (destination.isEmpty) { - final String? simulatorId = - await _xcode.findBestAvailableIphoneSimulator(); - if (simulatorId == null) { - printError('Cannot find any available iOS simulators.'); - throw ToolExit(_exitNoIOSSimulators); - } - destination = 'id=$simulatorId'; - } - _iOSDestinationFlags = [ - '-destination', - destination, - ]; - } - } - - @override - Future runForPackage(RepositoryPackage package) async { - final List testPlatforms = []; - for (final String platform in _requestedPlatforms) { - if (!pluginSupportsPlatform(platform, package, - requiredMode: PlatformSupport.inline)) { - print('No implementation for ${_platforms[platform]!.label}.'); - continue; - } - if (!pluginHasNativeCodeForPlatform(platform, package)) { - print('No native code for ${_platforms[platform]!.label}.'); - continue; - } - testPlatforms.add(platform); - } - - if (testPlatforms.isEmpty) { - return PackageResult.skip('Nothing to test for target platform(s).'); - } - - final _TestMode mode = _TestMode( - unit: getBoolArg(_unitTestFlag), - integration: getBoolArg(_integrationTestFlag), - ); - - bool ranTests = false; - bool failed = false; - final List failureMessages = []; - for (final String platform in testPlatforms) { - final _PlatformDetails platformInfo = _platforms[platform]!; - print('Running tests for ${platformInfo.label}...'); - print('----------------------------------------'); - final _PlatformResult result = - await platformInfo.testFunction(package, mode); - ranTests |= result.state != RunState.skipped; - if (result.state == RunState.failed) { - failed = true; - - final String? error = result.error; - // Only provide the failing platforms in the failure details if testing - // multiple platforms, otherwise it's just noise. - if (_requestedPlatforms.length > 1) { - failureMessages.add(error != null - ? '${platformInfo.label}: $error' - : platformInfo.label); - } else if (error != null) { - // If there's only one platform, only provide error details in the - // summary if the platform returned a message. - failureMessages.add(error); - } - } - } - - if (!ranTests) { - return PackageResult.skip('No tests found.'); - } - return failed - ? PackageResult.fail(failureMessages) - : PackageResult.success(); - } - - Future<_PlatformResult> _testAndroid( - RepositoryPackage plugin, _TestMode mode) async { - bool exampleHasUnitTests(RepositoryPackage example) { - return example - .platformDirectory(FlutterPlatform.android) - .childDirectory('app') - .childDirectory('src') - .childDirectory('test') - .existsSync() || - plugin - .platformDirectory(FlutterPlatform.android) - .childDirectory('src') - .childDirectory('test') - .existsSync(); - } - - bool exampleHasNativeIntegrationTests(RepositoryPackage example) { - final Directory integrationTestDirectory = example - .platformDirectory(FlutterPlatform.android) - .childDirectory('app') - .childDirectory('src') - .childDirectory('androidTest'); - // There are two types of integration tests that can be in the androidTest - // directory: - // - FlutterTestRunner.class tests, which bridge to Dart integration tests - // - Purely native tests - // Only the latter is supported by this command; the former will hang if - // run here because they will wait for a Dart call that will never come. - // - // This repository uses a convention of putting the former in a - // *ActivityTest.java file, so ignore that file when checking for tests. - // Also ignore DartIntegrationTest.java, which defines the annotation used - // below for filtering the former out when running tests. - // - // If those are the only files, then there are no tests to run here. - return integrationTestDirectory.existsSync() && - integrationTestDirectory - .listSync(recursive: true) - .whereType() - .any((File file) { - final String basename = file.basename; - return !basename.endsWith('ActivityTest.java') && - basename != 'DartIntegrationTest.java'; - }); - } - - final Iterable examples = plugin.getExamples(); - - bool ranUnitTests = false; - bool ranAnyTests = false; - bool failed = false; - bool hasMissingBuild = false; - for (final RepositoryPackage example in examples) { - final bool hasUnitTests = exampleHasUnitTests(example); - final bool hasIntegrationTests = - exampleHasNativeIntegrationTests(example); - - if (mode.unit && !hasUnitTests) { - _printNoExampleTestsMessage(example, 'Android unit'); - } - if (mode.integration && !hasIntegrationTests) { - _printNoExampleTestsMessage(example, 'Android integration'); - } - - final bool runUnitTests = mode.unit && hasUnitTests; - final bool runIntegrationTests = mode.integration && hasIntegrationTests; - if (!runUnitTests && !runIntegrationTests) { - continue; - } - - final String exampleName = example.displayName; - _printRunningExampleTestsMessage(example, 'Android'); - - final GradleProject project = GradleProject( - example, - processRunner: processRunner, - platform: platform, - ); - if (!project.isConfigured()) { - printError('ERROR: Run "flutter build apk" on $exampleName, or run ' - 'this tool\'s "build-examples --apk" command, ' - 'before executing tests.'); - failed = true; - hasMissingBuild = true; - continue; - } - - if (runUnitTests) { - print('Running unit tests...'); - final int exitCode = await project.runCommand('testDebugUnitTest'); - if (exitCode != 0) { - printError('$exampleName unit tests failed.'); - failed = true; - } - ranUnitTests = true; - ranAnyTests = true; - } - - if (runIntegrationTests) { - // FlutterTestRunner-based tests will hang forever if run in a normal - // app build, since they wait for a Dart call from integration_test that - // will never come. Those tests have an extra annotation to allow - // filtering them out. - const String filter = - 'notAnnotation=io.flutter.plugins.DartIntegrationTest'; - - print('Running integration tests...'); - final int exitCode = await project.runCommand( - 'app:connectedAndroidTest', - arguments: [ - '-Pandroid.testInstrumentationRunnerArguments.$filter', - ], - ); - if (exitCode != 0) { - printError('$exampleName integration tests failed.'); - failed = true; - } - ranAnyTests = true; - } - } - - if (failed) { - return _PlatformResult(RunState.failed, - error: hasMissingBuild - ? 'Examples must be built before testing.' - : null); - } - if (!mode.integrationOnly && !ranUnitTests) { - printError('No unit tests ran. Plugins are required to have unit tests.'); - return _PlatformResult(RunState.failed, - error: 'No unit tests ran (use --exclude if this is intentional).'); - } - if (!ranAnyTests) { - return _PlatformResult(RunState.skipped); - } - return _PlatformResult(RunState.succeeded); - } - - Future<_PlatformResult> _testIOS(RepositoryPackage plugin, _TestMode mode) { - return _runXcodeTests(plugin, 'iOS', mode, - extraFlags: _iOSDestinationFlags); - } - - Future<_PlatformResult> _testMacOS(RepositoryPackage plugin, _TestMode mode) { - return _runXcodeTests(plugin, 'macOS', mode); - } - - /// Runs all applicable tests for [plugin], printing status and returning - /// the test result. - /// - /// The tests targets must be added to the Xcode project of the example app, - /// usually at "example/{ios,macos}/Runner.xcworkspace". - Future<_PlatformResult> _runXcodeTests( - RepositoryPackage plugin, - String platform, - _TestMode mode, { - List extraFlags = const [], - }) async { - String? testTarget; - const String unitTestTarget = 'RunnerTests'; - if (mode.unitOnly) { - testTarget = unitTestTarget; - } else if (mode.integrationOnly) { - testTarget = 'RunnerUITests'; - } - - bool ranUnitTests = false; - // Assume skipped until at least one test has run. - RunState overallResult = RunState.skipped; - for (final RepositoryPackage example in plugin.getExamples()) { - final String exampleName = example.displayName; - - // If running a specific target, check that. Otherwise, check if there - // are unit tests, since having no unit tests for a plugin is fatal - // (by repo policy) even if there are integration tests. - bool exampleHasUnitTests = false; - final String? targetToCheck = - testTarget ?? (mode.unit ? unitTestTarget : null); - final Directory xcodeProject = example.directory - .childDirectory(platform.toLowerCase()) - .childDirectory('Runner.xcodeproj'); - if (targetToCheck != null) { - final bool? hasTarget = - await _xcode.projectHasTarget(xcodeProject, targetToCheck); - if (hasTarget == null) { - printError('Unable to check targets for $exampleName.'); - overallResult = RunState.failed; - continue; - } else if (!hasTarget) { - print('No "$targetToCheck" target in $exampleName; skipping.'); - continue; - } else if (targetToCheck == unitTestTarget) { - exampleHasUnitTests = true; - } - } - - _printRunningExampleTestsMessage(example, platform); - final int exitCode = await _xcode.runXcodeBuild( - example.directory, - actions: ['test'], - workspace: '${platform.toLowerCase()}/Runner.xcworkspace', - scheme: 'Runner', - configuration: 'Debug', - extraFlags: [ - if (testTarget != null) '-only-testing:$testTarget', - ...extraFlags, - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - ); - - // The exit code from 'xcodebuild test' when there are no tests. - const int xcodebuildNoTestExitCode = 66; - switch (exitCode) { - case xcodebuildNoTestExitCode: - _printNoExampleTestsMessage(example, platform); - break; - case 0: - printSuccess('Successfully ran $platform xctest for $exampleName'); - // If this is the first test, assume success until something fails. - if (overallResult == RunState.skipped) { - overallResult = RunState.succeeded; - } - if (exampleHasUnitTests) { - ranUnitTests = true; - } - break; - default: - // Any failure means a failure overall. - overallResult = RunState.failed; - // If unit tests ran, note that even if they failed. - if (exampleHasUnitTests) { - ranUnitTests = true; - } - break; - } - } - - if (!mode.integrationOnly && !ranUnitTests) { - printError('No unit tests ran. Plugins are required to have unit tests.'); - // Only return a specific summary error message about the missing unit - // tests if there weren't also failures, to avoid having a misleadingly - // specific message. - if (overallResult != RunState.failed) { - return _PlatformResult(RunState.failed, - error: 'No unit tests ran (use --exclude if this is intentional).'); - } - } - - return _PlatformResult(overallResult); - } - - Future<_PlatformResult> _testWindows( - RepositoryPackage plugin, _TestMode mode) async { - if (mode.integrationOnly) { - return _PlatformResult(RunState.skipped); - } - - bool isTestBinary(File file) { - return file.basename.endsWith('_test.exe') || - file.basename.endsWith('_tests.exe'); - } - - return _runGoogleTestTests(plugin, 'Windows', 'Debug', - isTestBinary: isTestBinary); - } - - Future<_PlatformResult> _testLinux( - RepositoryPackage plugin, _TestMode mode) async { - if (mode.integrationOnly) { - return _PlatformResult(RunState.skipped); - } - - bool isTestBinary(File file) { - return file.basename.endsWith('_test') || - file.basename.endsWith('_tests'); - } - - // Since Linux uses a single-config generator, building-examples only - // generates the build files for release, so the tests have to be run in - // release mode as well. - // - // TODO(stuartmorgan): Consider adding a command to `flutter` that would - // generate build files without doing a build, and using that instead of - // relying on running build-examples. See - // https://github.com/flutter/flutter/issues/93407. - return _runGoogleTestTests(plugin, 'Linux', 'Release', - isTestBinary: isTestBinary); - } - - /// Finds every file in the [buildDirectoryName] subdirectory of [plugin]'s - /// build directory for which [isTestBinary] is true, and runs all of them, - /// returning the overall result. - /// - /// The binaries are assumed to be Google Test test binaries, thus returning - /// zero for success and non-zero for failure. - Future<_PlatformResult> _runGoogleTestTests( - RepositoryPackage plugin, - String platformName, - String buildMode, { - required bool Function(File) isTestBinary, - }) async { - final List testBinaries = []; - bool hasMissingBuild = false; - bool buildFailed = false; - for (final RepositoryPackage example in plugin.getExamples()) { - final CMakeProject project = CMakeProject(example.directory, - buildMode: buildMode, - processRunner: processRunner, - platform: platform); - if (!project.isConfigured()) { - printError('ERROR: Run "flutter build" on ${example.displayName}, ' - 'or run this tool\'s "build-examples" command, for the target ' - 'platform before executing tests.'); - hasMissingBuild = true; - continue; - } - - // By repository convention, example projects create an aggregate target - // called 'unit_tests' that builds all unit tests (usually just an alias - // for a specific test target). - final int exitCode = await project.runBuild('unit_tests'); - if (exitCode != 0) { - printError('${example.displayName} unit tests failed to build.'); - buildFailed = true; - } - - testBinaries.addAll(project.buildDirectory - .listSync(recursive: true) - .whereType() - .where(isTestBinary) - .where((File file) { - // Only run the `buildMode` build of the unit tests, to avoid running - // the same tests multiple times. - final List components = path.split(file.path); - return components.contains(buildMode) || - components.contains(buildMode.toLowerCase()); - })); - } - - if (hasMissingBuild) { - return _PlatformResult(RunState.failed, - error: 'Examples must be built before testing.'); - } - - if (buildFailed) { - return _PlatformResult(RunState.failed, - error: 'Failed to build $platformName unit tests.'); - } - - if (testBinaries.isEmpty) { - final String binaryExtension = platform.isWindows ? '.exe' : ''; - printError( - 'No test binaries found. At least one *_test(s)$binaryExtension ' - 'binary should be built by the example(s)'); - return _PlatformResult(RunState.failed, - error: 'No $platformName unit tests found'); - } - - bool passing = true; - for (final File test in testBinaries) { - print('Running ${test.basename}...'); - final int exitCode = - await processRunner.runAndStream(test.path, []); - passing &= exitCode == 0; - } - return _PlatformResult(passing ? RunState.succeeded : RunState.failed); - } - - /// Prints a standard format message indicating that [platform] tests for - /// [plugin]'s [example] are about to be run. - void _printRunningExampleTestsMessage( - RepositoryPackage example, String platform) { - print('Running $platform tests for ${example.displayName}...'); - } - - /// Prints a standard format message indicating that no tests were found for - /// [plugin]'s [example] for [platform]. - void _printNoExampleTestsMessage(RepositoryPackage example, String platform) { - print('No $platform tests found for ${example.displayName}'); - } -} - -// The type for a function that takes a plugin directory and runs its native -// tests for a specific platform. -typedef _TestFunction = Future<_PlatformResult> Function( - RepositoryPackage, _TestMode); - -/// A collection of information related to a specific platform. -class _PlatformDetails { - const _PlatformDetails( - this.label, - this.testFunction, - ); - - /// The name to use in output. - final String label; - - /// The function to call to run tests. - final _TestFunction testFunction; -} - -/// Enabled state for different test types. -class _TestMode { - const _TestMode({required this.unit, required this.integration}); - - final bool unit; - final bool integration; - - bool get integrationOnly => integration && !unit; - bool get unitOnly => unit && !integration; -} - -/// The result of running a single platform's tests. -class _PlatformResult { - _PlatformResult(this.state, {this.error}); - - /// The overall state of the platform's tests. This should be: - /// - failed if any tests failed. - /// - succeeded if at least one test ran, and all tests passed. - /// - skipped if no tests ran. - final RunState state; - - /// An optional error string to include in the summary for this platform. - /// - /// Ignored unless [state] is `failed`. - final String? error; -} diff --git a/script/tool/lib/src/podspec_check_command.dart b/script/tool/lib/src/podspec_check_command.dart deleted file mode 100644 index 4cda7210a8ef..000000000000 --- a/script/tool/lib/src/podspec_check_command.dart +++ /dev/null @@ -1,194 +0,0 @@ -// 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 'dart:convert'; -import 'dart:io'; - -import 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -const int _exitUnsupportedPlatform = 2; -const int _exitPodNotInstalled = 3; - -/// Lint the CocoaPod podspecs and run unit tests. -/// -/// See https://guides.cocoapods.org/terminal/commands.html#pod_lib_lint. -class PodspecCheckCommand extends PackageLoopingCommand { - /// Creates an instance of the linter command. - PodspecCheckCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : super(packagesDir, processRunner: processRunner, platform: platform); - - @override - final String name = 'podspec-check'; - - @override - List get aliases => ['podspec', 'podspecs']; - - @override - final String description = - 'Runs "pod lib lint" on all iOS and macOS plugin podspecs, as well as ' - 'making sure the podspecs follow repository standards.\n\n' - 'This command requires "pod" and "flutter" to be in your path. Runs on macOS only.'; - - @override - Future initializeRun() async { - if (!platform.isMacOS) { - printError('This command is only supported on macOS'); - throw ToolExit(_exitUnsupportedPlatform); - } - - final ProcessResult result = await processRunner.run( - 'which', - ['pod'], - workingDir: packagesDir, - logOnError: true, - ); - if (result.exitCode != 0) { - printError('Unable to find "pod". Make sure it is in your path.'); - throw ToolExit(_exitPodNotInstalled); - } - } - - @override - Future runForPackage(RepositoryPackage package) async { - final List errors = []; - - final List podspecs = await _podspecsToLint(package); - if (podspecs.isEmpty) { - return PackageResult.skip('No podspecs.'); - } - - for (final File podspec in podspecs) { - if (!await _lintPodspec(podspec)) { - errors.add(podspec.basename); - } - } - - if (await _hasIOSSwiftCode(package)) { - print('iOS Swift code found, checking for search paths settings...'); - for (final File podspec in podspecs) { - if (_isPodspecMissingSearchPaths(podspec)) { - const String workaroundBlock = r''' - s.xcconfig = { - 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', - 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', - } -'''; - final String path = - getRelativePosixPath(podspec, from: package.directory); - printError('$path is missing seach path configuration. Any iOS ' - 'plugin implementation that contains Swift implementation code ' - 'needs to contain the following:\n\n' - '$workaroundBlock\n' - 'For more details, see https://github.com/flutter/flutter/issues/118418.'); - errors.add(podspec.basename); - } - } - } - - return errors.isEmpty - ? PackageResult.success() - : PackageResult.fail(errors); - } - - Future> _podspecsToLint(RepositoryPackage package) async { - final List podspecs = - await getFilesForPackage(package).where((File entity) { - final String filePath = entity.path; - return path.extension(filePath) == '.podspec'; - }).toList(); - - podspecs.sort((File a, File b) => a.basename.compareTo(b.basename)); - return podspecs; - } - - Future _lintPodspec(File podspec) async { - // Do not run the static analyzer on plugins with known analyzer issues. - final String podspecPath = podspec.path; - - final String podspecBasename = podspec.basename; - print('Linting $podspecBasename'); - - // Lint plugin as framework (use_frameworks!). - final ProcessResult frameworkResult = - await _runPodLint(podspecPath, libraryLint: true); - print(frameworkResult.stdout); - print(frameworkResult.stderr); - - // Lint plugin as library. - final ProcessResult libraryResult = - await _runPodLint(podspecPath, libraryLint: false); - print(libraryResult.stdout); - print(libraryResult.stderr); - - return frameworkResult.exitCode == 0 && libraryResult.exitCode == 0; - } - - Future _runPodLint(String podspecPath, - {required bool libraryLint}) async { - final List arguments = [ - 'lib', - 'lint', - podspecPath, - '--configuration=Debug', // Release targets unsupported arm64 simulators. Use Debug to only build against targeted x86_64 simulator devices. - '--skip-tests', - '--use-modular-headers', // Flutter sets use_modular_headers! in its templates. - if (libraryLint) '--use-libraries' - ]; - - print('Running "pod ${arguments.join(' ')}"'); - return processRunner.run('pod', arguments, - workingDir: packagesDir, stdoutEncoding: utf8, stderrEncoding: utf8); - } - - /// Returns true if there is any iOS plugin implementation code written in - /// Swift. - Future _hasIOSSwiftCode(RepositoryPackage package) async { - return getFilesForPackage(package).any((File entity) { - final String relativePath = - getRelativePosixPath(entity, from: package.directory); - // Ignore example code. - if (relativePath.startsWith('example/')) { - return false; - } - final String filePath = entity.path; - return path.extension(filePath) == '.swift'; - }); - } - - /// Returns true if [podspec] could apply to iOS, but does not have the - /// workaround for search paths that makes Swift plugins build correctly in - /// Objective-C applications. See - /// https://github.com/flutter/flutter/issues/118418 for context and details. - /// - /// This does not check that the plugin has Swift code, and thus whether the - /// workaround is needed, only whether or not it is there. - bool _isPodspecMissingSearchPaths(File podspec) { - final String directory = podspec.parent.basename; - // All macOS Flutter apps are Swift, so macOS-only podspecs don't need the - // workaround. If it's anywhere other than macos/, err or the side of - // assuming it's required. - if (directory == 'macos') { - return false; - } - - // This errs on the side of being too strict, to minimize the chance of - // accidental incorrect configuration. If we ever need more flexibility - // due to a false negative we can adjust this as necessary. - final RegExp workaround = RegExp(r''' -\s*s\.(?:ios\.)?xcconfig = {[^}]* -\s*'LIBRARY_SEARCH_PATHS' => '\$\(TOOLCHAIN_DIR\)/usr/lib/swift/\$\(PLATFORM_NAME\)/ \$\(SDKROOT\)/usr/lib/swift', -\s*'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift',[^}]* -\s*}''', dotAll: true); - return !workaround.hasMatch(podspec.readAsStringSync()); - } -} diff --git a/script/tool/lib/src/publish_check_command.dart b/script/tool/lib/src/publish_check_command.dart deleted file mode 100644 index 14b240dc04c2..000000000000 --- a/script/tool/lib/src/publish_check_command.dart +++ /dev/null @@ -1,289 +0,0 @@ -// 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 'dart:async'; -import 'dart:convert'; -import 'dart:io' as io; - -import 'package:file/file.dart'; -import 'package:http/http.dart' as http; -import 'package:platform/platform.dart'; -import 'package:pub_semver/pub_semver.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/process_runner.dart'; -import 'common/pub_version_finder.dart'; -import 'common/repository_package.dart'; - -/// A command to check that packages are publishable via 'dart publish'. -class PublishCheckCommand extends PackageLoopingCommand { - /// Creates an instance of the publish command. - PublishCheckCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - http.Client? httpClient, - }) : _pubVersionFinder = - PubVersionFinder(httpClient: httpClient ?? http.Client()), - super(packagesDir, processRunner: processRunner, platform: platform) { - argParser.addFlag( - _allowPrereleaseFlag, - help: 'Allows the pre-release SDK warning to pass.\n' - 'When enabled, a pub warning, which asks to publish the package as a pre-release version when ' - 'the SDK constraint is a pre-release version, is ignored.', - ); - argParser.addFlag(_machineFlag, - help: 'Switch outputs to a machine readable JSON. \n' - 'The JSON contains a "status" field indicating the final status of the command, the possible values are:\n' - ' $_statusNeedsPublish: There is at least one package need to be published. They also passed all publish checks.\n' - ' $_statusMessageNoPublish: There are no packages needs to be published. Either no pubspec change detected or all versions have already been published.\n' - ' $_statusMessageError: Some error has occurred.'); - } - - static const String _allowPrereleaseFlag = 'allow-pre-release'; - static const String _machineFlag = 'machine'; - static const String _statusNeedsPublish = 'needs-publish'; - static const String _statusMessageNoPublish = 'no-publish'; - static const String _statusMessageError = 'error'; - static const String _statusKey = 'status'; - static const String _humanMessageKey = 'humanMessage'; - - @override - final String name = 'publish-check'; - - @override - final String description = - 'Checks to make sure that a package *could* be published.'; - - final PubVersionFinder _pubVersionFinder; - - /// The overall result of the run for machine-readable output. This is the - /// highest value that occurs during the run. - _PublishCheckResult _overallResult = _PublishCheckResult.nothingToPublish; - - @override - bool get captureOutput => getBoolArg(_machineFlag); - - @override - Future initializeRun() async { - _overallResult = _PublishCheckResult.nothingToPublish; - } - - @override - Future runForPackage(RepositoryPackage package) async { - _PublishCheckResult? result = await _passesPublishCheck(package); - if (result == null) { - return PackageResult.skip('Package is marked as unpublishable.'); - } - if (!_passesAuthorsCheck(package)) { - _printImportantStatusMessage( - 'No AUTHORS file found. Packages must include an AUTHORS file.', - isError: true); - result = _PublishCheckResult.error; - } - - if (result.index > _overallResult.index) { - _overallResult = result; - } - return result == _PublishCheckResult.error - ? PackageResult.fail() - : PackageResult.success(); - } - - @override - Future completeRun() async { - _pubVersionFinder.httpClient.close(); - } - - @override - Future handleCapturedOutput(List output) async { - final Map machineOutput = { - _statusKey: _statusStringForResult(_overallResult), - _humanMessageKey: output, - }; - - print(const JsonEncoder.withIndent(' ').convert(machineOutput)); - } - - String _statusStringForResult(_PublishCheckResult result) { - switch (result) { - case _PublishCheckResult.nothingToPublish: - return _statusMessageNoPublish; - case _PublishCheckResult.needsPublishing: - return _statusNeedsPublish; - case _PublishCheckResult.error: - return _statusMessageError; - } - } - - Pubspec? _tryParsePubspec(RepositoryPackage package) { - try { - return package.parsePubspec(); - } on Exception catch (exception) { - print( - 'Failed to parse `pubspec.yaml` at ${package.pubspecFile.path}: ' - '$exception', - ); - return null; - } - } - - // Run `dart pub get` on the examples of [package]. - Future _fetchExampleDeps(RepositoryPackage package) async { - for (final RepositoryPackage example in package.getExamples()) { - await processRunner.runAndStream( - 'dart', - ['pub', 'get'], - workingDir: example.directory, - ); - } - } - - Future _hasValidPublishCheckRun(RepositoryPackage package) async { - // `pub publish` does not do `dart pub get` inside `example` directories - // of a package (but they're part of the analysis output!). - // Issue: https://github.com/flutter/flutter/issues/113788 - await _fetchExampleDeps(package); - - print('Running pub publish --dry-run:'); - final io.Process process = await processRunner.start( - flutterCommand, - ['pub', 'publish', '--', '--dry-run'], - workingDirectory: package.directory, - ); - - final StringBuffer outputBuffer = StringBuffer(); - - final Completer stdOutCompleter = Completer(); - process.stdout.listen( - (List event) { - final String output = String.fromCharCodes(event); - if (output.isNotEmpty) { - print(output); - outputBuffer.write(output); - } - }, - onDone: () => stdOutCompleter.complete(), - ); - - final Completer stdInCompleter = Completer(); - process.stderr.listen( - (List event) { - final String output = String.fromCharCodes(event); - if (output.isNotEmpty) { - // The final result is always printed on stderr, whether success or - // failure. - final bool isError = !output.contains('has 0 warnings'); - _printImportantStatusMessage(output, isError: isError); - outputBuffer.write(output); - } - }, - onDone: () => stdInCompleter.complete(), - ); - - if (await process.exitCode == 0) { - return true; - } - - if (!getBoolArg(_allowPrereleaseFlag)) { - return false; - } - - await stdOutCompleter.future; - await stdInCompleter.future; - - final String output = outputBuffer.toString(); - return output.contains('Package has 1 warning') && - output.contains( - 'Packages with an SDK constraint on a pre-release of the Dart SDK should themselves be published as a pre-release version.'); - } - - /// Returns the result of the publish check, or null if the package is marked - /// as unpublishable. - Future<_PublishCheckResult?> _passesPublishCheck( - RepositoryPackage package) async { - final String packageName = package.directory.basename; - final Pubspec? pubspec = _tryParsePubspec(package); - if (pubspec == null) { - print('No valid pubspec found.'); - return _PublishCheckResult.error; - } else if (pubspec.publishTo == 'none') { - return null; - } - - final Version? version = pubspec.version; - final _PublishCheckResult alreadyPublishedResult = - await _checkPublishingStatus( - packageName: packageName, version: version); - if (alreadyPublishedResult == _PublishCheckResult.nothingToPublish) { - print( - 'Package $packageName version: $version has already be published on pub.'); - return alreadyPublishedResult; - } else if (alreadyPublishedResult == _PublishCheckResult.error) { - print('Check pub version failed $packageName'); - return _PublishCheckResult.error; - } - - if (await _hasValidPublishCheckRun(package)) { - print('Package $packageName is able to be published.'); - return _PublishCheckResult.needsPublishing; - } else { - print('Unable to publish $packageName'); - return _PublishCheckResult.error; - } - } - - // Check if `packageName` already has `version` published on pub. - Future<_PublishCheckResult> _checkPublishingStatus( - {required String packageName, required Version? version}) async { - final PubVersionFinderResponse pubVersionFinderResponse = - await _pubVersionFinder.getPackageVersion(packageName: packageName); - switch (pubVersionFinderResponse.result) { - case PubVersionFinderResult.success: - return pubVersionFinderResponse.versions.contains(version) - ? _PublishCheckResult.nothingToPublish - : _PublishCheckResult.needsPublishing; - case PubVersionFinderResult.fail: - print(''' -Error fetching version on pub for $packageName. -HTTP Status ${pubVersionFinderResponse.httpResponse.statusCode} -HTTP response: ${pubVersionFinderResponse.httpResponse.body} -'''); - return _PublishCheckResult.error; - case PubVersionFinderResult.noPackageFound: - return _PublishCheckResult.needsPublishing; - } - } - - bool _passesAuthorsCheck(RepositoryPackage package) { - final List pathComponents = - package.directory.fileSystem.path.split(package.path); - if (pathComponents.contains('third_party')) { - // Third-party packages aren't required to have an AUTHORS file. - return true; - } - return package.authorsFile.existsSync(); - } - - void _printImportantStatusMessage(String message, {required bool isError}) { - final String statusMessage = '${isError ? 'ERROR' : 'SUCCESS'}: $message'; - if (getBoolArg(_machineFlag)) { - print(statusMessage); - } else { - if (isError) { - printError(statusMessage); - } else { - printSuccess(statusMessage); - } - } - } -} - -/// Possible outcomes of of a publishing check. -enum _PublishCheckResult { - nothingToPublish, - needsPublishing, - error, -} diff --git a/script/tool/lib/src/publish_command.dart b/script/tool/lib/src/publish_command.dart deleted file mode 100644 index e7b3d110c5fa..000000000000 --- a/script/tool/lib/src/publish_command.dart +++ /dev/null @@ -1,456 +0,0 @@ -// 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 'dart:async'; -import 'dart:convert'; -import 'dart:io' as io; - -import 'package:file/file.dart'; -import 'package:git/git.dart'; -import 'package:http/http.dart' as http; -import 'package:meta/meta.dart'; -import 'package:path/path.dart' as p; -import 'package:platform/platform.dart'; -import 'package:pub_semver/pub_semver.dart'; -import 'package:yaml/yaml.dart'; - -import 'common/core.dart'; -import 'common/file_utils.dart'; -import 'common/git_version_finder.dart'; -import 'common/package_command.dart'; -import 'common/package_looping_command.dart'; -import 'common/process_runner.dart'; -import 'common/pub_version_finder.dart'; -import 'common/repository_package.dart'; - -@immutable -class _RemoteInfo { - const _RemoteInfo({required this.name, required this.url}); - - /// The git name for the remote. - final String name; - - /// The remote's URL. - final String url; -} - -/// Wraps pub publish with a few niceties used by the flutter/plugin team. -/// -/// 1. Checks for any modified files in git and refuses to publish if there's an -/// issue. -/// 2. Tags the release with the format -v. -/// 3. Pushes the release to a remote. -/// -/// Both 2 and 3 are optional, see `plugin_tools help publish` for full -/// usage information. -/// -/// [processRunner], [print], and [stdin] can be overriden for easier testing. -class PublishCommand extends PackageLoopingCommand { - /// Creates an instance of the publish command. - PublishCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - io.Stdin? stdinput, - GitDir? gitDir, - http.Client? httpClient, - }) : _pubVersionFinder = - PubVersionFinder(httpClient: httpClient ?? http.Client()), - _stdin = stdinput ?? io.stdin, - super(packagesDir, - platform: platform, processRunner: processRunner, gitDir: gitDir) { - argParser.addMultiOption(_pubFlagsOption, - help: - 'A list of options that will be forwarded on to pub. Separate multiple flags with commas.'); - argParser.addOption( - _remoteOption, - help: 'The name of the remote to push the tags to.', - // Flutter convention is to use "upstream" for the single source of truth, and "origin" for personal forks. - defaultsTo: 'upstream', - ); - argParser.addFlag( - _allChangedFlag, - help: - 'Release all packages that contains pubspec changes at the current commit compares to the base-sha.\n' - 'The --packages option is ignored if this is on.', - ); - argParser.addFlag( - _dryRunFlag, - help: - 'Skips the real `pub publish` and `git tag` commands and assumes both commands are successful.\n' - 'This does not run `pub publish --dry-run`.\n' - 'If you want to run the command with `pub publish --dry-run`, use `pub-publish-flags=--dry-run`', - ); - argParser.addFlag(_skipConfirmationFlag, - help: 'Run the command without asking for Y/N inputs.\n' - 'This command will add a `--force` flag to the `pub publish` command if it is not added with $_pubFlagsOption\n'); - } - - static const String _pubFlagsOption = 'pub-publish-flags'; - static const String _remoteOption = 'remote'; - static const String _allChangedFlag = 'all-changed'; - static const String _dryRunFlag = 'dry-run'; - static const String _skipConfirmationFlag = 'skip-confirmation'; - - static const String _pubCredentialName = 'PUB_CREDENTIALS'; - - // Version tags should follow -v. For example, - // `flutter_plugin_tools-v0.0.24`. - static const String _tagFormat = '%PACKAGE%-v%VERSION%'; - - @override - final String name = 'publish'; - - @override - final String description = - 'Attempts to publish the given packages and tag the release(s) on GitHub.\n' - 'If running this on CI, an environment variable named $_pubCredentialName must be set to a String that represents the pub credential JSON.\n' - 'WARNING: Do not check in the content of pub credential JSON, it should only come from secure sources.'; - - final io.Stdin _stdin; - StreamSubscription? _stdinSubscription; - final PubVersionFinder _pubVersionFinder; - - // Tags that already exist in the repository. - List _existingGitTags = []; - // The remote to push tags to. - late _RemoteInfo _remote; - // Flags to pass to `pub publish`. - late List _publishFlags; - - @override - String get successSummaryMessage => 'published'; - - @override - String get failureListHeader => - 'The following packages had failures during publishing:'; - - @override - Future initializeRun() async { - print('Checking local repo...'); - - // Ensure that the requested remote is present. - final String remoteName = getStringArg(_remoteOption); - final String? remoteUrl = await _verifyRemote(remoteName); - if (remoteUrl == null) { - printError('Unable to find URL for remote $remoteName; cannot push tags'); - throw ToolExit(1); - } - _remote = _RemoteInfo(name: remoteName, url: remoteUrl); - - // Pre-fetch all the repository's tags, to check against when publishing. - final GitDir repository = await gitDir; - final io.ProcessResult existingTagsResult = - await repository.runCommand(['tag', '--sort=-committerdate']); - _existingGitTags = (existingTagsResult.stdout as String).split('\n') - ..removeWhere((String element) => element.isEmpty); - - _publishFlags = [ - ...getStringListArg(_pubFlagsOption), - if (getBoolArg(_skipConfirmationFlag)) '--force', - ]; - - if (getBoolArg(_dryRunFlag)) { - print('=============== DRY RUN ==============='); - } - } - - @override - Stream getPackagesToProcess() async* { - if (getBoolArg(_allChangedFlag)) { - final GitVersionFinder gitVersionFinder = await retrieveVersionFinder(); - final String baseSha = await gitVersionFinder.getBaseSha(); - print( - 'Publishing all packages that have changed relative to "$baseSha"\n'); - final List changedPubspecs = - await gitVersionFinder.getChangedPubSpecs(); - - for (final String pubspecPath in changedPubspecs) { - // git outputs a relativa, Posix-style path. - final File pubspecFile = childFileWithSubcomponents( - packagesDir.fileSystem.directory((await gitDir).path), - p.posix.split(pubspecPath)); - yield PackageEnumerationEntry(RepositoryPackage(pubspecFile.parent), - excluded: false); - } - } else { - yield* getTargetPackages(filterExcluded: false); - } - } - - @override - Future runForPackage(RepositoryPackage package) async { - final PackageResult? checkResult = await _checkNeedsRelease(package); - if (checkResult != null) { - return checkResult; - } - - if (!await _checkGitStatus(package)) { - return PackageResult.fail(['uncommitted changes']); - } - - if (!await _publish(package)) { - return PackageResult.fail(['publish failed']); - } - - if (!await _tagRelease(package)) { - return PackageResult.fail(['tagging failed']); - } - - print('\nPublished ${package.directory.basename} successfully!'); - return PackageResult.success(); - } - - @override - Future completeRun() async { - _pubVersionFinder.httpClient.close(); - await _stdinSubscription?.cancel(); - _stdinSubscription = null; - } - - /// Checks whether [package] needs to be released, printing check status and - /// returning one of: - /// - PackageResult.fail if the check could not be completed - /// - PackageResult.skip if no release is necessary - /// - null if releasing should proceed - /// - /// In cases where a non-null result is returned, that should be returned - /// as the final result for the package, without further processing. - Future _checkNeedsRelease(RepositoryPackage package) async { - if (!package.pubspecFile.existsSync()) { - logWarning(''' -The pubspec file for ${package.displayName} does not exist, so no publishing will happen. -Safe to ignore if the package is deleted in this commit. -'''); - return PackageResult.skip('package deleted'); - } - - final Pubspec pubspec = package.parsePubspec(); - - if (pubspec.name == 'flutter_plugin_tools') { - // Ignore flutter_plugin_tools package when running publishing through flutter_plugin_tools. - // TODO(cyanglaz): Make the tool also auto publish flutter_plugin_tools package. - // https://github.com/flutter/flutter/issues/85430 - return PackageResult.skip( - 'publishing flutter_plugin_tools via the tool is not supported'); - } - - if (pubspec.publishTo == 'none') { - return PackageResult.skip('publish_to: none'); - } - - if (pubspec.version == null) { - printError( - 'No version found. A package that intentionally has no version should be marked "publish_to: none"'); - return PackageResult.fail(['no version']); - } - - // Check if the package named `packageName` with `version` has already - // been published. - final Version version = pubspec.version!; - final PubVersionFinderResponse pubVersionFinderResponse = - await _pubVersionFinder.getPackageVersion(packageName: pubspec.name); - if (pubVersionFinderResponse.versions.contains(version)) { - final String tagsForPackageWithSameVersion = _existingGitTags.firstWhere( - (String tag) => - tag.split('-v').first == pubspec.name && - tag.split('-v').last == version.toString(), - orElse: () => ''); - if (tagsForPackageWithSameVersion.isEmpty) { - printError( - '${pubspec.name} $version has already been published, however ' - 'the git release tag (${pubspec.name}-v$version) was not found. ' - 'Please manually fix the tag then run the command again.'); - return PackageResult.fail(['published but untagged']); - } else { - print('${pubspec.name} $version has already been published.'); - return PackageResult.skip('already published'); - } - } - return null; - } - - // Tag the release with -v, and push it to the remote. - // - // Return `true` if successful, `false` otherwise. - Future _tagRelease(RepositoryPackage package) async { - final String tag = _getTag(package); - print('Tagging release $tag...'); - if (!getBoolArg(_dryRunFlag)) { - final io.ProcessResult result = await (await gitDir).runCommand( - ['tag', tag], - throwOnError: false, - ); - if (result.exitCode != 0) { - return false; - } - } - - print('Pushing tag to ${_remote.name}...'); - final bool success = await _pushTagToRemote( - tag: tag, - remote: _remote, - ); - if (success) { - print('Release tagged!'); - } - return success; - } - - Future _checkGitStatus(RepositoryPackage package) async { - final io.ProcessResult statusResult = await (await gitDir).runCommand( - [ - 'status', - '--porcelain', - '--ignored', - package.directory.absolute.path - ], - throwOnError: false, - ); - if (statusResult.exitCode != 0) { - return false; - } - - final String statusOutput = statusResult.stdout as String; - if (statusOutput.isNotEmpty) { - printError( - "There are files in the package directory that haven't been saved in git. Refusing to publish these files:\n\n" - '$statusOutput\n' - 'If the directory should be clean, you can run `git clean -xdf && git reset --hard HEAD` to wipe all local changes.'); - } - return statusOutput.isEmpty; - } - - Future _verifyRemote(String remote) async { - final io.ProcessResult getRemoteUrlResult = await (await gitDir).runCommand( - ['remote', 'get-url', remote], - throwOnError: false, - ); - if (getRemoteUrlResult.exitCode != 0) { - return null; - } - return getRemoteUrlResult.stdout as String?; - } - - Future _publish(RepositoryPackage package) async { - print('Publishing...'); - print('Running `pub publish ${_publishFlags.join(' ')}` in ' - '${package.directory.absolute.path}...\n'); - if (getBoolArg(_dryRunFlag)) { - return true; - } - - if (_publishFlags.contains('--force')) { - _ensureValidPubCredential(); - } - - final io.Process publish = await processRunner.start( - flutterCommand, ['pub', 'publish', ..._publishFlags], - workingDirectory: package.directory); - publish.stdout.transform(utf8.decoder).listen((String data) => print(data)); - publish.stderr.transform(utf8.decoder).listen((String data) => print(data)); - _stdinSubscription ??= _stdin - .transform(utf8.decoder) - .listen((String data) => publish.stdin.writeln(data)); - final int result = await publish.exitCode; - if (result != 0) { - printError('Publishing ${package.directory.basename} failed.'); - return false; - } - - print('Package published!'); - return true; - } - - String _getTag(RepositoryPackage package) { - final File pubspecFile = package.pubspecFile; - final YamlMap pubspecYaml = - loadYaml(pubspecFile.readAsStringSync()) as YamlMap; - final String name = pubspecYaml['name'] as String; - final String version = pubspecYaml['version'] as String; - // We should have failed to publish if these were unset. - assert(name.isNotEmpty && version.isNotEmpty); - return _tagFormat - .replaceAll('%PACKAGE%', name) - .replaceAll('%VERSION%', version); - } - - // Pushes the `tag` to `remote` - // - // Return `true` if successful, `false` otherwise. - Future _pushTagToRemote({ - required String tag, - required _RemoteInfo remote, - }) async { - assert(remote != null && tag != null); - if (!getBoolArg(_dryRunFlag)) { - final io.ProcessResult result = await (await gitDir).runCommand( - ['push', remote.name, tag], - throwOnError: false, - ); - if (result.exitCode != 0) { - return false; - } - } - return true; - } - - void _ensureValidPubCredential() { - final String credentialsPath = _credentialsPath; - final File credentialFile = packagesDir.fileSystem.file(credentialsPath); - if (credentialFile.existsSync() && - credentialFile.readAsStringSync().isNotEmpty) { - return; - } - final String? credential = io.Platform.environment[_pubCredentialName]; - if (credential == null) { - printError(''' -No pub credential available. Please check if `$credentialsPath` is valid. -If running this command on CI, you can set the pub credential content in the $_pubCredentialName environment variable. -'''); - throw ToolExit(1); - } - credentialFile.openSync(mode: FileMode.writeOnlyAppend) - ..writeStringSync(credential) - ..closeSync(); - } - - /// Returns the correct path where the pub credential is stored. - @visibleForTesting - static String getCredentialPath() { - return _credentialsPath; - } -} - -/// The path in which pub expects to find its credentials file. -final String _credentialsPath = () { - // This follows the same logic as pub: - // https://github.com/dart-lang/pub/blob/d99b0d58f4059d7bb4ac4616fd3d54ec00a2b5d4/lib/src/system_cache.dart#L34-L43 - String? cacheDir; - final String? pubCache = io.Platform.environment['PUB_CACHE']; - if (pubCache != null) { - cacheDir = pubCache; - } else if (io.Platform.isWindows) { - final String? appData = io.Platform.environment['APPDATA']; - if (appData == null) { - printError('"APPDATA" environment variable is not set.'); - } else { - cacheDir = p.join(appData, 'Pub', 'Cache'); - } - } else { - final String? home = io.Platform.environment['HOME']; - if (home == null) { - printError('"HOME" environment variable is not set.'); - } else { - cacheDir = p.join(home, '.pub-cache'); - } - } - - if (cacheDir == null) { - printError('Unable to determine pub cache location'); - throw ToolExit(1); - } - - return p.join(cacheDir, 'credentials.json'); -}(); diff --git a/script/tool/lib/src/pubspec_check_command.dart b/script/tool/lib/src/pubspec_check_command.dart deleted file mode 100644 index aefa316a41f6..000000000000 --- a/script/tool/lib/src/pubspec_check_command.dart +++ /dev/null @@ -1,397 +0,0 @@ -// 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 'package:file/file.dart'; -import 'package:git/git.dart'; -import 'package:platform/platform.dart'; -import 'package:pub_semver/pub_semver.dart'; -import 'package:yaml/yaml.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -/// A command to enforce pubspec conventions across the repository. -/// -/// This both ensures that repo best practices for which optional fields are -/// used are followed, and that the structure is consistent to make edits -/// across multiple pubspec files easier. -class PubspecCheckCommand extends PackageLoopingCommand { - /// Creates an instance of the version check command. - PubspecCheckCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - GitDir? gitDir, - }) : super( - packagesDir, - processRunner: processRunner, - platform: platform, - gitDir: gitDir, - ) { - argParser.addOption( - _minMinDartVersionFlag, - help: - 'The minimum Dart version to allow as the minimum SDK constraint.\n\n' - 'This is only enforced for non-Flutter packages; Flutter packages ' - 'use --$_minMinFlutterVersionFlag', - ); - argParser.addOption( - _minMinFlutterVersionFlag, - help: - 'The minimum Flutter version to allow as the minimum SDK constraint.', - ); - } - - static const String _minMinDartVersionFlag = 'min-min-dart-version'; - static const String _minMinFlutterVersionFlag = 'min-min-flutter-version'; - - // Section order for plugins. Because the 'flutter' section is critical - // information for plugins, and usually small, it goes near the top unlike in - // a normal app or package. - static const List _majorPluginSections = [ - 'environment:', - 'flutter:', - 'dependencies:', - 'dev_dependencies:', - 'false_secrets:', - ]; - - static const List _majorPackageSections = [ - 'environment:', - 'dependencies:', - 'dev_dependencies:', - 'flutter:', - 'false_secrets:', - ]; - - static const String _expectedIssueLinkFormat = - 'https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A'; - - @override - final String name = 'pubspec-check'; - - @override - final String description = - 'Checks that pubspecs follow repository conventions.'; - - @override - bool get hasLongOutput => false; - - @override - PackageLoopingType get packageLoopingType => - PackageLoopingType.includeAllSubpackages; - - @override - Future runForPackage(RepositoryPackage package) async { - final File pubspec = package.pubspecFile; - final bool passesCheck = - !pubspec.existsSync() || await _checkPubspec(pubspec, package: package); - if (!passesCheck) { - return PackageResult.fail(); - } - return PackageResult.success(); - } - - Future _checkPubspec( - File pubspecFile, { - required RepositoryPackage package, - }) async { - final String contents = pubspecFile.readAsStringSync(); - final Pubspec? pubspec = _tryParsePubspec(contents); - if (pubspec == null) { - return false; - } - - final List pubspecLines = contents.split('\n'); - final bool isPlugin = pubspec.flutter?.containsKey('plugin') ?? false; - final List sectionOrder = - isPlugin ? _majorPluginSections : _majorPackageSections; - bool passing = _checkSectionOrder(pubspecLines, sectionOrder); - if (!passing) { - printError('${indentation}Major sections should follow standard ' - 'repository ordering:'); - final String listIndentation = indentation * 2; - printError('$listIndentation${sectionOrder.join('\n$listIndentation')}'); - } - - final String minMinDartVersionString = getStringArg(_minMinDartVersionFlag); - final String minMinFlutterVersionString = - getStringArg(_minMinFlutterVersionFlag); - final String? minVersionError = _checkForMinimumVersionError( - pubspec, - package, - minMinDartVersion: minMinDartVersionString.isEmpty - ? null - : Version.parse(minMinDartVersionString), - minMinFlutterVersion: minMinFlutterVersionString.isEmpty - ? null - : Version.parse(minMinFlutterVersionString), - ); - if (minVersionError != null) { - printError('$indentation$minVersionError'); - passing = false; - } - - if (isPlugin) { - final String? implementsError = - _checkForImplementsError(pubspec, package: package); - if (implementsError != null) { - printError('$indentation$implementsError'); - passing = false; - } - - final String? defaultPackageError = - _checkForDefaultPackageError(pubspec, package: package); - if (defaultPackageError != null) { - printError('$indentation$defaultPackageError'); - passing = false; - } - } - - // Ignore metadata that's only relevant for published packages if the - // packages is not intended for publishing. - if (pubspec.publishTo != 'none') { - final List repositoryErrors = - _checkForRepositoryLinkErrors(pubspec, package: package); - if (repositoryErrors.isNotEmpty) { - for (final String error in repositoryErrors) { - printError('$indentation$error'); - } - passing = false; - } - - if (!_checkIssueLink(pubspec)) { - printError( - '${indentation}A package should have an "issue_tracker" link to a ' - 'search for open flutter/flutter bugs with the relevant label:\n' - '${indentation * 2}$_expectedIssueLinkFormat'); - passing = false; - } - - // Don't check descriptions for federated package components other than - // the app-facing package, since they are unlisted, and are expected to - // have short descriptions. - if (!package.isPlatformInterface && !package.isPlatformImplementation) { - final String? descriptionError = - _checkDescription(pubspec, package: package); - if (descriptionError != null) { - printError('$indentation$descriptionError'); - passing = false; - } - } - } - - return passing; - } - - Pubspec? _tryParsePubspec(String pubspecContents) { - try { - return Pubspec.parse(pubspecContents); - } on Exception catch (exception) { - print(' Cannot parse pubspec.yaml: $exception'); - } - return null; - } - - bool _checkSectionOrder( - List pubspecLines, List sectionOrder) { - int previousSectionIndex = 0; - for (final String line in pubspecLines) { - final int index = sectionOrder.indexOf(line); - if (index == -1) { - continue; - } - if (index < previousSectionIndex) { - return false; - } - previousSectionIndex = index; - } - return true; - } - - List _checkForRepositoryLinkErrors( - Pubspec pubspec, { - required RepositoryPackage package, - }) { - final List errorMessages = []; - if (pubspec.repository == null) { - errorMessages.add('Missing "repository"'); - } else { - final String relativePackagePath = - getRelativePosixPath(package.directory, from: packagesDir.parent); - if (!pubspec.repository!.path.endsWith(relativePackagePath)) { - errorMessages - .add('The "repository" link should end with the package path.'); - } - - if (pubspec.repository!.path.contains('/master/')) { - errorMessages - .add('The "repository" link should use "main", not "master".'); - } - } - - if (pubspec.homepage != null) { - errorMessages - .add('Found a "homepage" entry; only "repository" should be used.'); - } - - return errorMessages; - } - - // Validates the "description" field for a package, returning an error - // string if there are any issues. - String? _checkDescription( - Pubspec pubspec, { - required RepositoryPackage package, - }) { - final String? description = pubspec.description; - if (description == null) { - return 'Missing "description"'; - } - - if (description.length < 60) { - return '"description" is too short. pub.dev recommends package ' - 'descriptions of 60-180 characters.'; - } - if (description.length > 180) { - return '"description" is too long. pub.dev recommends package ' - 'descriptions of 60-180 characters.'; - } - return null; - } - - bool _checkIssueLink(Pubspec pubspec) { - return pubspec.issueTracker - ?.toString() - .startsWith(_expectedIssueLinkFormat) ?? - false; - } - - // Validates the "implements" keyword for a plugin, returning an error - // string if there are any issues. - // - // Should only be called on plugin packages. - String? _checkForImplementsError( - Pubspec pubspec, { - required RepositoryPackage package, - }) { - if (_isImplementationPackage(package)) { - final YamlMap pluginSection = pubspec.flutter!['plugin'] as YamlMap; - final String? implements = pluginSection['implements'] as String?; - final String expectedImplements = package.directory.parent.basename; - if (implements == null) { - return 'Missing "implements: $expectedImplements" in "plugin" section.'; - } else if (implements != expectedImplements) { - return 'Expecetd "implements: $expectedImplements"; ' - 'found "implements: $implements".'; - } - } - return null; - } - - // Validates any "default_package" entries a plugin, returning an error - // string if there are any issues. - // - // Should only be called on plugin packages. - String? _checkForDefaultPackageError( - Pubspec pubspec, { - required RepositoryPackage package, - }) { - final YamlMap pluginSection = pubspec.flutter!['plugin'] as YamlMap; - final YamlMap? platforms = pluginSection['platforms'] as YamlMap?; - if (platforms == null) { - logWarning('Does not implement any platforms'); - return null; - } - final String packageName = package.directory.basename; - - // Validate that the default_package entries look correct (e.g., no typos). - final Set defaultPackages = {}; - for (final MapEntry platformEntry in platforms.entries) { - final YamlMap platformDetails = platformEntry.value! as YamlMap; - final String? defaultPackage = - platformDetails['default_package'] as String?; - if (defaultPackage != null) { - defaultPackages.add(defaultPackage); - if (!defaultPackage.startsWith('${packageName}_')) { - return '"$defaultPackage" is not an expected implementation name ' - 'for "$packageName"'; - } - } - } - - // Validate that all default_packages are also dependencies. - final Iterable dependencies = pubspec.dependencies.keys; - final Iterable missingPackages = defaultPackages - .where((String package) => !dependencies.contains(package)); - if (missingPackages.isNotEmpty) { - return 'The following default_packages are missing ' - 'corresponding dependencies:\n' - ' ${missingPackages.join('\n ')}'; - } - - return null; - } - - // Returns true if [packageName] appears to be an implementation package - // according to repository conventions. - bool _isImplementationPackage(RepositoryPackage package) { - if (!package.isFederated) { - return false; - } - final String packageName = package.directory.basename; - final String parentName = package.directory.parent.basename; - // A few known package names are not implementation packages; assume - // anything else is. (This is done instead of listing known implementation - // suffixes to allow for non-standard suffixes; e.g., to put several - // platforms in one package for code-sharing purposes.) - const Set nonImplementationSuffixes = { - '', // App-facing package. - '_platform_interface', // Platform interface package. - }; - final String suffix = packageName.substring(parentName.length); - return !nonImplementationSuffixes.contains(suffix); - } - - /// Validates that a Flutter package has a minimum SDK version constraint of - /// at least [minMinFlutterVersion] (if provided), or that a non-Flutter - /// package has a minimum SDK version constraint of [minMinDartVersion] - /// (if provided). - /// - /// Returns an error string if validation fails. - String? _checkForMinimumVersionError( - Pubspec pubspec, - RepositoryPackage package, { - Version? minMinDartVersion, - Version? minMinFlutterVersion, - }) { - final VersionConstraint? dartConstraint = pubspec.environment?['sdk']; - final VersionConstraint? flutterConstraint = - pubspec.environment?['flutter']; - - if (flutterConstraint != null) { - // Validate Flutter packages against the Flutter requirement. - if (minMinFlutterVersion != null) { - final Version? constraintMin = - flutterConstraint is VersionRange ? flutterConstraint.min : null; - if ((constraintMin ?? Version(0, 0, 0)) < minMinFlutterVersion) { - return 'Minimum allowed Flutter version $constraintMin is less than $minMinFlutterVersion'; - } - } - } else { - // Validate non-Flutter packages against the Dart requirement. - if (minMinDartVersion != null) { - final Version? constraintMin = - dartConstraint is VersionRange ? dartConstraint.min : null; - if ((constraintMin ?? Version(0, 0, 0)) < minMinDartVersion) { - return 'Minimum allowed Dart version $constraintMin is less than $minMinDartVersion'; - } - } - } - - return null; - } -} diff --git a/script/tool/lib/src/readme_check_command.dart b/script/tool/lib/src/readme_check_command.dart deleted file mode 100644 index cbbb8b835a13..000000000000 --- a/script/tool/lib/src/readme_check_command.dart +++ /dev/null @@ -1,344 +0,0 @@ -// 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 'package:file/file.dart'; -import 'package:git/git.dart'; -import 'package:platform/platform.dart'; -import 'package:yaml/yaml.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -const String _instructionWikiUrl = - 'https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages'; - -/// A command to enforce README conventions across the repository. -class ReadmeCheckCommand extends PackageLoopingCommand { - /// Creates an instance of the README check command. - ReadmeCheckCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - GitDir? gitDir, - }) : super( - packagesDir, - processRunner: processRunner, - platform: platform, - gitDir: gitDir, - ) { - argParser.addFlag(_requireExcerptsArg, - help: 'Require that Dart code blocks be managed by code-excerpt.'); - } - - static const String _requireExcerptsArg = 'require-excerpts'; - - // Standardized capitalizations for platforms that a plugin can support. - static const Map _standardPlatformNames = { - 'android': 'Android', - 'ios': 'iOS', - 'linux': 'Linux', - 'macos': 'macOS', - 'web': 'Web', - 'windows': 'Windows', - }; - - @override - final String name = 'readme-check'; - - @override - final String description = - 'Checks that READMEs follow repository conventions.'; - - @override - bool get hasLongOutput => false; - - @override - Future runForPackage(RepositoryPackage package) async { - final List errors = _validateReadme(package.readmeFile, - mainPackage: package, isExample: false); - for (final RepositoryPackage packageToCheck in package.getExamples()) { - errors.addAll(_validateReadme(packageToCheck.readmeFile, - mainPackage: package, isExample: true)); - } - - // If there's an example/README.md for a multi-example package, validate - // that as well, as it will be shown on pub.dev. - final Directory exampleDir = package.directory.childDirectory('example'); - final File exampleDirReadme = exampleDir.childFile('README.md'); - if (exampleDir.existsSync() && !isPackage(exampleDir)) { - errors.addAll(_validateReadme(exampleDirReadme, - mainPackage: package, isExample: true)); - } - - return errors.isEmpty - ? PackageResult.success() - : PackageResult.fail(errors); - } - - List _validateReadme(File readme, - {required RepositoryPackage mainPackage, required bool isExample}) { - if (!readme.existsSync()) { - if (isExample) { - print('${indentation}No README for ' - '${getRelativePosixPath(readme.parent, from: mainPackage.directory)}'); - return []; - } else { - printError('${indentation}No README found at ' - '${getRelativePosixPath(readme, from: mainPackage.directory)}'); - return ['Missing README.md']; - } - } - - print('${indentation}Checking ' - '${getRelativePosixPath(readme, from: mainPackage.directory)}...'); - - final List readmeLines = readme.readAsLinesSync(); - final List errors = []; - - final String? blockValidationError = - _validateCodeBlocks(readmeLines, mainPackage: mainPackage); - if (blockValidationError != null) { - errors.add(blockValidationError); - } - - errors.addAll(_validateBoilerplate(readmeLines, - mainPackage: mainPackage, isExample: isExample)); - - // Check if this is the main readme for a plugin, and if so enforce extra - // checks. - if (!isExample) { - final Pubspec pubspec = mainPackage.parsePubspec(); - final bool isPlugin = pubspec.flutter?['plugin'] != null; - if (isPlugin && (!mainPackage.isFederated || mainPackage.isAppFacing)) { - final String? error = _validateSupportedPlatforms(readmeLines, pubspec); - if (error != null) { - errors.add(error); - } - } - } - - return errors; - } - - /// Validates that code blocks (``` ... ```) follow repository standards. - String? _validateCodeBlocks( - List readmeLines, { - required RepositoryPackage mainPackage, - }) { - final RegExp codeBlockDelimiterPattern = RegExp(r'^\s*```\s*([^ ]*)\s*'); - const String excerptTagStart = ' missingLanguageLines = []; - final List missingExcerptLines = []; - bool inBlock = false; - for (int i = 0; i < readmeLines.length; ++i) { - final RegExpMatch? match = - codeBlockDelimiterPattern.firstMatch(readmeLines[i]); - if (match == null) { - continue; - } - if (inBlock) { - inBlock = false; - continue; - } - inBlock = true; - - final int humanReadableLineNumber = i + 1; - - // Ensure that there's a language tag. - final String infoString = match[1] ?? ''; - if (infoString.isEmpty) { - missingLanguageLines.add(humanReadableLineNumber); - continue; - } - - // Check for code-excerpt usage if requested. - if (getBoolArg(_requireExcerptsArg) && infoString == 'dart') { - if (i == 0 || !readmeLines[i - 1].trim().startsWith(excerptTagStart)) { - missingExcerptLines.add(humanReadableLineNumber); - } - } - } - - String? errorSummary; - - if (missingLanguageLines.isNotEmpty) { - for (final int lineNumber in missingLanguageLines) { - printError('${indentation}Code block at line $lineNumber is missing ' - 'a language identifier.'); - } - printError( - '\n${indentation}For each block listed above, add a language tag to ' - 'the opening block. For instance, for Dart code, use:\n' - '${indentation * 2}```dart\n'); - errorSummary = 'Missing language identifier for code block'; - } - - // If any blocks use code excerpts, make sure excerpting is configured - // for the package. - if (readmeLines.any((String line) => line.startsWith(excerptTagStart))) { - const String buildRunnerConfigFile = 'build.excerpt.yaml'; - if (!mainPackage.getExamples().any((RepositoryPackage example) => - example.directory.childFile(buildRunnerConfigFile).existsSync())) { - printError('code-excerpt tag found, but the package is not configured ' - 'for excerpting. Follow the instructions at\n' - '$_instructionWikiUrl\n' - 'for setting up a build.excerpt.yaml file.'); - errorSummary ??= 'Missing code-excerpt configuration'; - } - } - - if (missingExcerptLines.isNotEmpty) { - for (final int lineNumber in missingExcerptLines) { - printError('${indentation}Dart code block at line $lineNumber is not ' - 'managed by code-excerpt.'); - } - printError( - '\n${indentation}For each block listed above, add ' - 'tag on the previous line, and ensure that a build.excerpt.yaml is ' - 'configured for the source example as explained at\n' - '$_instructionWikiUrl'); - errorSummary ??= 'Missing code-excerpt management for code block'; - } - - return errorSummary; - } - - /// Validates that the plugin has a supported platforms table following the - /// expected format, returning an error string if any issues are found. - String? _validateSupportedPlatforms( - List readmeLines, Pubspec pubspec) { - // Example table following expected format: - // | | Android | iOS | Web | - // |----------------|---------|----------|------------------------| - // | **Support** | SDK 21+ | iOS 10+* | [See `camera_web `][1] | - final int detailsLineNumber = readmeLines - .indexWhere((String line) => line.startsWith('| **Support**')); - if (detailsLineNumber == -1) { - return 'No OS support table found'; - } - final int osLineNumber = detailsLineNumber - 2; - if (osLineNumber < 0 || !readmeLines[osLineNumber].startsWith('|')) { - return 'OS support table does not have the expected header format'; - } - - // Utility method to convert an iterable of strings to a case-insensitive - // sorted, comma-separated string of its elements. - String sortedListString(Iterable entries) { - final List entryList = entries.toList(); - entryList.sort( - (String a, String b) => a.toLowerCase().compareTo(b.toLowerCase())); - return entryList.join(', '); - } - - // Validate that the supported OS lists match. - final YamlMap pluginSection = pubspec.flutter!['plugin'] as YamlMap; - final dynamic platformsEntry = pluginSection['platforms']; - if (platformsEntry == null) { - logWarning('Plugin not support any platforms'); - return null; - } - final YamlMap platformSupportMaps = platformsEntry as YamlMap; - final Set actuallySupportedPlatform = - platformSupportMaps.keys.toSet().cast(); - final Iterable documentedPlatforms = readmeLines[osLineNumber] - .split('|') - .map((String entry) => entry.trim()) - .where((String entry) => entry.isNotEmpty); - final Set documentedPlatformsLowercase = - documentedPlatforms.map((String entry) => entry.toLowerCase()).toSet(); - if (actuallySupportedPlatform.length != documentedPlatforms.length || - actuallySupportedPlatform - .intersection(documentedPlatformsLowercase) - .length != - actuallySupportedPlatform.length) { - printError(''' -${indentation}OS support table does not match supported platforms: -${indentation * 2}Actual: ${sortedListString(actuallySupportedPlatform)} -${indentation * 2}Documented: ${sortedListString(documentedPlatformsLowercase)} -'''); - return 'Incorrect OS support table'; - } - - // Enforce a standard set of capitalizations for the OS headings. - final Iterable incorrectCapitalizations = documentedPlatforms - .toSet() - .difference(_standardPlatformNames.values.toSet()); - if (incorrectCapitalizations.isNotEmpty) { - final Iterable expectedVersions = incorrectCapitalizations - .map((String name) => _standardPlatformNames[name.toLowerCase()]!); - printError(''' -${indentation}Incorrect OS capitalization: ${sortedListString(incorrectCapitalizations)} -${indentation * 2}Please use standard capitalizations: ${sortedListString(expectedVersions)} -'''); - return 'Incorrect OS support formatting'; - } - - // TODO(stuartmorgan): Add validation that the minimums in the table are - // consistent with what the current implementations require. See - // https://github.com/flutter/flutter/issues/84200 - return null; - } - - /// Validates [readmeLines], outputing error messages for any issue and - /// returning an array of error summaries (if any). - /// - /// Returns an empty array if validation passes. - List _validateBoilerplate( - List readmeLines, { - required RepositoryPackage mainPackage, - required bool isExample, - }) { - final List errors = []; - - if (_containsTemplateFlutterBoilerplate(readmeLines)) { - printError('${indentation}The boilerplate section about getting started ' - 'with Flutter should not be left in.'); - errors.add('Contains template boilerplate'); - } - - // Enforce a repository-standard message in implementation plugin examples, - // since they aren't typical examples, which has been a source of - // confusion for plugin clients who find them. - if (isExample && mainPackage.isPlatformImplementation) { - if (_containsExampleBoilerplate(readmeLines)) { - printError('${indentation}The boilerplate should not be left in for a ' - "federated plugin implementation package's example."); - errors.add('Contains template boilerplate'); - } - if (!_containsImplementationExampleExplanation(readmeLines)) { - printError('${indentation}The example README for a platform ' - 'implementation package should warn readers about its intended ' - 'use. Please copy the example README from another implementation ' - 'package in this repository.'); - errors.add('Missing implementation package example warning'); - } - } - - return errors; - } - - /// Returns true if the README still has unwanted parts of the boilerplate - /// from the `flutter create` templates. - bool _containsTemplateFlutterBoilerplate(List readmeLines) { - return readmeLines.any((String line) => - line.contains('For help getting started with Flutter')); - } - - /// Returns true if the README still has the generic description of an - /// example from the `flutter create` templates. - bool _containsExampleBoilerplate(List readmeLines) { - return readmeLines - .any((String line) => line.contains('Demonstrates how to use the')); - } - - /// Returns true if the README contains the repository-standard explanation of - /// the purpose of a federated plugin implementation's example. - bool _containsImplementationExampleExplanation(List readmeLines) { - return readmeLines.contains('# Platform Implementation Test App') && - readmeLines - .any((String line) => line.contains('This is a test app for')); - } -} diff --git a/script/tool/lib/src/remove_dev_dependencies.dart b/script/tool/lib/src/remove_dev_dependencies.dart deleted file mode 100644 index 3085e0df85e0..000000000000 --- a/script/tool/lib/src/remove_dev_dependencies.dart +++ /dev/null @@ -1,58 +0,0 @@ -// 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 'package:file/file.dart'; -import 'package:yaml/yaml.dart'; -import 'package:yaml_edit/yaml_edit.dart'; - -import 'common/package_looping_command.dart'; -import 'common/repository_package.dart'; - -/// A command to remove dev_dependencies, which are not used by package clients. -/// -/// This is intended for use with legacy Flutter version testing, to allow -/// running analysis (with --lib-only) with versions that are supported for -/// clients of the library, but not for development of the library. -class RemoveDevDependenciesCommand extends PackageLoopingCommand { - /// Creates a publish metadata updater command instance. - RemoveDevDependenciesCommand(Directory packagesDir) : super(packagesDir); - - @override - final String name = 'remove-dev-dependencies'; - - @override - final String description = 'Removes any dev_dependencies section from a ' - 'package, to allow more legacy testing.'; - - @override - bool get hasLongOutput => false; - - @override - PackageLoopingType get packageLoopingType => - PackageLoopingType.includeAllSubpackages; - - @override - Future runForPackage(RepositoryPackage package) async { - bool changed = false; - final YamlEditor editablePubspec = - YamlEditor(package.pubspecFile.readAsStringSync()); - const String devDependenciesKey = 'dev_dependencies'; - final YamlNode root = editablePubspec.parseAt([]); - final YamlMap? devDependencies = - (root as YamlMap)[devDependenciesKey] as YamlMap?; - if (devDependencies != null) { - changed = true; - print('${indentation}Removed dev_dependencies'); - editablePubspec.remove([devDependenciesKey]); - } - - if (changed) { - package.pubspecFile.writeAsStringSync(editablePubspec.toString()); - } - - return changed - ? PackageResult.success() - : PackageResult.skip('Nothing to remove.'); - } -} diff --git a/script/tool/lib/src/test_command.dart b/script/tool/lib/src/test_command.dart deleted file mode 100644 index 5101b8f19e7e..000000000000 --- a/script/tool/lib/src/test_command.dart +++ /dev/null @@ -1,104 +0,0 @@ -// 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 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/plugin_utils.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -/// A command to run Dart unit tests for packages. -class TestCommand extends PackageLoopingCommand { - /// Creates an instance of the test command. - TestCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : super(packagesDir, processRunner: processRunner, platform: platform) { - argParser.addOption( - kEnableExperiment, - defaultsTo: '', - help: - 'Runs Dart unit tests in Dart VM with the given experiments enabled. ' - 'See https://github.com/dart-lang/sdk/blob/main/docs/process/experimental-flags.md ' - 'for details.', - ); - } - - @override - final String name = 'test'; - - @override - final String description = 'Runs the Dart tests for all packages.\n\n' - 'This command requires "flutter" to be in your path.'; - - @override - PackageLoopingType get packageLoopingType => - PackageLoopingType.includeAllSubpackages; - - @override - Future runForPackage(RepositoryPackage package) async { - if (!package.testDirectory.existsSync()) { - return PackageResult.skip('No test/ directory.'); - } - - bool passed; - if (package.requiresFlutter()) { - passed = await _runFlutterTests(package); - } else { - passed = await _runDartTests(package); - } - return passed ? PackageResult.success() : PackageResult.fail(); - } - - /// Runs the Dart tests for a Flutter package, returning true on success. - Future _runFlutterTests(RepositoryPackage package) async { - final String experiment = getStringArg(kEnableExperiment); - - final int exitCode = await processRunner.runAndStream( - flutterCommand, - [ - 'test', - '--color', - if (experiment.isNotEmpty) '--enable-experiment=$experiment', - // TODO(ditman): Remove this once all plugins are migrated to 'drive'. - if (pluginSupportsPlatform(platformWeb, package)) '--platform=chrome', - ], - workingDir: package.directory, - ); - return exitCode == 0; - } - - /// Runs the Dart tests for a non-Flutter package, returning true on success. - Future _runDartTests(RepositoryPackage package) async { - // Unlike `flutter test`, `pub run test` does not automatically get - // packages - int exitCode = await processRunner.runAndStream( - 'dart', - ['pub', 'get'], - workingDir: package.directory, - ); - if (exitCode != 0) { - printError('Unable to fetch dependencies.'); - return false; - } - - final String experiment = getStringArg(kEnableExperiment); - - exitCode = await processRunner.runAndStream( - 'dart', - [ - 'run', - if (experiment.isNotEmpty) '--enable-experiment=$experiment', - 'test', - ], - workingDir: package.directory, - ); - - return exitCode == 0; - } -} diff --git a/script/tool/lib/src/update_excerpts_command.dart b/script/tool/lib/src/update_excerpts_command.dart deleted file mode 100644 index e65bed846cbc..000000000000 --- a/script/tool/lib/src/update_excerpts_command.dart +++ /dev/null @@ -1,233 +0,0 @@ -// 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 'dart:io' as io; - -import 'package:file/file.dart'; -import 'package:git/git.dart'; -import 'package:platform/platform.dart'; -import 'package:yaml/yaml.dart'; -import 'package:yaml_edit/yaml_edit.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -/// A command to update README code excerpts from code files. -class UpdateExcerptsCommand extends PackageLoopingCommand { - /// Creates a excerpt updater command instance. - UpdateExcerptsCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - GitDir? gitDir, - }) : super( - packagesDir, - processRunner: processRunner, - platform: platform, - gitDir: gitDir, - ) { - argParser.addFlag(_failOnChangeFlag, hide: true); - } - - static const String _failOnChangeFlag = 'fail-on-change'; - - static const String _buildRunnerConfigName = 'excerpt'; - // The name of the build_runner configuration file that will be in an example - // directory if the package is set up to use `code-excerpt`. - static const String _buildRunnerConfigFile = - 'build.$_buildRunnerConfigName.yaml'; - - // The relative directory path to put the extracted excerpt yaml files. - static const String _excerptOutputDir = 'excerpts'; - - // The filename to store the pre-modification copy of the pubspec. - static const String _originalPubspecFilename = - 'pubspec.plugin_tools_original.yaml'; - - @override - final String name = 'update-excerpts'; - - @override - final String description = 'Updates code excerpts in README.md files, based ' - 'on code from code files, via code-excerpt'; - - @override - Future runForPackage(RepositoryPackage package) async { - final Iterable configuredExamples = package - .getExamples() - .where((RepositoryPackage example) => - example.directory.childFile(_buildRunnerConfigFile).existsSync()); - - if (configuredExamples.isEmpty) { - return PackageResult.skip( - 'No $_buildRunnerConfigFile found in example(s).'); - } - - final Directory repoRoot = - packagesDir.fileSystem.directory((await gitDir).path); - - for (final RepositoryPackage example in configuredExamples) { - _addSubmoduleDependencies(example, repoRoot: repoRoot); - - try { - // Ensure that dependencies are available. - final int pubGetExitCode = await processRunner.runAndStream( - 'dart', ['pub', 'get'], - workingDir: example.directory); - if (pubGetExitCode != 0) { - return PackageResult.fail( - ['Unable to get script dependencies']); - } - - // Update the excerpts. - if (!await _extractSnippets(example)) { - return PackageResult.fail(['Unable to extract excerpts']); - } - if (!await _injectSnippets(example, targetPackage: package)) { - return PackageResult.fail(['Unable to inject excerpts']); - } - } finally { - // Clean up the pubspec changes and extracted excerpts directory. - _undoPubspecChanges(example); - final Directory excerptDirectory = - example.directory.childDirectory(_excerptOutputDir); - if (excerptDirectory.existsSync()) { - excerptDirectory.deleteSync(recursive: true); - } - } - } - - if (getBoolArg(_failOnChangeFlag)) { - final String? stateError = await _validateRepositoryState(); - if (stateError != null) { - printError('README.md is out of sync with its source excerpts.\n\n' - 'If you edited code in README.md directly, you should instead edit ' - 'the example source files. If you edited source files, run the ' - 'repository tooling\'s "$name" command on this package, and update ' - 'your PR with the resulting changes.'); - return PackageResult.fail([stateError]); - } - } - - return PackageResult.success(); - } - - /// Runs the extraction step to create the excerpt files for the given - /// example, returning true on success. - Future _extractSnippets(RepositoryPackage example) async { - final int exitCode = await processRunner.runAndStream( - 'dart', - [ - 'run', - 'build_runner', - 'build', - '--config', - _buildRunnerConfigName, - '--output', - _excerptOutputDir, - '--delete-conflicting-outputs', - ], - workingDir: example.directory); - return exitCode == 0; - } - - /// Runs the injection step to update [targetPackage]'s README with the latest - /// excerpts from [example], returning true on success. - Future _injectSnippets( - RepositoryPackage example, { - required RepositoryPackage targetPackage, - }) async { - final String relativeReadmePath = - getRelativePosixPath(targetPackage.readmeFile, from: example.directory); - final int exitCode = await processRunner.runAndStream( - 'dart', - [ - 'run', - 'code_excerpt_updater', - '--write-in-place', - '--yaml', - '--no-escape-ng-interpolation', - relativeReadmePath, - ], - workingDir: example.directory); - return exitCode == 0; - } - - /// Adds `code_excerpter` and `code_excerpt_updater` to [package]'s - /// `dev_dependencies` using path-based references to the submodule copies. - /// - /// This is done on the fly rather than being checked in so that: - /// - Just building examples don't require everyone to check out submodules. - /// - Examples can be analyzed/built even on versions of Flutter that these - /// submodules do not support. - void _addSubmoduleDependencies(RepositoryPackage package, - {required Directory repoRoot}) { - final String pubspecContents = package.pubspecFile.readAsStringSync(); - // Save aside a copy of the current pubspec state. This allows restoration - // to the previous state regardless of its git status at the time the script - // ran. - package.directory - .childFile(_originalPubspecFilename) - .writeAsStringSync(pubspecContents); - - // Update the actual pubspec. - final YamlEditor editablePubspec = YamlEditor(pubspecContents); - const String devDependenciesKey = 'dev_dependencies'; - final YamlNode root = editablePubspec.parseAt([]); - // Ensure that there's a `dev_dependencies` entry to update. - if ((root as YamlMap)[devDependenciesKey] == null) { - editablePubspec.update(['dev_dependencies'], YamlMap()); - } - final Set submoduleDependencies = { - 'code_excerpter', - 'code_excerpt_updater', - }; - final String relativeRootPath = - getRelativePosixPath(repoRoot, from: package.directory); - for (final String dependency in submoduleDependencies) { - editablePubspec.update([ - devDependenciesKey, - dependency - ], { - 'path': '$relativeRootPath/site-shared/packages/$dependency' - }); - } - package.pubspecFile.writeAsStringSync(editablePubspec.toString()); - } - - /// Restores the version of the pubspec that was present before running - /// [_addSubmoduleDependencies]. - void _undoPubspecChanges(RepositoryPackage package) { - package.directory - .childFile(_originalPubspecFilename) - .renameSync(package.pubspecFile.path); - } - - /// Checks the git state, returning an error string if any .md files have - /// changed. - Future _validateRepositoryState() async { - final io.ProcessResult checkFiles = await processRunner.run( - 'git', - ['ls-files', '--modified'], - workingDir: packagesDir, - logOnError: true, - ); - if (checkFiles.exitCode != 0) { - return 'Unable to determine local file state'; - } - - final String stdout = checkFiles.stdout as String; - final List changedFiles = stdout.trim().split('\n'); - final Iterable changedMDFiles = - changedFiles.where((String filePath) => filePath.endsWith('.md')); - if (changedMDFiles.isNotEmpty) { - return 'Snippets are out of sync in the following files: ' - '${changedMDFiles.join(', ')}'; - } - - return null; - } -} diff --git a/script/tool/lib/src/update_release_info_command.dart b/script/tool/lib/src/update_release_info_command.dart deleted file mode 100644 index 240ae72eed71..000000000000 --- a/script/tool/lib/src/update_release_info_command.dart +++ /dev/null @@ -1,317 +0,0 @@ -// 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 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:git/git.dart'; -import 'package:pub_semver/pub_semver.dart'; -import 'package:yaml_edit/yaml_edit.dart'; - -import 'common/core.dart'; -import 'common/git_version_finder.dart'; -import 'common/package_looping_command.dart'; -import 'common/package_state_utils.dart'; -import 'common/repository_package.dart'; - -/// Supported version change types, from smallest to largest component. -enum _VersionIncrementType { build, bugfix, minor } - -/// Possible results of attempting to update a CHANGELOG.md file. -enum _ChangelogUpdateOutcome { addedSection, updatedSection, failed } - -/// A state machine for the process of updating a CHANGELOG.md. -enum _ChangelogUpdateState { - /// Looking for the first version section. - findingFirstSection, - - /// Looking for the first list entry in an existing section. - findingFirstListItem, - - /// Finished with updates. - finishedUpdating, -} - -/// A command to update the changelog, and optionally version, of packages. -class UpdateReleaseInfoCommand extends PackageLoopingCommand { - /// Creates a publish metadata updater command instance. - UpdateReleaseInfoCommand( - Directory packagesDir, { - GitDir? gitDir, - }) : super(packagesDir, gitDir: gitDir) { - argParser.addOption(_changelogFlag, - mandatory: true, - help: 'The changelog entry to add. ' - 'Each line will be a separate list entry.'); - argParser.addOption(_versionTypeFlag, - mandatory: true, - help: 'The version change level', - allowed: [ - _versionNext, - _versionMinimal, - _versionBugfix, - _versionMinor, - ], - allowedHelp: { - _versionNext: - 'No version change; just adds a NEXT entry to the changelog.', - _versionBugfix: 'Increments the bugfix version.', - _versionMinor: 'Increments the minor version.', - _versionMinimal: 'Depending on the changes to each package: ' - 'increments the bugfix version (for publishable changes), ' - "uses NEXT (for changes that don't need to be published), " - 'or skips (if no changes).', - }); - } - - static const String _changelogFlag = 'changelog'; - static const String _versionTypeFlag = 'version'; - - static const String _versionNext = 'next'; - static const String _versionBugfix = 'bugfix'; - static const String _versionMinor = 'minor'; - static const String _versionMinimal = 'minimal'; - - // The version change type, if there is a set type for all platforms. - // - // If null, either there is no version change, or it is dynamic (`minimal`). - _VersionIncrementType? _versionChange; - - // The cache of changed files, for dynamic version change determination. - // - // Only set for `minimal` version change. - late final List _changedFiles; - - @override - final String name = 'update-release-info'; - - @override - final String description = 'Updates CHANGELOG.md files, and optionally the ' - 'version in pubspec.yaml, in a way that is consistent with version-check ' - 'enforcement.'; - - @override - bool get hasLongOutput => false; - - @override - Future initializeRun() async { - if (getStringArg(_changelogFlag).trim().isEmpty) { - throw UsageException('Changelog message must not be empty.', usage); - } - switch (getStringArg(_versionTypeFlag)) { - case _versionMinor: - _versionChange = _VersionIncrementType.minor; - break; - case _versionBugfix: - _versionChange = _VersionIncrementType.bugfix; - break; - case _versionMinimal: - final GitVersionFinder gitVersionFinder = await retrieveVersionFinder(); - // If the line below fails with "Not a valid object name FETCH_HEAD" - // run "git fetch", FETCH_HEAD is a temporary reference that only exists - // after a fetch. This can happen when a branch is made locally and - // pushed but never fetched. - _changedFiles = await gitVersionFinder.getChangedFiles(); - // Anothing other than a fixed change is null. - _versionChange = null; - break; - case _versionNext: - _versionChange = null; - break; - default: - throw UnimplementedError('Unimplemented version change type'); - } - } - - @override - Future runForPackage(RepositoryPackage package) async { - String nextVersionString; - - _VersionIncrementType? versionChange = _versionChange; - - // If the change type is `minimal` determine what changes, if any, are - // needed. - if (versionChange == null && - getStringArg(_versionTypeFlag) == _versionMinimal) { - final Directory gitRoot = - packagesDir.fileSystem.directory((await gitDir).path); - final String relativePackagePath = - getRelativePosixPath(package.directory, from: gitRoot); - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: _changedFiles, - relativePackagePath: relativePackagePath); - - if (!state.hasChanges) { - return PackageResult.skip('No changes to package'); - } - if (!state.needsVersionChange && !state.needsChangelogChange) { - return PackageResult.skip('No non-exempt changes to package'); - } - if (state.needsVersionChange) { - versionChange = _VersionIncrementType.bugfix; - } - } - - if (versionChange != null) { - final Version? updatedVersion = - _updatePubspecVersion(package, versionChange); - if (updatedVersion == null) { - return PackageResult.fail( - ['Could not determine current version.']); - } - nextVersionString = updatedVersion.toString(); - print('${indentation}Incremented version to $nextVersionString.'); - } else { - nextVersionString = 'NEXT'; - } - - final _ChangelogUpdateOutcome updateOutcome = - _updateChangelog(package, nextVersionString); - switch (updateOutcome) { - case _ChangelogUpdateOutcome.addedSection: - print('${indentation}Added a $nextVersionString section.'); - break; - case _ChangelogUpdateOutcome.updatedSection: - print('${indentation}Updated NEXT section.'); - break; - case _ChangelogUpdateOutcome.failed: - return PackageResult.fail(['Could not update CHANGELOG.md.']); - } - - return PackageResult.success(); - } - - _ChangelogUpdateOutcome _updateChangelog( - RepositoryPackage package, String version) { - if (!package.changelogFile.existsSync()) { - printError('${indentation}Missing CHANGELOG.md.'); - return _ChangelogUpdateOutcome.failed; - } - - final String newHeader = '## $version'; - final RegExp listItemPattern = RegExp(r'^(\s*[-*])'); - - final StringBuffer newChangelog = StringBuffer(); - _ChangelogUpdateState state = _ChangelogUpdateState.findingFirstSection; - bool updatedExistingSection = false; - - for (final String line in package.changelogFile.readAsLinesSync()) { - switch (state) { - case _ChangelogUpdateState.findingFirstSection: - final String trimmedLine = line.trim(); - if (trimmedLine.isEmpty) { - // Discard any whitespace at the top of the file. - } else if (trimmedLine == '## NEXT') { - // Replace the header with the new version (which may also be NEXT). - newChangelog.writeln(newHeader); - // Find the existing list to add to. - state = _ChangelogUpdateState.findingFirstListItem; - } else { - // The first content in the file isn't a NEXT section, so just add - // the new section. - [ - newHeader, - '', - ..._changelogAdditionsAsList(), - '', - line, // Don't drop the current line. - ].forEach(newChangelog.writeln); - state = _ChangelogUpdateState.finishedUpdating; - } - break; - case _ChangelogUpdateState.findingFirstListItem: - final RegExpMatch? match = listItemPattern.firstMatch(line); - if (match != null) { - final String listMarker = match[1]!; - // Add the new items on top. If the new change is changing the - // version, then the new item should be more relevant to package - // clients than anything that was already there. If it's still - // NEXT, the order doesn't matter. - [ - ..._changelogAdditionsAsList(listMarker: listMarker), - line, // Don't drop the current line. - ].forEach(newChangelog.writeln); - state = _ChangelogUpdateState.finishedUpdating; - updatedExistingSection = true; - } else if (line.trim().isEmpty) { - // Scan past empty lines, but keep them. - newChangelog.writeln(line); - } else { - printError(' Existing NEXT section has unrecognized format.'); - return _ChangelogUpdateOutcome.failed; - } - break; - case _ChangelogUpdateState.finishedUpdating: - // Once changes are done, add the rest of the lines as-is. - newChangelog.writeln(line); - break; - } - } - - package.changelogFile.writeAsStringSync(newChangelog.toString()); - - return updatedExistingSection - ? _ChangelogUpdateOutcome.updatedSection - : _ChangelogUpdateOutcome.addedSection; - } - - /// Returns the changelog to add as a Markdown list, using the given list - /// bullet style (default to the repository standard of '*'), and adding - /// any missing periods. - /// - /// E.g., 'A line\nAnother line.' will become: - /// ``` - /// [ '* A line.', '* Another line.' ] - /// ``` - Iterable _changelogAdditionsAsList({String listMarker = '*'}) { - return getStringArg(_changelogFlag).split('\n').map((String entry) { - String standardizedEntry = entry.trim(); - if (!standardizedEntry.endsWith('.')) { - standardizedEntry = '$standardizedEntry.'; - } - return '$listMarker $standardizedEntry'; - }); - } - - /// Updates the version in [package]'s pubspec according to [type], returning - /// the new version, or null if there was an error updating the version. - Version? _updatePubspecVersion( - RepositoryPackage package, _VersionIncrementType type) { - final Pubspec pubspec = package.parsePubspec(); - final Version? currentVersion = pubspec.version; - if (currentVersion == null) { - printError('${indentation}No version in pubspec.yaml'); - return null; - } - - // For versions less than 1.0, shift the change down one component per - // Dart versioning conventions. - final _VersionIncrementType adjustedType = currentVersion.major > 0 - ? type - : _VersionIncrementType.values[type.index - 1]; - - final Version newVersion = _nextVersion(currentVersion, adjustedType); - - // Write the new version to the pubspec. - final YamlEditor editablePubspec = - YamlEditor(package.pubspecFile.readAsStringSync()); - editablePubspec.update(['version'], newVersion.toString()); - package.pubspecFile.writeAsStringSync(editablePubspec.toString()); - - return newVersion; - } - - Version _nextVersion(Version version, _VersionIncrementType type) { - switch (type) { - case _VersionIncrementType.minor: - return version.nextMinor; - case _VersionIncrementType.bugfix: - return version.nextPatch; - case _VersionIncrementType.build: - final int buildNumber = - version.build.isEmpty ? 0 : version.build.first as int; - return Version(version.major, version.minor, version.patch, - build: '${buildNumber + 1}'); - } - } -} diff --git a/script/tool/lib/src/version_check_command.dart b/script/tool/lib/src/version_check_command.dart deleted file mode 100644 index bb53620f06d3..000000000000 --- a/script/tool/lib/src/version_check_command.dart +++ /dev/null @@ -1,591 +0,0 @@ -// 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 'package:file/file.dart'; -import 'package:git/git.dart'; -import 'package:http/http.dart' as http; -import 'package:meta/meta.dart'; -import 'package:path/path.dart' as p; -import 'package:platform/platform.dart'; -import 'package:pub_semver/pub_semver.dart'; - -import 'common/core.dart'; -import 'common/git_version_finder.dart'; -import 'common/package_looping_command.dart'; -import 'common/package_state_utils.dart'; -import 'common/process_runner.dart'; -import 'common/pub_version_finder.dart'; -import 'common/repository_package.dart'; - -/// Categories of version change types. -enum NextVersionType { - /// A breaking change. - BREAKING_MAJOR, - - /// A minor change (e.g., added feature). - MINOR, - - /// A bugfix change. - PATCH, - - /// The release of an existing pre-1.0 version. - V1_RELEASE, -} - -/// The state of a package's version relative to the comparison base. -enum _CurrentVersionState { - /// The version is unchanged. - unchanged, - - /// The version has increased, and the transition is valid. - validIncrease, - - /// The version has decrease, and the transition is a valid revert. - validRevert, - - /// The version has changed, and the transition is invalid. - invalidChange, - - /// There was an error determining the version state. - unknown, -} - -/// Returns the set of allowed next non-prerelease versions, with their change -/// type, for [version]. -/// -/// [newVersion] is used to check whether this is a pre-1.0 version bump, as -/// those have different semver rules. -@visibleForTesting -Map getAllowedNextVersions( - Version version, { - required Version newVersion, -}) { - final Map allowedNextVersions = - { - version.nextMajor: NextVersionType.BREAKING_MAJOR, - version.nextMinor: NextVersionType.MINOR, - version.nextPatch: NextVersionType.PATCH, - }; - - if (version.major < 1 && newVersion.major < 1) { - int nextBuildNumber = -1; - if (version.build.isEmpty) { - nextBuildNumber = 1; - } else { - final int currentBuildNumber = version.build.first as int; - nextBuildNumber = currentBuildNumber + 1; - } - final Version nextBuildVersion = Version( - version.major, - version.minor, - version.patch, - build: nextBuildNumber.toString(), - ); - allowedNextVersions.clear(); - allowedNextVersions[version.nextMajor] = NextVersionType.V1_RELEASE; - allowedNextVersions[version.nextMinor] = NextVersionType.BREAKING_MAJOR; - allowedNextVersions[version.nextPatch] = NextVersionType.MINOR; - allowedNextVersions[nextBuildVersion] = NextVersionType.PATCH; - } - return allowedNextVersions; -} - -/// A command to validate version changes to packages. -class VersionCheckCommand extends PackageLoopingCommand { - /// Creates an instance of the version check command. - VersionCheckCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - GitDir? gitDir, - http.Client? httpClient, - }) : _pubVersionFinder = - PubVersionFinder(httpClient: httpClient ?? http.Client()), - super( - packagesDir, - processRunner: processRunner, - platform: platform, - gitDir: gitDir, - ) { - argParser.addFlag( - _againstPubFlag, - help: 'Whether the version check should run against the version on pub.\n' - 'Defaults to false, which means the version check only run against ' - 'the previous version in code.', - ); - argParser.addOption(_prLabelsArg, - help: 'A comma-separated list of labels associated with this PR, ' - 'if applicable.\n\n' - 'If supplied, this may be to allow overrides to some version ' - 'checks.'); - argParser.addFlag(_checkForMissingChanges, - help: 'Validates that changes to packages include CHANGELOG and ' - 'version changes unless they meet an established exemption.\n\n' - 'If used with --$_prLabelsArg, this is should only be ' - 'used in pre-submit CI checks, to prevent post-submit breakage ' - 'when labels are no longer applicable.', - hide: true); - argParser.addFlag(_ignorePlatformInterfaceBreaks, - help: 'Bypasses the check that platform interfaces do not contain ' - 'breaking changes.\n\n' - 'This is only intended for use in post-submit CI checks, to ' - 'prevent post-submit breakage when overriding the check with ' - 'labels. Pre-submit checks should always use ' - '--$_prLabelsArg instead.', - hide: true); - } - - static const String _againstPubFlag = 'against-pub'; - static const String _prLabelsArg = 'pr-labels'; - static const String _checkForMissingChanges = 'check-for-missing-changes'; - static const String _ignorePlatformInterfaceBreaks = - 'ignore-platform-interface-breaks'; - - /// The label that must be on a PR to allow a breaking - /// change to a platform interface. - static const String _breakingChangeOverrideLabel = - 'override: allow breaking change'; - - /// The label that must be on a PR to allow skipping a version change for a PR - /// that would normally require one. - static const String _missingVersionChangeOverrideLabel = - 'override: no versioning needed'; - - /// The label that must be on a PR to allow skipping a CHANGELOG change for a - /// PR that would normally require one. - static const String _missingChangelogChangeOverrideLabel = - 'override: no changelog needed'; - - final PubVersionFinder _pubVersionFinder; - - late final GitVersionFinder _gitVersionFinder; - late final String _mergeBase; - late final List _changedFiles; - - late final Set _prLabels = _getPRLabels(); - - @override - final String name = 'version-check'; - - @override - final String description = - 'Checks if the versions of packages have been incremented per pub specification.\n' - 'Also checks if the latest version in CHANGELOG matches the version in pubspec.\n\n' - 'This command requires "pub" and "flutter" to be in your path.'; - - @override - bool get hasLongOutput => false; - - @override - Future initializeRun() async { - _gitVersionFinder = await retrieveVersionFinder(); - _mergeBase = await _gitVersionFinder.getBaseSha(); - _changedFiles = await _gitVersionFinder.getChangedFiles(); - } - - @override - Future runForPackage(RepositoryPackage package) async { - final Pubspec? pubspec = _tryParsePubspec(package); - if (pubspec == null) { - // No remaining checks make sense, so fail immediately. - return PackageResult.fail(['Invalid pubspec.yaml.']); - } - - if (pubspec.publishTo == 'none') { - return PackageResult.skip('Found "publish_to: none".'); - } - - final Version? currentPubspecVersion = pubspec.version; - if (currentPubspecVersion == null) { - printError('${indentation}No version found in pubspec.yaml. A package ' - 'that intentionally has no version should be marked ' - '"publish_to: none".'); - // No remaining checks make sense, so fail immediately. - return PackageResult.fail(['No pubspec.yaml version.']); - } - - final List errors = []; - - bool versionChanged; - final _CurrentVersionState versionState = - await _getVersionState(package, pubspec: pubspec); - switch (versionState) { - case _CurrentVersionState.unchanged: - versionChanged = false; - break; - case _CurrentVersionState.validIncrease: - case _CurrentVersionState.validRevert: - versionChanged = true; - break; - case _CurrentVersionState.invalidChange: - versionChanged = true; - errors.add('Disallowed version change.'); - break; - case _CurrentVersionState.unknown: - versionChanged = false; - errors.add('Unable to determine previous version.'); - break; - } - - if (!(await _validateChangelogVersion(package, - pubspec: pubspec, pubspecVersionState: versionState))) { - errors.add('CHANGELOG.md failed validation.'); - } - - // If there are no other issues, make sure that there isn't a missing - // change to the version and/or CHANGELOG. - if (getBoolArg(_checkForMissingChanges) && - !versionChanged && - errors.isEmpty) { - final String? error = await _checkForMissingChangeError(package); - if (error != null) { - errors.add(error); - } - } - - return errors.isEmpty - ? PackageResult.success() - : PackageResult.fail(errors); - } - - @override - Future completeRun() async { - _pubVersionFinder.httpClient.close(); - } - - /// Returns the previous published version of [package]. - /// - /// [packageName] must be the actual name of the package as published (i.e., - /// the name from pubspec.yaml, not the on disk name if different.) - Future _fetchPreviousVersionFromPub(String packageName) async { - final PubVersionFinderResponse pubVersionFinderResponse = - await _pubVersionFinder.getPackageVersion(packageName: packageName); - switch (pubVersionFinderResponse.result) { - case PubVersionFinderResult.success: - return pubVersionFinderResponse.versions.first; - case PubVersionFinderResult.fail: - printError(''' -${indentation}Error fetching version on pub for $packageName. -${indentation}HTTP Status ${pubVersionFinderResponse.httpResponse.statusCode} -${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body} -'''); - return null; - case PubVersionFinderResult.noPackageFound: - return Version.none; - } - } - - /// Returns the version of [package] from git at the base comparison hash. - Future _getPreviousVersionFromGit(RepositoryPackage package) async { - final File pubspecFile = package.pubspecFile; - final String relativePath = - path.relative(pubspecFile.absolute.path, from: (await gitDir).path); - // Use Posix-style paths for git. - final String gitPath = path.style == p.Style.windows - ? p.posix.joinAll(path.split(relativePath)) - : relativePath; - return _gitVersionFinder.getPackageVersion(gitPath, gitRef: _mergeBase); - } - - /// Returns the state of the verison of [package] relative to the comparison - /// base (git or pub, depending on flags). - Future<_CurrentVersionState> _getVersionState( - RepositoryPackage package, { - required Pubspec pubspec, - }) async { - // This method isn't called unless `version` is non-null. - final Version currentVersion = pubspec.version!; - Version? previousVersion; - String previousVersionSource; - if (getBoolArg(_againstPubFlag)) { - previousVersionSource = 'pub'; - previousVersion = await _fetchPreviousVersionFromPub(pubspec.name); - if (previousVersion == null) { - return _CurrentVersionState.unknown; - } - if (previousVersion != Version.none) { - print( - '$indentation${pubspec.name}: Current largest version on pub: $previousVersion'); - } - } else { - previousVersionSource = _mergeBase; - previousVersion = - await _getPreviousVersionFromGit(package) ?? Version.none; - } - if (previousVersion == Version.none) { - print('${indentation}Unable to find previous version ' - '${getBoolArg(_againstPubFlag) ? 'on pub server' : 'at git base'}.'); - logWarning( - '${indentation}If this package is not new, something has gone wrong.'); - return _CurrentVersionState.validIncrease; // Assume new, thus valid. - } - - if (previousVersion == currentVersion) { - print('${indentation}No version change.'); - return _CurrentVersionState.unchanged; - } - - // Check for reverts when doing local validation. - if (!getBoolArg(_againstPubFlag) && currentVersion < previousVersion) { - // Since this skips validation, try to ensure that it really is likely - // to be a revert rather than a typo by checking that the transition - // from the lower version to the new version would have been valid. - if (_shouldAllowVersionChange( - oldVersion: currentVersion, newVersion: previousVersion)) { - logWarning('${indentation}New version is lower than previous version. ' - 'This is assumed to be a revert.'); - return _CurrentVersionState.validRevert; - } - } - - final Map allowedNextVersions = - getAllowedNextVersions(previousVersion, newVersion: currentVersion); - - if (_shouldAllowVersionChange( - oldVersion: previousVersion, newVersion: currentVersion)) { - print('$indentation$previousVersion -> $currentVersion'); - } else { - printError('${indentation}Incorrectly updated version.\n' - '${indentation}HEAD: $currentVersion, $previousVersionSource: $previousVersion.\n' - '${indentation}Allowed versions: $allowedNextVersions'); - return _CurrentVersionState.invalidChange; - } - - // Check whether the version (or for a pre-release, the version that - // pre-release would eventually be released as) is a breaking change, and - // if so, validate it. - final Version targetReleaseVersion = - currentVersion.isPreRelease ? currentVersion.nextPatch : currentVersion; - if (allowedNextVersions[targetReleaseVersion] == - NextVersionType.BREAKING_MAJOR && - !_validateBreakingChange(package)) { - printError('${indentation}Breaking change detected.\n' - '${indentation}Breaking changes to platform interfaces are not ' - 'allowed without explicit justification.\n' - '${indentation}See ' - 'https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages ' - 'for more information.'); - return _CurrentVersionState.invalidChange; - } - - return _CurrentVersionState.validIncrease; - } - - /// Checks whether or not [package]'s CHANGELOG's versioning is correct, - /// both that it matches [pubspec] and that NEXT is used correctly, printing - /// the results of its checks. - /// - /// Returns false if the CHANGELOG fails validation. - Future _validateChangelogVersion( - RepositoryPackage package, { - required Pubspec pubspec, - required _CurrentVersionState pubspecVersionState, - }) async { - // This method isn't called unless `version` is non-null. - final Version fromPubspec = pubspec.version!; - - // get first version from CHANGELOG - final File changelog = package.changelogFile; - final List lines = changelog.readAsLinesSync(); - String? firstLineWithText; - final Iterator iterator = lines.iterator; - while (iterator.moveNext()) { - if (iterator.current.trim().isNotEmpty) { - firstLineWithText = iterator.current.trim(); - break; - } - } - // Remove all leading mark down syntax from the version line. - String? versionString = firstLineWithText?.split(' ').last; - - final String badNextErrorMessage = '${indentation}When bumping the version ' - 'for release, the NEXT section should be incorporated into the new ' - "version's release notes."; - - // Skip validation for the special NEXT version that's used to accumulate - // changes that don't warrant publishing on their own. - final bool hasNextSection = versionString == 'NEXT'; - if (hasNextSection) { - // NEXT should not be present in a commit that increases the version. - if (pubspecVersionState == _CurrentVersionState.validIncrease || - pubspecVersionState == _CurrentVersionState.invalidChange) { - printError(badNextErrorMessage); - return false; - } - print( - '${indentation}Found NEXT; validating next version in the CHANGELOG.'); - // Ensure that the version in pubspec hasn't changed without updating - // CHANGELOG. That means the next version entry in the CHANGELOG should - // pass the normal validation. - versionString = null; - while (iterator.moveNext()) { - if (iterator.current.trim().startsWith('## ')) { - versionString = iterator.current.trim().split(' ').last; - break; - } - } - } - - if (versionString == null) { - printError('${indentation}Unable to find a version in CHANGELOG.md'); - print('${indentation}The current version should be on a line starting ' - 'with "## ", either on the first non-empty line or after a "## NEXT" ' - 'section.'); - return false; - } - - final Version fromChangeLog; - try { - fromChangeLog = Version.parse(versionString); - } on FormatException { - printError('"$versionString" could not be parsed as a version.'); - return false; - } - - if (fromPubspec != fromChangeLog) { - printError(''' -${indentation}Versions in CHANGELOG.md and pubspec.yaml do not match. -${indentation}The version in pubspec.yaml is $fromPubspec. -${indentation}The first version listed in CHANGELOG.md is $fromChangeLog. -'''); - return false; - } - - // If NEXT wasn't the first section, it should not exist at all. - if (!hasNextSection) { - final RegExp nextRegex = RegExp(r'^#+\s*NEXT\s*$'); - if (lines.any((String line) => nextRegex.hasMatch(line))) { - printError(badNextErrorMessage); - return false; - } - } - - return true; - } - - Pubspec? _tryParsePubspec(RepositoryPackage package) { - try { - final Pubspec pubspec = package.parsePubspec(); - return pubspec; - } on Exception catch (exception) { - printError('${indentation}Failed to parse `pubspec.yaml`: $exception}'); - return null; - } - } - - /// Checks whether the current breaking change to [package] should be allowed, - /// logging extra information for auditing when allowing unusual cases. - bool _validateBreakingChange(RepositoryPackage package) { - // Only platform interfaces have breaking change restrictions. - if (!package.isPlatformInterface) { - return true; - } - - if (getBoolArg(_ignorePlatformInterfaceBreaks)) { - logWarning( - '${indentation}Allowing breaking change to ${package.displayName} ' - 'due to --$_ignorePlatformInterfaceBreaks'); - return true; - } - - if (_prLabels.contains(_breakingChangeOverrideLabel)) { - logWarning( - '${indentation}Allowing breaking change to ${package.displayName} ' - 'due to the "$_breakingChangeOverrideLabel" label.'); - return true; - } - - return false; - } - - /// Returns the labels associated with this PR, if any, or an empty set - /// if that flag is not provided. - Set _getPRLabels() { - final String labels = getStringArg(_prLabelsArg); - if (labels.isEmpty) { - return {}; - } - return labels.split(',').map((String label) => label.trim()).toSet(); - } - - /// Returns true if the given version transition should be allowed. - bool _shouldAllowVersionChange( - {required Version oldVersion, required Version newVersion}) { - // Get the non-pre-release next version mapping. - final Map allowedNextVersions = - getAllowedNextVersions(oldVersion, newVersion: newVersion); - - if (allowedNextVersions.containsKey(newVersion)) { - return true; - } - // Allow a pre-release version of a version that would be a valid - // transition. - if (newVersion.isPreRelease) { - final Version targetReleaseVersion = newVersion.nextPatch; - if (allowedNextVersions.containsKey(targetReleaseVersion)) { - return true; - } - } - return false; - } - - /// Returns an error string if the changes to this package should have - /// resulted in a version change, or shoud have resulted in a CHANGELOG change - /// but didn't. - /// - /// This should only be called if the version did not change. - Future _checkForMissingChangeError(RepositoryPackage package) async { - // Find the relative path to the current package, as it would appear at the - // beginning of a path reported by getChangedFiles() (which always uses - // Posix paths). - final Directory gitRoot = - packagesDir.fileSystem.directory((await gitDir).path); - final String relativePackagePath = - getRelativePosixPath(package.directory, from: gitRoot); - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: _changedFiles, - relativePackagePath: relativePackagePath, - git: await retrieveVersionFinder()); - - if (!state.hasChanges) { - return null; - } - - if (state.needsVersionChange) { - if (_prLabels.contains(_missingVersionChangeOverrideLabel)) { - logWarning('Ignoring lack of version change due to the ' - '"$_missingVersionChangeOverrideLabel" label.'); - } else { - printError( - 'No version change found, but the change to this package could ' - 'not be verified to be exempt from version changes according to ' - 'repository policy. If this is a false positive, please comment in ' - 'the PR to explain why the PR is exempt, and add (or ask your ' - 'reviewer to add) the "$_missingVersionChangeOverrideLabel" ' - 'label.'); - return 'Missing version change'; - } - } - - if (!state.hasChangelogChange && state.needsChangelogChange) { - if (_prLabels.contains(_missingChangelogChangeOverrideLabel)) { - logWarning('Ignoring lack of CHANGELOG update due to the ' - '"$_missingChangelogChangeOverrideLabel" label.'); - } else { - printError( - 'No CHANGELOG change found. If this PR needs an exemption from ' - 'the standard policy of listing all changes in the CHANGELOG, ' - 'comment in the PR to explain why the PR is exempt, and add (or ' - 'ask your reviewer to add) the ' - '"$_missingChangelogChangeOverrideLabel" label. Otherwise, ' - 'please add a NEXT entry in the CHANGELOG as described in ' - 'the contributing guide.'); - return 'Missing CHANGELOG change'; - } - } - - return null; - } -} diff --git a/script/tool/lib/src/xcode_analyze_command.dart b/script/tool/lib/src/xcode_analyze_command.dart deleted file mode 100644 index a81bf15477af..000000000000 --- a/script/tool/lib/src/xcode_analyze_command.dart +++ /dev/null @@ -1,133 +0,0 @@ -// 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 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/plugin_utils.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; -import 'common/xcode.dart'; - -/// The command to run Xcode's static analyzer on plugins. -class XcodeAnalyzeCommand extends PackageLoopingCommand { - /// Creates an instance of the test command. - XcodeAnalyzeCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : _xcode = Xcode(processRunner: processRunner, log: true), - super(packagesDir, processRunner: processRunner, platform: platform) { - argParser.addFlag(platformIOS, help: 'Analyze iOS'); - argParser.addFlag(platformMacOS, help: 'Analyze macOS'); - argParser.addOption(_minIOSVersionArg, - help: 'Sets the minimum iOS deployment version to use when compiling, ' - 'overriding the default minimum version. This can be used to find ' - 'deprecation warnings that will affect the plugin in the future.'); - argParser.addOption(_minMacOSVersionArg, - help: - 'Sets the minimum macOS deployment version to use when compiling, ' - 'overriding the default minimum version. This can be used to find ' - 'deprecation warnings that will affect the plugin in the future.'); - } - - static const String _minIOSVersionArg = 'ios-min-version'; - static const String _minMacOSVersionArg = 'macos-min-version'; - - final Xcode _xcode; - - @override - final String name = 'xcode-analyze'; - - @override - final String description = - 'Runs Xcode analysis on the iOS and/or macOS example apps.'; - - @override - Future initializeRun() async { - if (!(getBoolArg(platformIOS) || getBoolArg(platformMacOS))) { - printError('At least one platform flag must be provided.'); - throw ToolExit(exitInvalidArguments); - } - } - - @override - Future runForPackage(RepositoryPackage package) async { - final bool testIOS = getBoolArg(platformIOS) && - pluginSupportsPlatform(platformIOS, package, - requiredMode: PlatformSupport.inline); - final bool testMacOS = getBoolArg(platformMacOS) && - pluginSupportsPlatform(platformMacOS, package, - requiredMode: PlatformSupport.inline); - - final bool multiplePlatformsRequested = - getBoolArg(platformIOS) && getBoolArg(platformMacOS); - if (!(testIOS || testMacOS)) { - return PackageResult.skip('Not implemented for target platform(s).'); - } - - final String minIOSVersion = getStringArg(_minIOSVersionArg); - final String minMacOSVersion = getStringArg(_minMacOSVersionArg); - - final List failures = []; - if (testIOS && - !await _analyzePlugin(package, 'iOS', extraFlags: [ - '-destination', - 'generic/platform=iOS Simulator', - if (minIOSVersion.isNotEmpty) - 'IPHONEOS_DEPLOYMENT_TARGET=$minIOSVersion', - ])) { - failures.add('iOS'); - } - if (testMacOS && - !await _analyzePlugin(package, 'macOS', extraFlags: [ - if (minMacOSVersion.isNotEmpty) - 'MACOSX_DEPLOYMENT_TARGET=$minMacOSVersion', - ])) { - failures.add('macOS'); - } - - // Only provide the failing platform in the failure details if testing - // multiple platforms, otherwise it's just noise. - return failures.isEmpty - ? PackageResult.success() - : PackageResult.fail( - multiplePlatformsRequested ? failures : []); - } - - /// Analyzes [plugin] for [platform], returning true if it passed analysis. - Future _analyzePlugin( - RepositoryPackage plugin, - String platform, { - List extraFlags = const [], - }) async { - bool passing = true; - for (final RepositoryPackage example in plugin.getExamples()) { - // Running tests and static analyzer. - final String examplePath = getRelativePosixPath(example.directory, - from: plugin.directory.parent); - print('Running $platform tests and analyzer for $examplePath...'); - final int exitCode = await _xcode.runXcodeBuild( - example.directory, - actions: ['analyze'], - workspace: '${platform.toLowerCase()}/Runner.xcworkspace', - scheme: 'Runner', - configuration: 'Debug', - extraFlags: [ - ...extraFlags, - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - ); - if (exitCode == 0) { - printSuccess('$examplePath ($platform) passed analysis.'); - } else { - printError('$examplePath ($platform) failed analysis.'); - passing = false; - } - } - return passing; - } -} diff --git a/script/tool/pubspec.yaml b/script/tool/pubspec.yaml index 52d23b8f72a3..60884fdeb8ae 100644 --- a/script/tool/pubspec.yaml +++ b/script/tool/pubspec.yaml @@ -2,6 +2,7 @@ name: flutter_plugin_tools description: Productivity utils for flutter/plugins and flutter/packages repository: https://github.com/flutter/plugins/tree/main/script/tool version: 0.13.4+2 +publish_to: none # See README.md dependencies: args: ^2.1.0 diff --git a/script/tool/test/analyze_command_test.dart b/script/tool/test/analyze_command_test.dart deleted file mode 100644 index e6b910960846..000000000000 --- a/script/tool/test/analyze_command_test.dart +++ /dev/null @@ -1,425 +0,0 @@ -// 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 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/analyze_command.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late RecordingProcessRunner processRunner; - late CommandRunner runner; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final AnalyzeCommand analyzeCommand = AnalyzeCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner('analyze_command', 'Test for analyze_command'); - runner.addCommand(analyzeCommand); - }); - - test('analyzes all packages', () async { - final RepositoryPackage package1 = createFakePackage('a', packagesDir); - final RepositoryPackage plugin2 = createFakePlugin('b', packagesDir); - - await runCapturingPrint(runner, ['analyze']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall('flutter', const ['pub', 'get'], package1.path), - ProcessCall('dart', const ['analyze', '--fatal-infos'], - package1.path), - ProcessCall('flutter', const ['pub', 'get'], plugin2.path), - ProcessCall( - 'dart', const ['analyze', '--fatal-infos'], plugin2.path), - ])); - }); - - test('skips flutter pub get for examples', () async { - final RepositoryPackage plugin1 = createFakePlugin('a', packagesDir); - - await runCapturingPrint(runner, ['analyze']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall('flutter', const ['pub', 'get'], plugin1.path), - ProcessCall( - 'dart', const ['analyze', '--fatal-infos'], plugin1.path), - ])); - }); - - test('runs flutter pub get for non-example subpackages', () async { - final RepositoryPackage mainPackage = createFakePackage('a', packagesDir); - final Directory otherPackagesDir = - mainPackage.directory.childDirectory('other_packages'); - final RepositoryPackage subpackage1 = - createFakePackage('subpackage1', otherPackagesDir); - final RepositoryPackage subpackage2 = - createFakePackage('subpackage2', otherPackagesDir); - - await runCapturingPrint(runner, ['analyze']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'flutter', const ['pub', 'get'], mainPackage.path), - ProcessCall( - 'flutter', const ['pub', 'get'], subpackage1.path), - ProcessCall( - 'flutter', const ['pub', 'get'], subpackage2.path), - ProcessCall('dart', const ['analyze', '--fatal-infos'], - mainPackage.path), - ])); - }); - - test('passes lib/ directory with --lib-only', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - await runCapturingPrint(runner, ['analyze', '--lib-only']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall('flutter', const ['pub', 'get'], package.path), - ProcessCall('dart', const ['analyze', '--fatal-infos', 'lib'], - package.path), - ])); - }); - - test('skips when missing lib/ directory with --lib-only', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - package.libDirectory.deleteSync(); - - final List output = - await runCapturingPrint(runner, ['analyze', '--lib-only']); - - expect(processRunner.recordedCalls, isEmpty); - expect( - output, - containsAllInOrder([ - contains('SKIPPING: No lib/ directory'), - ]), - ); - }); - - test( - 'does not run flutter pub get for non-example subpackages with --lib-only', - () async { - final RepositoryPackage mainPackage = createFakePackage('a', packagesDir); - final Directory otherPackagesDir = - mainPackage.directory.childDirectory('other_packages'); - createFakePackage('subpackage1', otherPackagesDir); - createFakePackage('subpackage2', otherPackagesDir); - - await runCapturingPrint(runner, ['analyze', '--lib-only']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'flutter', const ['pub', 'get'], mainPackage.path), - ProcessCall('dart', const ['analyze', '--fatal-infos', 'lib'], - mainPackage.path), - ])); - }); - - test("don't elide a non-contained example package", () async { - final RepositoryPackage plugin1 = createFakePlugin('a', packagesDir); - final RepositoryPackage plugin2 = createFakePlugin('example', packagesDir); - - await runCapturingPrint(runner, ['analyze']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall('flutter', const ['pub', 'get'], plugin1.path), - ProcessCall( - 'dart', const ['analyze', '--fatal-infos'], plugin1.path), - ProcessCall('flutter', const ['pub', 'get'], plugin2.path), - ProcessCall( - 'dart', const ['analyze', '--fatal-infos'], plugin2.path), - ])); - }); - - test('uses a separate analysis sdk', () async { - final RepositoryPackage plugin = createFakePlugin('a', packagesDir); - - await runCapturingPrint( - runner, ['analyze', '--analysis-sdk', 'foo/bar/baz']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'flutter', - const ['pub', 'get'], - plugin.path, - ), - ProcessCall( - 'foo/bar/baz/bin/dart', - const ['analyze', '--fatal-infos'], - plugin.path, - ), - ]), - ); - }); - - test('downgrades first when requested', () async { - final RepositoryPackage plugin = createFakePlugin('a', packagesDir); - - await runCapturingPrint(runner, ['analyze', '--downgrade']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'flutter', - const ['pub', 'downgrade'], - plugin.path, - ), - ProcessCall( - 'flutter', - const ['pub', 'get'], - plugin.path, - ), - ProcessCall( - 'dart', - const ['analyze', '--fatal-infos'], - plugin.path, - ), - ]), - ); - }); - - group('verifies analysis settings', () { - test('fails analysis_options.yaml', () async { - createFakePlugin('foo', packagesDir, - extraFiles: ['analysis_options.yaml']); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['analyze'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Found an extra analysis_options.yaml at /packages/foo/analysis_options.yaml'), - contains(' foo:\n' - ' Unexpected local analysis options'), - ]), - ); - }); - - test('fails .analysis_options', () async { - createFakePlugin('foo', packagesDir, - extraFiles: ['.analysis_options']); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['analyze'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Found an extra analysis_options.yaml at /packages/foo/.analysis_options'), - contains(' foo:\n' - ' Unexpected local analysis options'), - ]), - ); - }); - - test('takes an allow list', () async { - final RepositoryPackage plugin = createFakePlugin('foo', packagesDir, - extraFiles: ['analysis_options.yaml']); - - await runCapturingPrint( - runner, ['analyze', '--custom-analysis', 'foo']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall('flutter', const ['pub', 'get'], plugin.path), - ProcessCall('dart', const ['analyze', '--fatal-infos'], - plugin.path), - ])); - }); - - test('takes an allow config file', () async { - final RepositoryPackage plugin = createFakePlugin('foo', packagesDir, - extraFiles: ['analysis_options.yaml']); - final File allowFile = packagesDir.childFile('custom.yaml'); - allowFile.writeAsStringSync('- foo'); - - await runCapturingPrint( - runner, ['analyze', '--custom-analysis', allowFile.path]); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall('flutter', const ['pub', 'get'], plugin.path), - ProcessCall('dart', const ['analyze', '--fatal-infos'], - plugin.path), - ])); - }); - - test('allows an empty config file', () async { - createFakePlugin('foo', packagesDir, - extraFiles: ['analysis_options.yaml']); - final File allowFile = packagesDir.childFile('custom.yaml'); - allowFile.createSync(); - - await expectLater( - () => runCapturingPrint( - runner, ['analyze', '--custom-analysis', allowFile.path]), - throwsA(isA())); - }); - - // See: https://github.com/flutter/flutter/issues/78994 - test('takes an empty allow list', () async { - createFakePlugin('foo', packagesDir, - extraFiles: ['analysis_options.yaml']); - - await expectLater( - () => runCapturingPrint( - runner, ['analyze', '--custom-analysis', '']), - throwsA(isA())); - }); - }); - - test('fails if "pub get" fails', () async { - createFakePlugin('foo', packagesDir); - - processRunner.mockProcessesForExecutable['flutter'] = [ - MockProcess(exitCode: 1) // flutter pub get - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['analyze'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to get dependencies'), - ]), - ); - }); - - test('fails if "pub downgrade" fails', () async { - createFakePlugin('foo', packagesDir); - - processRunner.mockProcessesForExecutable['flutter'] = [ - MockProcess(exitCode: 1) // flutter pub downgrade - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['analyze', '--downgrade'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to downgrade dependencies'), - ]), - ); - }); - - test('fails if "analyze" fails', () async { - createFakePlugin('foo', packagesDir); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(exitCode: 1) // dart analyze - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['analyze'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains(' foo'), - ]), - ); - }); - - // Ensure that the command used to analyze flutter/plugins in the Dart repo: - // https://github.com/dart-lang/sdk/blob/main/tools/bots/flutter/analyze_flutter_plugins.sh - // continues to work. - // - // DO NOT remove or modify this test without a coordination plan in place to - // modify the script above, as it is run from source, but out-of-repo. - // Contact stuartmorgan or devoncarew for assistance. - test('Dart repo analyze command works', () async { - final RepositoryPackage plugin = createFakePlugin('foo', packagesDir, - extraFiles: ['analysis_options.yaml']); - final File allowFile = packagesDir.childFile('custom.yaml'); - allowFile.writeAsStringSync('- foo'); - - await runCapturingPrint(runner, [ - // DO NOT change this call; see comment above. - 'analyze', - '--analysis-sdk', - 'foo/bar/baz', - '--custom-analysis', - allowFile.path - ]); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'flutter', - const ['pub', 'get'], - plugin.path, - ), - ProcessCall( - 'foo/bar/baz/bin/dart', - const ['analyze', '--fatal-infos'], - plugin.path, - ), - ]), - ); - }); -} diff --git a/script/tool/test/build_examples_command_test.dart b/script/tool/test/build_examples_command_test.dart deleted file mode 100644 index a819e7a12674..000000000000 --- a/script/tool/test/build_examples_command_test.dart +++ /dev/null @@ -1,634 +0,0 @@ -// 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 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/build_examples_command.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - group('build-example', () { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late CommandRunner runner; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final BuildExamplesCommand command = BuildExamplesCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner( - 'build_examples_command', 'Test for build_example_command'); - runner.addCommand(command); - }); - - test('fails if no plaform flags are passed', () async { - Error? commandError; - final List output = await runCapturingPrint( - runner, ['build-examples'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('At least one platform must be provided'), - ])); - }); - - test('fails if building fails', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline), - }); - - processRunner - .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] = - [ - MockProcess(exitCode: 1) // flutter pub get - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['build-examples', '--ios'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains(' plugin:\n' - ' plugin/example (iOS)'), - ])); - }); - - test('fails if a plugin has no examples', () async { - createFakePlugin('plugin', packagesDir, - examples: [], - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline) - }); - - processRunner - .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] = - [ - MockProcess(exitCode: 1) // flutter pub get - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['build-examples', '--ios'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains(' plugin:\n' - ' No examples found'), - ])); - }); - - test('building for iOS when plugin is not set up for iOS results in no-op', - () async { - mockPlatform.isMacOS = true; - createFakePlugin('plugin', packagesDir); - - final List output = - await runCapturingPrint(runner, ['build-examples', '--ios']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('iOS is not supported by this plugin'), - ]), - ); - - // Output should be empty since running build-examples --macos with no macos - // implementation is a no-op. - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('building for iOS', () async { - mockPlatform.isMacOS = true; - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, - ['build-examples', '--ios', '--enable-experiment=exp1']); - - expect( - output, - containsAllInOrder([ - '\nBUILDING plugin/example for iOS', - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'build', - 'ios', - '--no-codesign', - '--enable-experiment=exp1' - ], - pluginExampleDirectory.path), - ])); - }); - - test( - 'building for Linux when plugin is not set up for Linux results in no-op', - () async { - mockPlatform.isLinux = true; - createFakePlugin('plugin', packagesDir); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--linux']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Linux is not supported by this plugin'), - ]), - ); - - // Output should be empty since running build-examples --linux with no - // Linux implementation is a no-op. - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('building for Linux', () async { - mockPlatform.isLinux = true; - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--linux']); - - expect( - output, - containsAllInOrder([ - '\nBUILDING plugin/example for Linux', - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['build', 'linux'], pluginExampleDirectory.path), - ])); - }); - - test('building for macOS with no implementation results in no-op', - () async { - mockPlatform.isMacOS = true; - createFakePlugin('plugin', packagesDir); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--macos']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('macOS is not supported by this plugin'), - ]), - ); - - // Output should be empty since running build-examples --macos with no macos - // implementation is a no-op. - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('building for macOS', () async { - mockPlatform.isMacOS = true; - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--macos']); - - expect( - output, - containsAllInOrder([ - '\nBUILDING plugin/example for macOS', - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['build', 'macos'], pluginExampleDirectory.path), - ])); - }); - - test('building for web with no implementation results in no-op', () async { - createFakePlugin('plugin', packagesDir); - - final List output = - await runCapturingPrint(runner, ['build-examples', '--web']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('web is not supported by this plugin'), - ]), - ); - - // Output should be empty since running build-examples --macos with no macos - // implementation is a no-op. - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('building for web', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformWeb: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = - await runCapturingPrint(runner, ['build-examples', '--web']); - - expect( - output, - containsAllInOrder([ - '\nBUILDING plugin/example for web', - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['build', 'web'], pluginExampleDirectory.path), - ])); - }); - - test( - 'building for Windows when plugin is not set up for Windows results in no-op', - () async { - mockPlatform.isWindows = true; - createFakePlugin('plugin', packagesDir); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--windows']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Windows is not supported by this plugin'), - ]), - ); - - // Output should be empty since running build-examples --windows with no - // Windows implementation is a no-op. - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('building for Windows', () async { - mockPlatform.isWindows = true; - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformWindows: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--windows']); - - expect( - output, - containsAllInOrder([ - '\nBUILDING plugin/example for Windows', - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const ['build', 'windows'], - pluginExampleDirectory.path), - ])); - }); - - test( - 'building for Android when plugin is not set up for Android results in no-op', - () async { - createFakePlugin('plugin', packagesDir); - - final List output = - await runCapturingPrint(runner, ['build-examples', '--apk']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Android is not supported by this plugin'), - ]), - ); - - // Output should be empty since running build-examples --macos with no macos - // implementation is a no-op. - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('building for Android', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, [ - 'build-examples', - '--apk', - ]); - - expect( - output, - containsAllInOrder([ - '\nBUILDING plugin/example for Android (apk)', - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['build', 'apk'], pluginExampleDirectory.path), - ])); - }); - - test('enable-experiment flag for Android', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - await runCapturingPrint(runner, - ['build-examples', '--apk', '--enable-experiment=exp1']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const ['build', 'apk', '--enable-experiment=exp1'], - pluginExampleDirectory.path), - ])); - }); - - test('enable-experiment flag for ios', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - await runCapturingPrint(runner, - ['build-examples', '--ios', '--enable-experiment=exp1']); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'build', - 'ios', - '--no-codesign', - '--enable-experiment=exp1' - ], - pluginExampleDirectory.path), - ])); - }); - - test('logs skipped platforms', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - }); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--apk', '--ios', '--macos']); - - expect( - output, - containsAllInOrder([ - contains('Skipping unsupported platform(s): iOS, macOS'), - ]), - ); - }); - - group('packages', () { - test('builds when requested platform is supported by example', () async { - final RepositoryPackage package = createFakePackage( - 'package', packagesDir, isFlutter: true, extraFiles: [ - 'example/ios/Runner.xcodeproj/project.pbxproj' - ]); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--ios']); - - expect( - output, - containsAllInOrder([ - contains('Running for package'), - contains('BUILDING package/example for iOS'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'build', - 'ios', - '--no-codesign', - ], - getExampleDir(package).path), - ])); - }); - - test('skips non-Flutter examples', () async { - createFakePackage('package', packagesDir); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--ios']); - - expect( - output, - containsAllInOrder([ - contains('Running for package'), - contains('No examples found supporting requested platform(s).'), - ]), - ); - - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('skips when there is no example', () async { - createFakePackage('package', packagesDir, - isFlutter: true, examples: []); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--ios']); - - expect( - output, - containsAllInOrder([ - contains('Running for package'), - contains('No examples found supporting requested platform(s).'), - ]), - ); - - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('skip when example does not support requested platform', () async { - createFakePackage('package', packagesDir, - isFlutter: true, - extraFiles: ['example/linux/CMakeLists.txt']); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--ios']); - - expect( - output, - containsAllInOrder([ - contains('Running for package'), - contains('Skipping iOS for package/example; not supported.'), - contains('No examples found supporting requested platform(s).'), - ]), - ); - - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('logs skipped platforms when only some are supported', () async { - final RepositoryPackage package = createFakePackage( - 'package', packagesDir, - isFlutter: true, - extraFiles: ['example/linux/CMakeLists.txt']); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--apk', '--linux']); - - expect( - output, - containsAllInOrder([ - contains('Running for package'), - contains('Building for: Android, Linux'), - contains('Skipping Android for package/example; not supported.'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const ['build', 'linux'], - getExampleDir(package).path), - ])); - }); - }); - - test('The .pluginToolsConfig.yaml file', () async { - mockPlatform.isLinux = true; - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline), - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final File pluginExampleConfigFile = - pluginExampleDirectory.childFile('.pluginToolsConfig.yaml'); - pluginExampleConfigFile - .writeAsStringSync('buildFlags:\n global:\n - "test argument"'); - - final List output = [ - ...await runCapturingPrint( - runner, ['build-examples', '--linux']), - ...await runCapturingPrint( - runner, ['build-examples', '--macos']), - ]; - - expect( - output, - containsAllInOrder([ - '\nBUILDING plugin/example for Linux', - '\nBUILDING plugin/example for macOS', - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const ['build', 'linux', 'test argument'], - pluginExampleDirectory.path), - ProcessCall( - getFlutterCommand(mockPlatform), - const ['build', 'macos', 'test argument'], - pluginExampleDirectory.path), - ])); - }); - }); -} diff --git a/script/tool/test/common/file_utils_test.dart b/script/tool/test/common/file_utils_test.dart deleted file mode 100644 index 79b804e31ea5..000000000000 --- a/script/tool/test/common/file_utils_test.dart +++ /dev/null @@ -1,32 +0,0 @@ -// 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 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/file_utils.dart'; -import 'package:test/test.dart'; - -void main() { - test('works on Posix', () async { - final FileSystem fileSystem = - MemoryFileSystem(); - - final Directory base = fileSystem.directory('/').childDirectory('base'); - final File file = - childFileWithSubcomponents(base, ['foo', 'bar', 'baz.txt']); - - expect(file.absolute.path, '/base/foo/bar/baz.txt'); - }); - - test('works on Windows', () async { - final FileSystem fileSystem = - MemoryFileSystem(style: FileSystemStyle.windows); - - final Directory base = fileSystem.directory(r'C:\').childDirectory('base'); - final File file = - childFileWithSubcomponents(base, ['foo', 'bar', 'baz.txt']); - - expect(file.absolute.path, r'C:\base\foo\bar\baz.txt'); - }); -} diff --git a/script/tool/test/common/git_version_finder_test.dart b/script/tool/test/common/git_version_finder_test.dart deleted file mode 100644 index 538b72a90021..000000000000 --- a/script/tool/test/common/git_version_finder_test.dart +++ /dev/null @@ -1,107 +0,0 @@ -// 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 'dart:io'; - -import 'package:flutter_plugin_tools/src/common/git_version_finder.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -import 'package_command_test.mocks.dart'; - -void main() { - late List?> gitDirCommands; - late String gitDiffResponse; - late MockGitDir gitDir; - String mergeBaseResponse = ''; - - setUp(() { - gitDirCommands = ?>[]; - gitDiffResponse = ''; - gitDir = MockGitDir(); - when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) - .thenAnswer((Invocation invocation) { - final List arguments = - invocation.positionalArguments[0]! as List; - gitDirCommands.add(arguments); - final MockProcessResult mockProcessResult = MockProcessResult(); - if (arguments[0] == 'diff') { - when(mockProcessResult.stdout as String?) - .thenReturn(gitDiffResponse); - } else if (arguments[0] == 'merge-base') { - when(mockProcessResult.stdout as String?) - .thenReturn(mergeBaseResponse); - } - return Future.value(mockProcessResult); - }); - }); - - test('No git diff should result no files changed', () async { - final GitVersionFinder finder = GitVersionFinder(gitDir, 'some base sha'); - final List changedFiles = await finder.getChangedFiles(); - - expect(changedFiles, isEmpty); - }); - - test('get correct files changed based on git diff', () async { - gitDiffResponse = ''' -file1/file1.cc -file2/file2.cc -'''; - final GitVersionFinder finder = GitVersionFinder(gitDir, 'some base sha'); - final List changedFiles = await finder.getChangedFiles(); - - expect(changedFiles, equals(['file1/file1.cc', 'file2/file2.cc'])); - }); - - test('get correct pubspec change based on git diff', () async { - gitDiffResponse = ''' -file1/pubspec.yaml -file2/file2.cc -'''; - final GitVersionFinder finder = GitVersionFinder(gitDir, 'some base sha'); - final List changedFiles = await finder.getChangedPubSpecs(); - - expect(changedFiles, equals(['file1/pubspec.yaml'])); - }); - - test('use correct base sha if not specified', () async { - mergeBaseResponse = 'shaqwiueroaaidf12312jnadf123nd'; - gitDiffResponse = ''' -file1/pubspec.yaml -file2/file2.cc -'''; - - final GitVersionFinder finder = GitVersionFinder(gitDir, null); - await finder.getChangedFiles(); - verify(gitDir.runCommand( - ['diff', '--name-only', mergeBaseResponse, 'HEAD'])); - }); - - test('use correct base sha if specified', () async { - const String customBaseSha = 'aklsjdcaskf12312'; - gitDiffResponse = ''' -file1/pubspec.yaml -file2/file2.cc -'''; - final GitVersionFinder finder = GitVersionFinder(gitDir, customBaseSha); - await finder.getChangedFiles(); - verify(gitDir - .runCommand(['diff', '--name-only', customBaseSha, 'HEAD'])); - }); - - test('include uncommitted files if requested', () async { - const String customBaseSha = 'aklsjdcaskf12312'; - gitDiffResponse = ''' -file1/pubspec.yaml -file2/file2.cc -'''; - final GitVersionFinder finder = GitVersionFinder(gitDir, customBaseSha); - await finder.getChangedFiles(includeUncommitted: true); - // The call should not have HEAD as a final argument like the default diff. - verify(gitDir.runCommand(['diff', '--name-only', customBaseSha])); - }); -} - -class MockProcessResult extends Mock implements ProcessResult {} diff --git a/script/tool/test/common/gradle_test.dart b/script/tool/test/common/gradle_test.dart deleted file mode 100644 index 8df4a65b93a5..000000000000 --- a/script/tool/test/common/gradle_test.dart +++ /dev/null @@ -1,188 +0,0 @@ -// 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 'dart:io' as io; - -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/gradle.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; -import '../util.dart'; - -void main() { - late FileSystem fileSystem; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - processRunner = RecordingProcessRunner(); - }); - - group('isConfigured', () { - test('reports true when configured on Windows', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', fileSystem.directory('/'), - extraFiles: ['android/gradlew.bat']); - final GradleProject project = GradleProject( - plugin, - processRunner: processRunner, - platform: MockPlatform(isWindows: true), - ); - - expect(project.isConfigured(), true); - }); - - test('reports true when configured on non-Windows', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', fileSystem.directory('/'), - extraFiles: ['android/gradlew']); - final GradleProject project = GradleProject( - plugin, - processRunner: processRunner, - platform: MockPlatform(isMacOS: true), - ); - - expect(project.isConfigured(), true); - }); - - test('reports false when not configured on Windows', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', fileSystem.directory('/'), - extraFiles: ['android/foo']); - final GradleProject project = GradleProject( - plugin, - processRunner: processRunner, - platform: MockPlatform(isWindows: true), - ); - - expect(project.isConfigured(), false); - }); - - test('reports true when configured on non-Windows', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', fileSystem.directory('/'), - extraFiles: ['android/foo']); - final GradleProject project = GradleProject( - plugin, - processRunner: processRunner, - platform: MockPlatform(isMacOS: true), - ); - - expect(project.isConfigured(), false); - }); - }); - - group('runCommand', () { - test('runs without arguments', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', fileSystem.directory('/'), - extraFiles: ['android/gradlew']); - final GradleProject project = GradleProject( - plugin, - processRunner: processRunner, - platform: MockPlatform(isMacOS: true), - ); - - final int exitCode = await project.runCommand('foo'); - - expect(exitCode, 0); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - plugin - .platformDirectory(FlutterPlatform.android) - .childFile('gradlew') - .path, - const [ - 'foo', - ], - plugin.platformDirectory(FlutterPlatform.android).path), - ])); - }); - - test('runs with arguments', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', fileSystem.directory('/'), - extraFiles: ['android/gradlew']); - final GradleProject project = GradleProject( - plugin, - processRunner: processRunner, - platform: MockPlatform(isMacOS: true), - ); - - final int exitCode = await project.runCommand( - 'foo', - arguments: ['--bar', '--baz'], - ); - - expect(exitCode, 0); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - plugin - .platformDirectory(FlutterPlatform.android) - .childFile('gradlew') - .path, - const [ - 'foo', - '--bar', - '--baz', - ], - plugin.platformDirectory(FlutterPlatform.android).path), - ])); - }); - - test('runs with the correct wrapper on Windows', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', fileSystem.directory('/'), - extraFiles: ['android/gradlew.bat']); - final GradleProject project = GradleProject( - plugin, - processRunner: processRunner, - platform: MockPlatform(isWindows: true), - ); - - final int exitCode = await project.runCommand('foo'); - - expect(exitCode, 0); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - plugin - .platformDirectory(FlutterPlatform.android) - .childFile('gradlew.bat') - .path, - const [ - 'foo', - ], - plugin.platformDirectory(FlutterPlatform.android).path), - ])); - }); - - test('returns error codes', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', fileSystem.directory('/'), - extraFiles: ['android/gradlew.bat']); - final GradleProject project = GradleProject( - plugin, - processRunner: processRunner, - platform: MockPlatform(isWindows: true), - ); - - processRunner.mockProcessesForExecutable[project.gradleWrapper.path] = - [ - MockProcess(exitCode: 1), - ]; - - final int exitCode = await project.runCommand('foo'); - - expect(exitCode, 1); - }); - }); -} diff --git a/script/tool/test/common/package_command_test.dart b/script/tool/test/common/package_command_test.dart deleted file mode 100644 index 3620f8fd63a9..000000000000 --- a/script/tool/test/common/package_command_test.dart +++ /dev/null @@ -1,1151 +0,0 @@ -// 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 'dart:io'; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/package_command.dart'; -import 'package:flutter_plugin_tools/src/common/process_runner.dart'; -import 'package:git/git.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; -import 'package:platform/platform.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; -import '../util.dart'; -import 'package_command_test.mocks.dart'; - -@GenerateMocks([GitDir]) -void main() { - late RecordingProcessRunner processRunner; - late SamplePackageCommand command; - late CommandRunner runner; - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late Directory thirdPartyPackagesDir; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - thirdPartyPackagesDir = packagesDir.parent - .childDirectory('third_party') - .childDirectory('packages'); - - final MockGitDir gitDir = MockGitDir(); - when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) - .thenAnswer((Invocation invocation) { - final List arguments = - invocation.positionalArguments[0]! as List; - // Attach the first argument to the command to make targeting the mock - // results easier. - final String gitCommand = arguments.removeAt(0); - return processRunner.run('git-$gitCommand', arguments); - }); - processRunner = RecordingProcessRunner(); - command = SamplePackageCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - gitDir: gitDir, - ); - runner = - CommandRunner('common_command', 'Test for common functionality'); - runner.addCommand(command); - }); - - group('plugin iteration', () { - test('all plugins from file system', () async { - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - await runCapturingPrint(runner, ['sample']); - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path])); - }); - - test('includes both plugins and packages', () async { - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - final RepositoryPackage package3 = - createFakePackage('package3', packagesDir); - final RepositoryPackage package4 = - createFakePackage('package4', packagesDir); - await runCapturingPrint(runner, ['sample']); - expect( - command.plugins, - unorderedEquals([ - plugin1.path, - plugin2.path, - package3.path, - package4.path, - ])); - }); - - test('includes packages without source', () async { - final RepositoryPackage package = - createFakePackage('package', packagesDir); - package.libDirectory.deleteSync(recursive: true); - - await runCapturingPrint(runner, ['sample']); - expect( - command.plugins, - unorderedEquals([ - package.path, - ])); - }); - - test('all plugins includes third_party/packages', () async { - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - final RepositoryPackage plugin3 = - createFakePlugin('plugin3', thirdPartyPackagesDir); - await runCapturingPrint(runner, ['sample']); - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path, plugin3.path])); - }); - - test('--packages limits packages', () async { - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir); - createFakePackage('package3', packagesDir); - final RepositoryPackage package4 = - createFakePackage('package4', packagesDir); - await runCapturingPrint( - runner, ['sample', '--packages=plugin1,package4']); - expect( - command.plugins, - unorderedEquals([ - plugin1.path, - package4.path, - ])); - }); - - test('--plugins acts as an alias to --packages', () async { - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir); - createFakePackage('package3', packagesDir); - final RepositoryPackage package4 = - createFakePackage('package4', packagesDir); - await runCapturingPrint( - runner, ['sample', '--plugins=plugin1,package4']); - expect( - command.plugins, - unorderedEquals([ - plugin1.path, - package4.path, - ])); - }); - - test('exclude packages when packages flag is specified', () async { - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - await runCapturingPrint(runner, [ - 'sample', - '--packages=plugin1,plugin2', - '--exclude=plugin1' - ]); - expect(command.plugins, unorderedEquals([plugin2.path])); - }); - - test("exclude packages when packages flag isn't specified", () async { - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir); - await runCapturingPrint( - runner, ['sample', '--exclude=plugin1,plugin2']); - expect(command.plugins, unorderedEquals([])); - }); - - test('exclude federated plugins when packages flag is specified', () async { - createFakePlugin('plugin1', packagesDir.childDirectory('federated')); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - await runCapturingPrint(runner, [ - 'sample', - '--packages=federated/plugin1,plugin2', - '--exclude=federated/plugin1' - ]); - expect(command.plugins, unorderedEquals([plugin2.path])); - }); - - test('exclude entire federated plugins when packages flag is specified', - () async { - createFakePlugin('plugin1', packagesDir.childDirectory('federated')); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - await runCapturingPrint(runner, [ - 'sample', - '--packages=federated/plugin1,plugin2', - '--exclude=federated' - ]); - expect(command.plugins, unorderedEquals([plugin2.path])); - }); - - test('exclude accepts config files', () async { - createFakePlugin('plugin1', packagesDir); - final File configFile = packagesDir.childFile('exclude.yaml'); - configFile.writeAsStringSync('- plugin1'); - - await runCapturingPrint(runner, [ - 'sample', - '--packages=plugin1', - '--exclude=${configFile.path}' - ]); - expect(command.plugins, unorderedEquals([])); - }); - - test( - 'explicitly specifying the plugin (group) name of a federated plugin ' - 'should include all plugins in the group', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin1/plugin1/plugin1.dart -'''), - ]; - final Directory pluginGroup = packagesDir.childDirectory('plugin1'); - final RepositoryPackage appFacingPackage = - createFakePlugin('plugin1', pluginGroup); - final RepositoryPackage platformInterfacePackage = - createFakePlugin('plugin1_platform_interface', pluginGroup); - final RepositoryPackage implementationPackage = - createFakePlugin('plugin1_web', pluginGroup); - - await runCapturingPrint( - runner, ['sample', '--base-sha=main', '--packages=plugin1']); - - expect( - command.plugins, - unorderedEquals([ - appFacingPackage.path, - platformInterfacePackage.path, - implementationPackage.path - ])); - }); - - test( - 'specifying the app-facing package of a federated plugin using its ' - 'fully qualified name should include only that package', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin1/plugin1/plugin1.dart -'''), - ]; - final Directory pluginGroup = packagesDir.childDirectory('plugin1'); - final RepositoryPackage appFacingPackage = - createFakePlugin('plugin1', pluginGroup); - createFakePlugin('plugin1_platform_interface', pluginGroup); - createFakePlugin('plugin1_web', pluginGroup); - - await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--packages=plugin1/plugin1']); - - expect(command.plugins, unorderedEquals([appFacingPackage.path])); - }); - - test( - 'specifying a package of a federated plugin by its name should ' - 'include only that package', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin1/plugin1/plugin1.dart -'''), - ]; - final Directory pluginGroup = packagesDir.childDirectory('plugin1'); - - createFakePlugin('plugin1', pluginGroup); - final RepositoryPackage platformInterfacePackage = - createFakePlugin('plugin1_platform_interface', pluginGroup); - createFakePlugin('plugin1_web', pluginGroup); - - await runCapturingPrint(runner, [ - 'sample', - '--base-sha=main', - '--packages=plugin1_platform_interface' - ]); - - expect(command.plugins, - unorderedEquals([platformInterfacePackage.path])); - }); - - test('returns subpackages after the enclosing package', () async { - final SamplePackageCommand localCommand = SamplePackageCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - gitDir: MockGitDir(), - includeSubpackages: true, - ); - final CommandRunner localRunner = - CommandRunner('common_command', 'subpackage testing'); - localRunner.addCommand(localCommand); - - final RepositoryPackage package = - createFakePackage('apackage', packagesDir); - - await runCapturingPrint(localRunner, ['sample']); - expect( - localCommand.plugins, - containsAllInOrder([ - package.path, - getExampleDir(package).path, - ])); - }); - - group('conflicting package selection', () { - test('does not allow --packages with --run-on-changed-packages', - () async { - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'sample', - '--run-on-changed-packages', - '--packages=plugin1', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Only one of --packages, --run-on-changed-packages, or ' - '--packages-for-branch can be provided.') - ])); - }); - - test('does not allow --packages with --packages-for-branch', () async { - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'sample', - '--packages-for-branch', - '--packages=plugin1', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Only one of --packages, --run-on-changed-packages, or ' - '--packages-for-branch can be provided.') - ])); - }); - - test( - 'does not allow --run-on-changed-packages with --packages-for-branch', - () async { - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'sample', - '--packages-for-branch', - '--packages=plugin1', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Only one of --packages, --run-on-changed-packages, or ' - '--packages-for-branch can be provided.') - ])); - }); - }); - - group('test run-on-changed-packages', () { - test('all plugins should be tested if there are no changes.', () async { - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path])); - }); - - test( - 'all plugins should be tested if there are no plugin related changes.', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: 'AUTHORS'), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path])); - }); - - test('all plugins should be tested if .cirrus.yml changes.', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -.cirrus.yml -packages/plugin1/CHANGELOG -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - - final List output = await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path])); - expect( - output, - containsAllInOrder([ - contains('Running for all packages, since a file has changed ' - 'that could affect the entire repository.') - ])); - }); - - test('all plugins should be tested if .ci.yaml changes', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -.ci.yaml -packages/plugin1/CHANGELOG -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - final List output = await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path])); - expect( - output, - containsAllInOrder([ - contains('Running for all packages, since a file has changed ' - 'that could affect the entire repository.') - ])); - }); - - test('all plugins should be tested if anything in .ci/ changes', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -.ci/Dockerfile -packages/plugin1/CHANGELOG -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - final List output = await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path])); - expect( - output, - containsAllInOrder([ - contains('Running for all packages, since a file has changed ' - 'that could affect the entire repository.') - ])); - }); - - test('all plugins should be tested if anything in script/ changes.', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -script/tool_runner.sh -packages/plugin1/CHANGELOG -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - final List output = await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path])); - expect( - output, - containsAllInOrder([ - contains('Running for all packages, since a file has changed ' - 'that could affect the entire repository.') - ])); - }); - - test('all plugins should be tested if the root analysis options change.', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -analysis_options.yaml -packages/plugin1/CHANGELOG -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - final List output = await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path])); - expect( - output, - containsAllInOrder([ - contains('Running for all packages, since a file has changed ' - 'that could affect the entire repository.') - ])); - }); - - test('all plugins should be tested if formatting options change.', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -.clang-format -packages/plugin1/CHANGELOG -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - final List output = await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path])); - expect( - output, - containsAllInOrder([ - contains('Running for all packages, since a file has changed ' - 'that could affect the entire repository.') - ])); - }); - - test('Only changed plugin should be tested.', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: 'packages/plugin1/plugin1.dart'), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir); - final List output = await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect( - output, - containsAllInOrder([ - contains( - 'Running for all packages that have diffs relative to "main"'), - ])); - - expect(command.plugins, unorderedEquals([plugin1.path])); - }); - - test('multiple files in one plugin should also test the plugin', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin1/plugin1.dart -packages/plugin1/ios/plugin1.m -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir); - await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, unorderedEquals([plugin1.path])); - }); - - test('multiple plugins changed should test all the changed plugins', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin1/plugin1.dart -packages/plugin2/ios/plugin2.m -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - createFakePlugin('plugin3', packagesDir); - await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path])); - }); - - test( - 'multiple plugins inside the same plugin group changed should output the plugin group name', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin1/plugin1/plugin1.dart -packages/plugin1/plugin1_platform_interface/plugin1_platform_interface.dart -packages/plugin1/plugin1_web/plugin1_web.dart -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir.childDirectory('plugin1')); - createFakePlugin('plugin2', packagesDir); - createFakePlugin('plugin3', packagesDir); - await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, unorderedEquals([plugin1.path])); - }); - - test( - 'changing one plugin in a federated group should only include that plugin', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin1/plugin1/plugin1.dart -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir.childDirectory('plugin1')); - createFakePlugin('plugin1_platform_interface', - packagesDir.childDirectory('plugin1')); - createFakePlugin('plugin1_web', packagesDir.childDirectory('plugin1')); - await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, unorderedEquals([plugin1.path])); - }); - - test('--exclude flag works with --run-on-changed-packages', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin1/plugin1.dart -packages/plugin2/ios/plugin2.m -packages/plugin3/plugin3.dart -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir.childDirectory('plugin1')); - createFakePlugin('plugin2', packagesDir); - createFakePlugin('plugin3', packagesDir); - await runCapturingPrint(runner, [ - 'sample', - '--exclude=plugin2,plugin3', - '--base-sha=main', - '--run-on-changed-packages' - ]); - - expect(command.plugins, unorderedEquals([plugin1.path])); - }); - }); - - group('test run-on-dirty-packages', () { - test('no packages should be tested if there are no changes.', () async { - createFakePackage('a_package', packagesDir); - await runCapturingPrint( - runner, ['sample', '--run-on-dirty-packages']); - - expect(command.plugins, unorderedEquals([])); - }); - - test( - 'no packages should be tested if there are no plugin related changes.', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: 'AUTHORS'), - ]; - createFakePackage('a_package', packagesDir); - await runCapturingPrint( - runner, ['sample', '--run-on-dirty-packages']); - - expect(command.plugins, unorderedEquals([])); - }); - - test('no packages should be tested even if special repo files change.', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -.cirrus.yml -.ci.yaml -.ci/Dockerfile -.clang-format -analysis_options.yaml -script/tool_runner.sh -'''), - ]; - createFakePackage('a_package', packagesDir); - await runCapturingPrint( - runner, ['sample', '--run-on-dirty-packages']); - - expect(command.plugins, unorderedEquals([])); - }); - - test('Only changed packages should be tested.', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: 'packages/a_package/lib/a_package.dart'), - ]; - final RepositoryPackage packageA = - createFakePackage('a_package', packagesDir); - createFakePlugin('b_package', packagesDir); - final List output = await runCapturingPrint( - runner, ['sample', '--run-on-dirty-packages']); - - expect( - output, - containsAllInOrder([ - contains( - 'Running for all packages that have uncommitted changes'), - ])); - - expect(command.plugins, unorderedEquals([packageA.path])); - }); - - test('multiple packages changed should test all the changed packages', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/a_package/lib/a_package.dart -packages/b_package/lib/src/foo.dart -'''), - ]; - final RepositoryPackage packageA = - createFakePackage('a_package', packagesDir); - final RepositoryPackage packageB = - createFakePackage('b_package', packagesDir); - createFakePackage('c_package', packagesDir); - await runCapturingPrint( - runner, ['sample', '--run-on-dirty-packages']); - - expect(command.plugins, - unorderedEquals([packageA.path, packageB.path])); - }); - - test('honors --exclude flag', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/a_package/lib/a_package.dart -packages/b_package/lib/src/foo.dart -'''), - ]; - final RepositoryPackage packageA = - createFakePackage('a_package', packagesDir); - createFakePackage('b_package', packagesDir); - createFakePackage('c_package', packagesDir); - await runCapturingPrint(runner, [ - 'sample', - '--exclude=b_package', - '--run-on-dirty-packages' - ]); - - expect(command.plugins, unorderedEquals([packageA.path])); - }); - }); - }); - - group('--packages-for-branch', () { - test('only tests changed packages relative to the merge base on a branch', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: 'packages/plugin1/plugin1.dart'), - ]; - processRunner.mockProcessesForExecutable['git-rev-parse'] = [ - MockProcess(stdout: 'a-branch'), - ]; - processRunner.mockProcessesForExecutable['git-merge-base'] = [ - MockProcess(exitCode: 1), // --is-ancestor check - MockProcess(stdout: 'abc123'), // finding merge base - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir); - - final List output = await runCapturingPrint( - runner, ['sample', '--packages-for-branch']); - - expect(command.plugins, unorderedEquals([plugin1.path])); - expect( - output, - containsAllInOrder([ - contains('--packages-for-branch: running on branch "a-branch"'), - contains( - 'Running for all packages that have diffs relative to "abc123"'), - ])); - // Ensure that it's diffing against the merge-base. - expect( - processRunner.recordedCalls, - contains( - const ProcessCall( - 'git-diff', ['--name-only', 'abc123', 'HEAD'], null), - )); - }); - - test('only tests changed packages relative to the previous commit on main', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: 'packages/plugin1/plugin1.dart'), - ]; - processRunner.mockProcessesForExecutable['git-rev-parse'] = [ - MockProcess(stdout: 'main'), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir); - - final List output = await runCapturingPrint( - runner, ['sample', '--packages-for-branch']); - - expect(command.plugins, unorderedEquals([plugin1.path])); - expect( - output, - containsAllInOrder([ - contains('--packages-for-branch: running on default branch.'), - contains( - '--packages-for-branch: using parent commit as the diff base'), - contains( - 'Running for all packages that have diffs relative to "HEAD~"'), - ])); - // Ensure that it's diffing against the prior commit. - expect( - processRunner.recordedCalls, - contains( - const ProcessCall( - 'git-diff', ['--name-only', 'HEAD~', 'HEAD'], null), - )); - }); - - test( - 'only tests changed packages relative to the previous commit if ' - 'running on a specific hash from main', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: 'packages/plugin1/plugin1.dart'), - ]; - processRunner.mockProcessesForExecutable['git-rev-parse'] = [ - MockProcess(stdout: 'HEAD'), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir); - - final List output = await runCapturingPrint( - runner, ['sample', '--packages-for-branch']); - - expect(command.plugins, unorderedEquals([plugin1.path])); - expect( - output, - containsAllInOrder([ - contains( - '--packages-for-branch: running on a commit from default branch.'), - contains( - '--packages-for-branch: using parent commit as the diff base'), - contains( - 'Running for all packages that have diffs relative to "HEAD~"'), - ])); - // Ensure that it's diffing against the prior commit. - expect( - processRunner.recordedCalls, - contains( - const ProcessCall( - 'git-diff', ['--name-only', 'HEAD~', 'HEAD'], null), - )); - }); - - test( - 'only tests changed packages relative to the previous commit if ' - 'running on a specific hash from origin/main', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: 'packages/plugin1/plugin1.dart'), - ]; - processRunner.mockProcessesForExecutable['git-rev-parse'] = [ - MockProcess(stdout: 'HEAD'), - ]; - processRunner.mockProcessesForExecutable['git-merge-base'] = [ - MockProcess(exitCode: 128), // Fail with a non-1 exit code for 'main' - MockProcess(), // Succeed for the variant. - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir); - - final List output = await runCapturingPrint( - runner, ['sample', '--packages-for-branch']); - - expect(command.plugins, unorderedEquals([plugin1.path])); - expect( - output, - containsAllInOrder([ - contains( - '--packages-for-branch: running on a commit from default branch.'), - contains( - '--packages-for-branch: using parent commit as the diff base'), - contains( - 'Running for all packages that have diffs relative to "HEAD~"'), - ])); - // Ensure that it's diffing against the prior commit. - expect( - processRunner.recordedCalls, - contains( - const ProcessCall( - 'git-diff', ['--name-only', 'HEAD~', 'HEAD'], null), - )); - }); - - test( - 'only tests changed packages relative to the previous commit on master', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: 'packages/plugin1/plugin1.dart'), - ]; - processRunner.mockProcessesForExecutable['git-rev-parse'] = [ - MockProcess(stdout: 'master'), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir); - - final List output = await runCapturingPrint( - runner, ['sample', '--packages-for-branch']); - - expect(command.plugins, unorderedEquals([plugin1.path])); - expect( - output, - containsAllInOrder([ - contains('--packages-for-branch: running on default branch.'), - contains( - '--packages-for-branch: using parent commit as the diff base'), - contains( - 'Running for all packages that have diffs relative to "HEAD~"'), - ])); - // Ensure that it's diffing against the prior commit. - expect( - processRunner.recordedCalls, - contains( - const ProcessCall( - 'git-diff', ['--name-only', 'HEAD~', 'HEAD'], null), - )); - }); - - test('throws if getting the branch fails', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: 'packages/plugin1/plugin1.dart'), - ]; - processRunner.mockProcessesForExecutable['git-rev-parse'] = [ - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['sample', '--packages-for-branch'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to determine branch'), - ])); - }); - }); - - group('sharding', () { - test('distributes evenly when evenly divisible', () async { - final List> expectedShards = - >[ - [ - createFakePackage('package1', packagesDir), - createFakePackage('package2', packagesDir), - createFakePackage('package3', packagesDir), - ], - [ - createFakePackage('package4', packagesDir), - createFakePackage('package5', packagesDir), - createFakePackage('package6', packagesDir), - ], - [ - createFakePackage('package7', packagesDir), - createFakePackage('package8', packagesDir), - createFakePackage('package9', packagesDir), - ], - ]; - - for (int i = 0; i < expectedShards.length; ++i) { - final SamplePackageCommand localCommand = SamplePackageCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - gitDir: MockGitDir(), - ); - final CommandRunner localRunner = - CommandRunner('common_command', 'Shard testing'); - localRunner.addCommand(localCommand); - - await runCapturingPrint(localRunner, [ - 'sample', - '--shardIndex=$i', - '--shardCount=3', - ]); - expect( - localCommand.plugins, - unorderedEquals(expectedShards[i] - .map((RepositoryPackage package) => package.path) - .toList())); - } - }); - - test('distributes as evenly as possible when not evenly divisible', - () async { - final List> expectedShards = - >[ - [ - createFakePackage('package1', packagesDir), - createFakePackage('package2', packagesDir), - createFakePackage('package3', packagesDir), - ], - [ - createFakePackage('package4', packagesDir), - createFakePackage('package5', packagesDir), - createFakePackage('package6', packagesDir), - ], - [ - createFakePackage('package7', packagesDir), - createFakePackage('package8', packagesDir), - ], - ]; - - for (int i = 0; i < expectedShards.length; ++i) { - final SamplePackageCommand localCommand = SamplePackageCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - gitDir: MockGitDir(), - ); - final CommandRunner localRunner = - CommandRunner('common_command', 'Shard testing'); - localRunner.addCommand(localCommand); - - await runCapturingPrint(localRunner, [ - 'sample', - '--shardIndex=$i', - '--shardCount=3', - ]); - expect( - localCommand.plugins, - unorderedEquals(expectedShards[i] - .map((RepositoryPackage package) => package.path) - .toList())); - } - }); - - // In CI (which is the use case for sharding) we often want to run muliple - // commands on the same set of packages, but the exclusion lists for those - // commands may be different. In those cases we still want all the commands - // to operate on a consistent set of plugins. - // - // E.g., some commands require running build-examples in a previous step; - // excluding some plugins from the later step shouldn't change what's tested - // in each shard, as it may no longer align with what was built. - test('counts excluded plugins when sharding', () async { - final List> expectedShards = - >[ - [ - createFakePackage('package1', packagesDir), - createFakePackage('package2', packagesDir), - createFakePackage('package3', packagesDir), - ], - [ - createFakePackage('package4', packagesDir), - createFakePackage('package5', packagesDir), - createFakePackage('package6', packagesDir), - ], - [ - createFakePackage('package7', packagesDir), - ], - ]; - // These would be in the last shard, but are excluded. - createFakePackage('package8', packagesDir); - createFakePackage('package9', packagesDir); - - for (int i = 0; i < expectedShards.length; ++i) { - final SamplePackageCommand localCommand = SamplePackageCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - gitDir: MockGitDir(), - ); - final CommandRunner localRunner = - CommandRunner('common_command', 'Shard testing'); - localRunner.addCommand(localCommand); - - await runCapturingPrint(localRunner, [ - 'sample', - '--shardIndex=$i', - '--shardCount=3', - '--exclude=package8,package9', - ]); - expect( - localCommand.plugins, - unorderedEquals(expectedShards[i] - .map((RepositoryPackage package) => package.path) - .toList())); - } - }); - }); -} - -class SamplePackageCommand extends PackageCommand { - SamplePackageCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - GitDir? gitDir, - this.includeSubpackages = false, - }) : super(packagesDir, - processRunner: processRunner, platform: platform, gitDir: gitDir); - - final List plugins = []; - - final bool includeSubpackages; - - @override - final String name = 'sample'; - - @override - final String description = 'sample command'; - - @override - Future run() async { - final Stream packages = includeSubpackages - ? getTargetPackagesAndSubpackages() - : getTargetPackages(); - await for (final PackageEnumerationEntry entry in packages) { - plugins.add(entry.package.path); - } - } -} diff --git a/script/tool/test/common/package_command_test.mocks.dart b/script/tool/test/common/package_command_test.mocks.dart deleted file mode 100644 index 79c5d4df1a8c..000000000000 --- a/script/tool/test/common/package_command_test.mocks.dart +++ /dev/null @@ -1,286 +0,0 @@ -// Mocks generated by Mockito 5.3.2 from annotations -// in flutter_plugin_tools/test/common/package_command_test.dart. -// Do not manually edit this file. - -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i6; -import 'dart:io' as _i4; - -import 'package:git/src/branch_reference.dart' as _i3; -import 'package:git/src/commit.dart' as _i2; -import 'package:git/src/commit_reference.dart' as _i8; -import 'package:git/src/git_dir.dart' as _i5; -import 'package:git/src/tag.dart' as _i7; -import 'package:git/src/tree_entry.dart' as _i9; -import 'package:mockito/mockito.dart' as _i1; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types -// ignore_for_file: subtype_of_sealed_class - -class _FakeCommit_0 extends _i1.SmartFake implements _i2.Commit { - _FakeCommit_0( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeBranchReference_1 extends _i1.SmartFake - implements _i3.BranchReference { - _FakeBranchReference_1( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeProcessResult_2 extends _i1.SmartFake implements _i4.ProcessResult { - _FakeProcessResult_2( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -/// A class which mocks [GitDir]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockGitDir extends _i1.Mock implements _i5.GitDir { - MockGitDir() { - _i1.throwOnMissingStub(this); - } - - @override - String get path => (super.noSuchMethod( - Invocation.getter(#path), - returnValue: '', - ) as String); - @override - _i6.Future commitCount([String? branchName = r'HEAD']) => - (super.noSuchMethod( - Invocation.method( - #commitCount, - [branchName], - ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); - @override - _i6.Future<_i2.Commit> commitFromRevision(String? revision) => - (super.noSuchMethod( - Invocation.method( - #commitFromRevision, - [revision], - ), - returnValue: _i6.Future<_i2.Commit>.value(_FakeCommit_0( - this, - Invocation.method( - #commitFromRevision, - [revision], - ), - )), - ) as _i6.Future<_i2.Commit>); - @override - _i6.Future> commits([String? branchName = r'HEAD']) => - (super.noSuchMethod( - Invocation.method( - #commits, - [branchName], - ), - returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); - @override - _i6.Future<_i3.BranchReference?> branchReference(String? branchName) => - (super.noSuchMethod( - Invocation.method( - #branchReference, - [branchName], - ), - returnValue: _i6.Future<_i3.BranchReference?>.value(), - ) as _i6.Future<_i3.BranchReference?>); - @override - _i6.Future> branches() => (super.noSuchMethod( - Invocation.method( - #branches, - [], - ), - returnValue: _i6.Future>.value( - <_i3.BranchReference>[]), - ) as _i6.Future>); - @override - _i6.Stream<_i7.Tag> tags() => (super.noSuchMethod( - Invocation.method( - #tags, - [], - ), - returnValue: _i6.Stream<_i7.Tag>.empty(), - ) as _i6.Stream<_i7.Tag>); - @override - _i6.Future> showRef({ - bool? heads = false, - bool? tags = false, - }) => - (super.noSuchMethod( - Invocation.method( - #showRef, - [], - { - #heads: heads, - #tags: tags, - }, - ), - returnValue: _i6.Future>.value( - <_i8.CommitReference>[]), - ) as _i6.Future>); - @override - _i6.Future<_i3.BranchReference> currentBranch() => (super.noSuchMethod( - Invocation.method( - #currentBranch, - [], - ), - returnValue: - _i6.Future<_i3.BranchReference>.value(_FakeBranchReference_1( - this, - Invocation.method( - #currentBranch, - [], - ), - )), - ) as _i6.Future<_i3.BranchReference>); - @override - _i6.Future> lsTree( - String? treeish, { - bool? subTreesOnly = false, - String? path, - }) => - (super.noSuchMethod( - Invocation.method( - #lsTree, - [treeish], - { - #subTreesOnly: subTreesOnly, - #path: path, - }, - ), - returnValue: _i6.Future>.value(<_i9.TreeEntry>[]), - ) as _i6.Future>); - @override - _i6.Future createOrUpdateBranch( - String? branchName, - String? treeSha, - String? commitMessage, - ) => - (super.noSuchMethod( - Invocation.method( - #createOrUpdateBranch, - [ - branchName, - treeSha, - commitMessage, - ], - ), - returnValue: _i6.Future.value(), - ) as _i6.Future); - @override - _i6.Future commitTree( - String? treeSha, - String? commitMessage, { - List? parentCommitShas, - }) => - (super.noSuchMethod( - Invocation.method( - #commitTree, - [ - treeSha, - commitMessage, - ], - {#parentCommitShas: parentCommitShas}, - ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); - @override - _i6.Future> writeObjects(List? paths) => - (super.noSuchMethod( - Invocation.method( - #writeObjects, - [paths], - ), - returnValue: _i6.Future>.value({}), - ) as _i6.Future>); - @override - _i6.Future<_i4.ProcessResult> runCommand( - Iterable? args, { - bool? throwOnError = true, - }) => - (super.noSuchMethod( - Invocation.method( - #runCommand, - [args], - {#throwOnError: throwOnError}, - ), - returnValue: _i6.Future<_i4.ProcessResult>.value(_FakeProcessResult_2( - this, - Invocation.method( - #runCommand, - [args], - {#throwOnError: throwOnError}, - ), - )), - ) as _i6.Future<_i4.ProcessResult>); - @override - _i6.Future isWorkingTreeClean() => (super.noSuchMethod( - Invocation.method( - #isWorkingTreeClean, - [], - ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); - @override - _i6.Future<_i2.Commit?> updateBranch( - String? branchName, - _i6.Future Function(_i4.Directory)? populater, - String? commitMessage, - ) => - (super.noSuchMethod( - Invocation.method( - #updateBranch, - [ - branchName, - populater, - commitMessage, - ], - ), - returnValue: _i6.Future<_i2.Commit?>.value(), - ) as _i6.Future<_i2.Commit?>); - @override - _i6.Future<_i2.Commit?> updateBranchWithDirectoryContents( - String? branchName, - String? sourceDirectoryPath, - String? commitMessage, - ) => - (super.noSuchMethod( - Invocation.method( - #updateBranchWithDirectoryContents, - [ - branchName, - sourceDirectoryPath, - commitMessage, - ], - ), - returnValue: _i6.Future<_i2.Commit?>.value(), - ) as _i6.Future<_i2.Commit?>); -} diff --git a/script/tool/test/common/package_looping_command_test.dart b/script/tool/test/common/package_looping_command_test.dart deleted file mode 100644 index 34f346c62fe7..000000000000 --- a/script/tool/test/common/package_looping_command_test.dart +++ /dev/null @@ -1,949 +0,0 @@ -// 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 'dart:async'; -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/package_looping_command.dart'; -import 'package:flutter_plugin_tools/src/common/process_runner.dart'; -import 'package:git/git.dart'; -import 'package:mockito/mockito.dart'; -import 'package:platform/platform.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; -import '../util.dart'; -import 'package_command_test.mocks.dart'; - -// Constants for colorized output start and end. -const String _startElapsedTimeColor = '\x1B[90m'; -const String _startErrorColor = '\x1B[31m'; -const String _startHeadingColor = '\x1B[36m'; -const String _startSkipColor = '\x1B[90m'; -const String _startSkipWithWarningColor = '\x1B[93m'; -const String _startSuccessColor = '\x1B[32m'; -const String _startWarningColor = '\x1B[33m'; -const String _endColor = '\x1B[0m'; - -// The filename within a package containing warnings to log during runForPackage. -enum _ResultFileType { - /// A file containing errors to return. - errors, - - /// A file containing warnings that should be logged. - warns, - - /// A file indicating that the package should be skipped, and why. - skips, - - /// A file indicating that the package should throw. - throws, -} - -// The filename within a package containing errors to return from runForPackage. -const String _errorFile = 'errors'; -// The filename within a package indicating that it should be skipped. -const String _skipFile = 'skip'; -// The filename within a package containing warnings to log during runForPackage. -const String _warningFile = 'warnings'; -// The filename within a package indicating that it should throw. -const String _throwFile = 'throw'; - -/// Writes a file to [package] to control the behavior of -/// [TestPackageLoopingCommand] for that package. -void _addResultFile(RepositoryPackage package, _ResultFileType type, - {String? contents}) { - final File file = package.directory.childFile(_filenameForType(type)); - file.createSync(); - if (contents != null) { - file.writeAsStringSync(contents); - } -} - -String _filenameForType(_ResultFileType type) { - switch (type) { - case _ResultFileType.errors: - return _errorFile; - case _ResultFileType.warns: - return _warningFile; - case _ResultFileType.skips: - return _skipFile; - case _ResultFileType.throws: - return _throwFile; - } -} - -void main() { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late Directory thirdPartyPackagesDir; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - thirdPartyPackagesDir = packagesDir.parent - .childDirectory('third_party') - .childDirectory('packages'); - }); - - /// Creates a TestPackageLoopingCommand instance that uses [gitDiffResponse] - /// for git diffs, and logs output to [printOutput]. - TestPackageLoopingCommand createTestCommand({ - String gitDiffResponse = '', - bool hasLongOutput = true, - PackageLoopingType packageLoopingType = PackageLoopingType.topLevelOnly, - bool failsDuringInit = false, - bool warnsDuringInit = false, - bool warnsDuringCleanup = false, - bool captureOutput = false, - String? customFailureListHeader, - String? customFailureListFooter, - }) { - // Set up the git diff response. - final MockGitDir gitDir = MockGitDir(); - when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) - .thenAnswer((Invocation invocation) { - final List arguments = - invocation.positionalArguments[0]! as List; - final MockProcessResult mockProcessResult = MockProcessResult(); - if (arguments[0] == 'diff') { - when(mockProcessResult.stdout as String?) - .thenReturn(gitDiffResponse); - } - return Future.value(mockProcessResult); - }); - - return TestPackageLoopingCommand( - packagesDir, - platform: mockPlatform, - hasLongOutput: hasLongOutput, - packageLoopingType: packageLoopingType, - failsDuringInit: failsDuringInit, - warnsDuringInit: warnsDuringInit, - warnsDuringCleanup: warnsDuringCleanup, - customFailureListHeader: customFailureListHeader, - customFailureListFooter: customFailureListFooter, - captureOutput: captureOutput, - gitDir: gitDir, - ); - } - - /// Runs [command] with the given [arguments], and returns its output. - Future> runCommand( - TestPackageLoopingCommand command, { - List arguments = const [], - void Function(Error error)? errorHandler, - }) async { - late CommandRunner runner; - runner = CommandRunner('test_package_looping_command', - 'Test for base package looping functionality'); - runner.addCommand(command); - return runCapturingPrint( - runner, - [command.name, ...arguments], - errorHandler: errorHandler, - ); - } - - group('tool exit', () { - test('is handled during initializeRun', () async { - final TestPackageLoopingCommand command = - createTestCommand(failsDuringInit: true); - - expect(() => runCommand(command), throwsA(isA())); - }); - - test('does not stop looping on error', () async { - createFakePackage('package_a', packagesDir); - final RepositoryPackage failingPackage = - createFakePlugin('package_b', packagesDir); - createFakePackage('package_c', packagesDir); - _addResultFile(failingPackage, _ResultFileType.errors); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - Error? commandError; - final List output = - await runCommand(command, errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - '${_startHeadingColor}Running for package_a...$_endColor', - '${_startHeadingColor}Running for package_b...$_endColor', - '${_startHeadingColor}Running for package_c...$_endColor', - ])); - }); - - test('does not stop looping on exceptions', () async { - createFakePackage('package_a', packagesDir); - final RepositoryPackage failingPackage = - createFakePlugin('package_b', packagesDir); - createFakePackage('package_c', packagesDir); - _addResultFile(failingPackage, _ResultFileType.throws); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - Error? commandError; - final List output = - await runCommand(command, errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - '${_startHeadingColor}Running for package_a...$_endColor', - '${_startHeadingColor}Running for package_b...$_endColor', - '${_startHeadingColor}Running for package_c...$_endColor', - ])); - }); - }); - - group('package iteration', () { - test('includes plugins and packages', () async { - final RepositoryPackage plugin = - createFakePlugin('a_plugin', packagesDir); - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - final TestPackageLoopingCommand command = createTestCommand(); - await runCommand(command); - - expect(command.checkedPackages, - unorderedEquals([plugin.path, package.path])); - }); - - test('includes third_party/packages', () async { - final RepositoryPackage package1 = - createFakePackage('a_package', packagesDir); - final RepositoryPackage package2 = - createFakePackage('another_package', thirdPartyPackagesDir); - - final TestPackageLoopingCommand command = createTestCommand(); - await runCommand(command); - - expect(command.checkedPackages, - unorderedEquals([package1.path, package2.path])); - }); - - test('includes all subpackages when requested', () async { - final RepositoryPackage plugin = createFakePlugin('a_plugin', packagesDir, - examples: ['example1', 'example2']); - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - final RepositoryPackage subPackage = createFakePackage( - 'sub_package', package.directory, - examples: []); - - final TestPackageLoopingCommand command = createTestCommand( - packageLoopingType: PackageLoopingType.includeAllSubpackages); - await runCommand(command); - - expect( - command.checkedPackages, - unorderedEquals([ - plugin.path, - getExampleDir(plugin).childDirectory('example1').path, - getExampleDir(plugin).childDirectory('example2').path, - package.path, - getExampleDir(package).path, - subPackage.path, - ])); - }); - - test('includes examples when requested', () async { - final RepositoryPackage plugin = createFakePlugin('a_plugin', packagesDir, - examples: ['example1', 'example2']); - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - final RepositoryPackage subPackage = - createFakePackage('sub_package', package.directory); - - final TestPackageLoopingCommand command = createTestCommand( - packageLoopingType: PackageLoopingType.includeExamples); - await runCommand(command); - - expect( - command.checkedPackages, - unorderedEquals([ - plugin.path, - getExampleDir(plugin).childDirectory('example1').path, - getExampleDir(plugin).childDirectory('example2').path, - package.path, - getExampleDir(package).path, - ])); - expect(command.checkedPackages, isNot(contains(subPackage.path))); - }); - - test('excludes subpackages when main package is excluded', () async { - final RepositoryPackage excluded = createFakePlugin( - 'a_plugin', packagesDir, - examples: ['example1', 'example2']); - final RepositoryPackage included = - createFakePackage('a_package', packagesDir); - final RepositoryPackage subpackage = - createFakePackage('sub_package', excluded.directory); - - final TestPackageLoopingCommand command = createTestCommand( - packageLoopingType: PackageLoopingType.includeAllSubpackages); - await runCommand(command, arguments: ['--exclude=a_plugin']); - - final Iterable examples = excluded.getExamples(); - - expect( - command.checkedPackages, - unorderedEquals([ - included.path, - getExampleDir(included).path, - ])); - expect(command.checkedPackages, isNot(contains(excluded.path))); - expect(examples.length, 2); - for (final RepositoryPackage example in examples) { - expect(command.checkedPackages, isNot(contains(example.path))); - } - expect(command.checkedPackages, isNot(contains(subpackage.path))); - }); - - test('excludes examples when main package is excluded', () async { - final RepositoryPackage excluded = createFakePlugin( - 'a_plugin', packagesDir, - examples: ['example1', 'example2']); - final RepositoryPackage included = - createFakePackage('a_package', packagesDir); - - final TestPackageLoopingCommand command = createTestCommand( - packageLoopingType: PackageLoopingType.includeExamples); - await runCommand(command, arguments: ['--exclude=a_plugin']); - - final Iterable examples = excluded.getExamples(); - - expect( - command.checkedPackages, - unorderedEquals([ - included.path, - getExampleDir(included).path, - ])); - expect(command.checkedPackages, isNot(contains(excluded.path))); - expect(examples.length, 2); - for (final RepositoryPackage example in examples) { - expect(command.checkedPackages, isNot(contains(example.path))); - } - }); - - test('skips unsupported Flutter versions when requested', () async { - final RepositoryPackage excluded = createFakePlugin( - 'a_plugin', packagesDir, - flutterConstraint: '>=2.10.0'); - final RepositoryPackage included = - createFakePackage('a_package', packagesDir); - - final TestPackageLoopingCommand command = createTestCommand( - packageLoopingType: PackageLoopingType.includeAllSubpackages, - hasLongOutput: false); - final List output = await runCommand(command, arguments: [ - '--skip-if-not-supporting-flutter-version=2.5.0' - ]); - - expect( - command.checkedPackages, - unorderedEquals([ - included.path, - getExampleDir(included).path, - ])); - expect(command.checkedPackages, isNot(contains(excluded.path))); - - expect( - output, - containsAllInOrder([ - '${_startHeadingColor}Running for a_package...$_endColor', - '${_startHeadingColor}Running for a_plugin...$_endColor', - '$_startSkipColor SKIPPING: Does not support Flutter 2.5.0$_endColor', - ])); - }); - - test('skips unsupported Dart versions when requested', () async { - final RepositoryPackage excluded = createFakePackage( - 'excluded_package', packagesDir, - dartConstraint: '>=2.17.0 <3.0.0'); - final RepositoryPackage included = - createFakePackage('a_package', packagesDir); - - final TestPackageLoopingCommand command = createTestCommand( - packageLoopingType: PackageLoopingType.includeAllSubpackages, - hasLongOutput: false); - final List output = await runCommand(command, - arguments: ['--skip-if-not-supporting-dart-version=2.14.0']); - - expect( - command.checkedPackages, - unorderedEquals([ - included.path, - getExampleDir(included).path, - ])); - expect(command.checkedPackages, isNot(contains(excluded.path))); - - expect( - output, - containsAllInOrder([ - '${_startHeadingColor}Running for a_package...$_endColor', - '${_startHeadingColor}Running for excluded_package...$_endColor', - '$_startSkipColor SKIPPING: Does not support Dart 2.14.0$_endColor', - ])); - }); - }); - - group('output', () { - test('has the expected package headers for long-form output', () async { - createFakePlugin('package_a', packagesDir); - createFakePackage('package_b', packagesDir); - - final TestPackageLoopingCommand command = createTestCommand(); - final List output = await runCommand(command); - - const String separator = - '============================================================'; - expect( - output, - containsAllInOrder([ - '$_startHeadingColor\n$separator\n|| Running for package_a\n$separator\n$_endColor', - '$_startHeadingColor\n$separator\n|| Running for package_b\n$separator\n$_endColor', - ])); - }); - - test('has the expected package headers for short-form output', () async { - createFakePlugin('package_a', packagesDir); - createFakePackage('package_b', packagesDir); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - final List output = await runCommand(command); - - expect( - output, - containsAllInOrder([ - '${_startHeadingColor}Running for package_a...$_endColor', - '${_startHeadingColor}Running for package_b...$_endColor', - ])); - }); - - test('prints timing info in long-form output when requested', () async { - createFakePlugin('package_a', packagesDir); - createFakePackage('package_b', packagesDir); - - final TestPackageLoopingCommand command = createTestCommand(); - final List output = - await runCommand(command, arguments: ['--log-timing']); - - const String separator = - '============================================================'; - expect( - output, - containsAllInOrder([ - '$_startHeadingColor\n$separator\n|| Running for package_a [@0:00]\n$separator\n$_endColor', - '$_startElapsedTimeColor\n[package_a completed in 0m 0s]$_endColor', - '$_startHeadingColor\n$separator\n|| Running for package_b [@0:00]\n$separator\n$_endColor', - '$_startElapsedTimeColor\n[package_b completed in 0m 0s]$_endColor', - ])); - }); - - test('prints timing info in short-form output when requested', () async { - createFakePlugin('package_a', packagesDir); - createFakePackage('package_b', packagesDir); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - final List output = - await runCommand(command, arguments: ['--log-timing']); - - expect( - output, - containsAllInOrder([ - '$_startHeadingColor[0:00] Running for package_a...$_endColor', - '$_startHeadingColor[0:00] Running for package_b...$_endColor', - ])); - // Short-form output should not include elapsed time. - expect(output, isNot(contains('[package_a completed in 0m 0s]'))); - }); - - test('shows the success message when nothing fails', () async { - createFakePackage('package_a', packagesDir); - createFakePackage('package_b', packagesDir); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - final List output = await runCommand(command); - - expect( - output, - containsAllInOrder([ - '\n', - '${_startSuccessColor}No issues found!$_endColor', - ])); - }); - - test('shows failure summaries when something fails without extra details', - () async { - createFakePackage('package_a', packagesDir); - final RepositoryPackage failingPackage1 = - createFakePlugin('package_b', packagesDir); - createFakePackage('package_c', packagesDir); - final RepositoryPackage failingPackage2 = - createFakePlugin('package_d', packagesDir); - _addResultFile(failingPackage1, _ResultFileType.errors); - _addResultFile(failingPackage2, _ResultFileType.errors); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - Error? commandError; - final List output = - await runCommand(command, errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - '\n', - '${_startErrorColor}The following packages had errors:$_endColor', - '$_startErrorColor package_b$_endColor', - '$_startErrorColor package_d$_endColor', - '${_startErrorColor}See above for full details.$_endColor', - ])); - }); - - test('uses custom summary header and footer if provided', () async { - createFakePackage('package_a', packagesDir); - final RepositoryPackage failingPackage1 = - createFakePlugin('package_b', packagesDir); - createFakePackage('package_c', packagesDir); - final RepositoryPackage failingPackage2 = - createFakePlugin('package_d', packagesDir); - _addResultFile(failingPackage1, _ResultFileType.errors); - _addResultFile(failingPackage2, _ResultFileType.errors); - - final TestPackageLoopingCommand command = createTestCommand( - hasLongOutput: false, - customFailureListHeader: 'This is a custom header', - customFailureListFooter: 'And a custom footer!'); - Error? commandError; - final List output = - await runCommand(command, errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - '\n', - '${_startErrorColor}This is a custom header$_endColor', - '$_startErrorColor package_b$_endColor', - '$_startErrorColor package_d$_endColor', - '${_startErrorColor}And a custom footer!$_endColor', - ])); - }); - - test('shows failure summaries when something fails with extra details', - () async { - createFakePackage('package_a', packagesDir); - final RepositoryPackage failingPackage1 = - createFakePlugin('package_b', packagesDir); - createFakePackage('package_c', packagesDir); - final RepositoryPackage failingPackage2 = - createFakePlugin('package_d', packagesDir); - _addResultFile(failingPackage1, _ResultFileType.errors, - contents: 'just one detail'); - _addResultFile(failingPackage2, _ResultFileType.errors, - contents: 'first detail\nsecond detail'); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - Error? commandError; - final List output = - await runCommand(command, errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - '\n', - '${_startErrorColor}The following packages had errors:$_endColor', - '$_startErrorColor package_b:\n just one detail$_endColor', - '$_startErrorColor package_d:\n first detail\n second detail$_endColor', - '${_startErrorColor}See above for full details.$_endColor', - ])); - }); - - test('is captured, not printed, when requested', () async { - createFakePlugin('package_a', packagesDir); - createFakePackage('package_b', packagesDir); - - final TestPackageLoopingCommand command = - createTestCommand(captureOutput: true); - final List output = await runCommand(command); - - expect(output, isEmpty); - - // None of the output should be colorized when captured. - const String separator = - '============================================================'; - expect( - command.capturedOutput, - containsAllInOrder([ - '\n$separator\n|| Running for package_a\n$separator\n', - '\n$separator\n|| Running for package_b\n$separator\n', - 'No issues found!', - ])); - }); - - test('logs skips', () async { - createFakePackage('package_a', packagesDir); - final RepositoryPackage skipPackage = - createFakePackage('package_b', packagesDir); - _addResultFile(skipPackage, _ResultFileType.skips, - contents: 'For a reason'); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - final List output = await runCommand(command); - - expect( - output, - containsAllInOrder([ - '${_startHeadingColor}Running for package_a...$_endColor', - '${_startHeadingColor}Running for package_b...$_endColor', - '$_startSkipColor SKIPPING: For a reason$_endColor', - ])); - }); - - test('logs exclusions', () async { - createFakePackage('package_a', packagesDir); - createFakePackage('package_b', packagesDir); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - final List output = - await runCommand(command, arguments: ['--exclude=package_b']); - - expect( - output, - containsAllInOrder([ - '${_startHeadingColor}Running for package_a...$_endColor', - '${_startSkipColor}Not running for package_b; excluded$_endColor', - ])); - }); - - test('logs warnings', () async { - final RepositoryPackage warnPackage = - createFakePackage('package_a', packagesDir); - _addResultFile(warnPackage, _ResultFileType.warns, - contents: 'Warning 1\nWarning 2'); - createFakePackage('package_b', packagesDir); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - final List output = await runCommand(command); - - expect( - output, - containsAllInOrder([ - '${_startHeadingColor}Running for package_a...$_endColor', - '${_startWarningColor}Warning 1$_endColor', - '${_startWarningColor}Warning 2$_endColor', - '${_startHeadingColor}Running for package_b...$_endColor', - ])); - }); - - test('logs unhandled exceptions as errors', () async { - createFakePackage('package_a', packagesDir); - final RepositoryPackage failingPackage = - createFakePlugin('package_b', packagesDir); - createFakePackage('package_c', packagesDir); - _addResultFile(failingPackage, _ResultFileType.throws); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - Error? commandError; - final List output = - await runCommand(command, errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - '${_startErrorColor}Exception: Uh-oh$_endColor', - '${_startErrorColor}The following packages had errors:$_endColor', - '$_startErrorColor package_b:\n Unhandled exception$_endColor', - ])); - }); - - test('prints run summary on success', () async { - final RepositoryPackage warnPackage1 = - createFakePackage('package_a', packagesDir); - _addResultFile(warnPackage1, _ResultFileType.warns, - contents: 'Warning 1\nWarning 2'); - - createFakePackage('package_b', packagesDir); - - final RepositoryPackage skipPackage = - createFakePackage('package_c', packagesDir); - _addResultFile(skipPackage, _ResultFileType.skips, - contents: 'For a reason'); - - final RepositoryPackage skipAndWarnPackage = - createFakePackage('package_d', packagesDir); - _addResultFile(skipAndWarnPackage, _ResultFileType.warns, - contents: 'Warning'); - _addResultFile(skipAndWarnPackage, _ResultFileType.skips, - contents: 'See warning'); - - final RepositoryPackage warnPackage2 = - createFakePackage('package_e', packagesDir); - _addResultFile(warnPackage2, _ResultFileType.warns, - contents: 'Warning 1\nWarning 2'); - - createFakePackage('package_f', packagesDir); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - final List output = await runCommand(command); - - expect( - output, - containsAllInOrder([ - '------------------------------------------------------------', - 'Ran for 4 package(s) (2 with warnings)', - 'Skipped 2 package(s) (1 with warnings)', - '\n', - '${_startSuccessColor}No issues found!$_endColor', - ])); - // The long-form summary should not be printed for short-form commands. - expect(output, isNot(contains('Run summary:'))); - expect(output, isNot(contains(contains('package a - ran')))); - }); - - test('counts exclusions as skips in run summary', () async { - createFakePackage('package_a', packagesDir); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - final List output = - await runCommand(command, arguments: ['--exclude=package_a']); - - expect( - output, - containsAllInOrder([ - '------------------------------------------------------------', - 'Skipped 1 package(s)', - '\n', - '${_startSuccessColor}No issues found!$_endColor', - ])); - }); - - test('prints long-form run summary for long-output commands', () async { - final RepositoryPackage warnPackage1 = - createFakePackage('package_a', packagesDir); - _addResultFile(warnPackage1, _ResultFileType.warns, - contents: 'Warning 1\nWarning 2'); - - createFakePackage('package_b', packagesDir); - - final RepositoryPackage skipPackage = - createFakePackage('package_c', packagesDir); - _addResultFile(skipPackage, _ResultFileType.skips, - contents: 'For a reason'); - - final RepositoryPackage skipAndWarnPackage = - createFakePackage('package_d', packagesDir); - _addResultFile(skipAndWarnPackage, _ResultFileType.warns, - contents: 'Warning'); - _addResultFile(skipAndWarnPackage, _ResultFileType.skips, - contents: 'See warning'); - - final RepositoryPackage warnPackage2 = - createFakePackage('package_e', packagesDir); - _addResultFile(warnPackage2, _ResultFileType.warns, - contents: 'Warning 1\nWarning 2'); - - createFakePackage('package_f', packagesDir); - - final TestPackageLoopingCommand command = createTestCommand(); - final List output = await runCommand(command); - - expect( - output, - containsAllInOrder([ - '------------------------------------------------------------', - 'Run overview:', - ' package_a - ${_startWarningColor}ran (with warning)$_endColor', - ' package_b - ${_startSuccessColor}ran$_endColor', - ' package_c - ${_startSkipColor}skipped$_endColor', - ' package_d - ${_startSkipWithWarningColor}skipped (with warning)$_endColor', - ' package_e - ${_startWarningColor}ran (with warning)$_endColor', - ' package_f - ${_startSuccessColor}ran$_endColor', - '', - 'Ran for 4 package(s) (2 with warnings)', - 'Skipped 2 package(s) (1 with warnings)', - '\n', - '${_startSuccessColor}No issues found!$_endColor', - ])); - }); - - test('prints exclusions as skips in long-form run summary', () async { - createFakePackage('package_a', packagesDir); - - final TestPackageLoopingCommand command = createTestCommand(); - final List output = - await runCommand(command, arguments: ['--exclude=package_a']); - - expect( - output, - containsAllInOrder([ - ' package_a - ${_startSkipColor}excluded$_endColor', - '', - 'Skipped 1 package(s)', - '\n', - '${_startSuccessColor}No issues found!$_endColor', - ])); - }); - - test('handles warnings outside of runForPackage', () async { - createFakePackage('package_a', packagesDir); - - final TestPackageLoopingCommand command = createTestCommand( - hasLongOutput: false, - warnsDuringCleanup: true, - warnsDuringInit: true, - ); - final List output = await runCommand(command); - - expect( - output, - containsAllInOrder([ - '${_startWarningColor}Warning during initializeRun$_endColor', - '${_startHeadingColor}Running for package_a...$_endColor', - '${_startWarningColor}Warning during completeRun$_endColor', - '------------------------------------------------------------', - 'Ran for 1 package(s)', - '2 warnings not associated with a package', - '\n', - '${_startSuccessColor}No issues found!$_endColor', - ])); - }); - }); -} - -class TestPackageLoopingCommand extends PackageLoopingCommand { - TestPackageLoopingCommand( - Directory packagesDir, { - required Platform platform, - this.hasLongOutput = true, - this.packageLoopingType = PackageLoopingType.topLevelOnly, - this.customFailureListHeader, - this.customFailureListFooter, - this.failsDuringInit = false, - this.warnsDuringInit = false, - this.warnsDuringCleanup = false, - this.captureOutput = false, - ProcessRunner processRunner = const ProcessRunner(), - GitDir? gitDir, - }) : super(packagesDir, - processRunner: processRunner, platform: platform, gitDir: gitDir); - - final List checkedPackages = []; - final List capturedOutput = []; - - final String? customFailureListHeader; - final String? customFailureListFooter; - - final bool failsDuringInit; - final bool warnsDuringInit; - final bool warnsDuringCleanup; - - @override - bool hasLongOutput; - - @override - PackageLoopingType packageLoopingType; - - @override - String get failureListHeader => - customFailureListHeader ?? super.failureListHeader; - - @override - String get failureListFooter => - customFailureListFooter ?? super.failureListFooter; - - @override - bool captureOutput; - - @override - final String name = 'loop-test'; - - @override - final String description = 'sample package looping command'; - - @override - Future initializeRun() async { - if (warnsDuringInit) { - logWarning('Warning during initializeRun'); - } - if (failsDuringInit) { - throw ToolExit(2); - } - } - - @override - Future runForPackage(RepositoryPackage package) async { - checkedPackages.add(package.path); - final File warningFile = package.directory.childFile(_warningFile); - if (warningFile.existsSync()) { - final List warnings = warningFile.readAsLinesSync(); - warnings.forEach(logWarning); - } - final File skipFile = package.directory.childFile(_skipFile); - if (skipFile.existsSync()) { - return PackageResult.skip(skipFile.readAsStringSync()); - } - final File errorFile = package.directory.childFile(_errorFile); - if (errorFile.existsSync()) { - return PackageResult.fail(errorFile.readAsLinesSync()); - } - final File throwFile = package.directory.childFile(_throwFile); - if (throwFile.existsSync()) { - throw Exception('Uh-oh'); - } - return PackageResult.success(); - } - - @override - Future completeRun() async { - if (warnsDuringInit) { - logWarning('Warning during completeRun'); - } - } - - @override - Future handleCapturedOutput(List output) async { - capturedOutput.addAll(output); - } -} - -class MockProcessResult extends Mock implements io.ProcessResult {} diff --git a/script/tool/test/common/package_state_utils_test.dart b/script/tool/test/common/package_state_utils_test.dart deleted file mode 100644 index 9b6429a084ce..000000000000 --- a/script/tool/test/common/package_state_utils_test.dart +++ /dev/null @@ -1,346 +0,0 @@ -// 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 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/git_version_finder.dart'; -import 'package:flutter_plugin_tools/src/common/package_state_utils.dart'; -import 'package:test/fake.dart'; -import 'package:test/test.dart'; - -import '../util.dart'; - -void main() { - late FileSystem fileSystem; - late Directory packagesDir; - - setUp(() { - fileSystem = MemoryFileSystem(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - }); - - group('checkPackageChangeState', () { - test('reports version change needed for code changes', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - const List changedFiles = [ - 'packages/a_package/lib/plugin.dart', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_package'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, true); - expect(state.needsChangelogChange, true); - }); - - test('handles trailing slash on package path', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - const List changedFiles = [ - 'packages/a_package/lib/plugin.dart', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_package/'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, true); - expect(state.needsChangelogChange, true); - expect(state.hasChangelogChange, false); - }); - - test('does not flag version- and changelog-change-exempt changes', - () async { - final RepositoryPackage package = - createFakePlugin('a_plugin', packagesDir); - - const List changedFiles = [ - 'packages/a_plugin/CHANGELOG.md', - // Analysis. - 'packages/a_plugin/example/android/lint-baseline.xml', - // Tests. - 'packages/a_plugin/example/android/src/androidTest/foo/bar/FooTest.java', - 'packages/a_plugin/example/ios/RunnerTests/Foo.m', - 'packages/a_plugin/example/ios/RunnerUITests/info.plist', - // Pigeon input. - 'packages/a_plugin/pigeons/messages.dart', - // Test scripts. - 'packages/a_plugin/run_tests.sh', - // Tools. - 'packages/a_plugin/tool/a_development_tool.dart', - // Example build files. - 'packages/a_plugin/example/android/build.gradle', - 'packages/a_plugin/example/android/gradle/wrapper/gradle-wrapper.properties', - 'packages/a_plugin/example/ios/Runner.xcodeproj/project.pbxproj', - 'packages/a_plugin/example/linux/flutter/CMakeLists.txt', - 'packages/a_plugin/example/macos/Runner.xcodeproj/project.pbxproj', - 'packages/a_plugin/example/windows/CMakeLists.txt', - 'packages/a_plugin/example/pubspec.yaml', - // Pigeon platform tests, which have an unusual structure. - 'packages/a_plugin/platform_tests/shared_test_plugin_code/lib/integration_tests.dart', - 'packages/a_plugin/platform_tests/test_plugin/windows/test_plugin.cpp', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, false); - expect(state.needsChangelogChange, false); - expect(state.hasChangelogChange, true); - }); - - test('only considers a root "tool" folder to be special', () async { - final RepositoryPackage package = - createFakePlugin('a_plugin', packagesDir); - - const List changedFiles = [ - 'packages/a_plugin/lib/foo/tool/tool_thing.dart', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, true); - expect(state.needsChangelogChange, true); - }); - - test('requires a version change for example/lib/main.dart', () async { - final RepositoryPackage package = createFakePlugin( - 'a_plugin', packagesDir, - extraFiles: ['example/lib/main.dart']); - - const List changedFiles = [ - 'packages/a_plugin/example/lib/main.dart', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, true); - expect(state.needsChangelogChange, true); - }); - - test('requires a version change for example/main.dart', () async { - final RepositoryPackage package = createFakePlugin( - 'a_plugin', packagesDir, - extraFiles: ['example/main.dart']); - - const List changedFiles = [ - 'packages/a_plugin/example/main.dart', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, true); - expect(state.needsChangelogChange, true); - }); - - test('requires a version change for example readme.md', () async { - final RepositoryPackage package = - createFakePlugin('a_plugin', packagesDir); - - const List changedFiles = [ - 'packages/a_plugin/example/README.md', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, true); - expect(state.needsChangelogChange, true); - }); - - test('requires a version change for example/example.md', () async { - final RepositoryPackage package = createFakePlugin( - 'a_plugin', packagesDir, - extraFiles: ['example/example.md']); - - const List changedFiles = [ - 'packages/a_plugin/example/example.md', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, true); - expect(state.needsChangelogChange, true); - }); - - test( - 'requires a changelog change but no version change for ' - 'lower-priority examples when example.md is present', () async { - final RepositoryPackage package = createFakePlugin( - 'a_plugin', packagesDir, - extraFiles: ['example/example.md']); - - const List changedFiles = [ - 'packages/a_plugin/example/lib/main.dart', - 'packages/a_plugin/example/main.dart', - 'packages/a_plugin/example/README.md', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, false); - expect(state.needsChangelogChange, true); - }); - - test( - 'requires a changelog change but no version change for README.md when ' - 'code example is present', () async { - final RepositoryPackage package = createFakePlugin( - 'a_plugin', packagesDir, - extraFiles: ['example/lib/main.dart']); - - const List changedFiles = [ - 'packages/a_plugin/example/README.md', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, false); - expect(state.needsChangelogChange, true); - }); - - test( - 'does not requires changelog or version change for build.gradle ' - 'test-dependency-only changes', () async { - final RepositoryPackage package = - createFakePlugin('a_plugin', packagesDir); - - const List changedFiles = [ - 'packages/a_plugin/android/build.gradle', - ]; - - final GitVersionFinder git = FakeGitVersionFinder(>{ - 'packages/a_plugin/android/build.gradle': [ - "- androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'", - "- testImplementation 'junit:junit:4.10.0'", - "+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'", - "+ testImplementation 'junit:junit:4.13.2'", - ] - }); - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/', - git: git); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, false); - expect(state.needsChangelogChange, false); - }); - - test('requires changelog or version change for other build.gradle changes', - () async { - final RepositoryPackage package = - createFakePlugin('a_plugin', packagesDir); - - const List changedFiles = [ - 'packages/a_plugin/android/build.gradle', - ]; - - final GitVersionFinder git = FakeGitVersionFinder(>{ - 'packages/a_plugin/android/build.gradle': [ - "- androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'", - "- testImplementation 'junit:junit:4.10.0'", - "+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'", - "+ testImplementation 'junit:junit:4.13.2'", - "- implementation 'com.google.android.gms:play-services-maps:18.0.0'", - "+ implementation 'com.google.android.gms:play-services-maps:18.0.2'", - ] - }); - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/', - git: git); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, true); - expect(state.needsChangelogChange, true); - }); - - test( - 'requires changelog or version change if build.gradle diffs cannot ' - 'be checked', () async { - final RepositoryPackage package = - createFakePlugin('a_plugin', packagesDir); - - const List changedFiles = [ - 'packages/a_plugin/android/build.gradle', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, true); - expect(state.needsChangelogChange, true); - }); - - test( - 'requires changelog or version change if build.gradle diffs cannot ' - 'be determined', () async { - final RepositoryPackage package = - createFakePlugin('a_plugin', packagesDir); - - const List changedFiles = [ - 'packages/a_plugin/android/build.gradle', - ]; - - final GitVersionFinder git = FakeGitVersionFinder(>{ - 'packages/a_plugin/android/build.gradle': [] - }); - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/', - git: git); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, true); - expect(state.needsChangelogChange, true); - }); - }); -} - -class FakeGitVersionFinder extends Fake implements GitVersionFinder { - FakeGitVersionFinder(this.fileDiffs); - - final Map> fileDiffs; - - @override - Future> getDiffContents({ - String? targetPath, - bool includeUncommitted = false, - }) async { - return fileDiffs[targetPath]!; - } -} diff --git a/script/tool/test/common/plugin_utils_test.dart b/script/tool/test/common/plugin_utils_test.dart deleted file mode 100644 index 415b1db8932a..000000000000 --- a/script/tool/test/common/plugin_utils_test.dart +++ /dev/null @@ -1,256 +0,0 @@ -// 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 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; -import 'package:test/test.dart'; - -import '../util.dart'; - -void main() { - late FileSystem fileSystem; - late Directory packagesDir; - - setUp(() { - fileSystem = MemoryFileSystem(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - }); - - group('pluginSupportsPlatform', () { - test('no platforms', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir); - - expect(pluginSupportsPlatform(platformAndroid, plugin), isFalse); - expect(pluginSupportsPlatform(platformIOS, plugin), isFalse); - expect(pluginSupportsPlatform(platformLinux, plugin), isFalse); - expect(pluginSupportsPlatform(platformMacOS, plugin), isFalse); - expect(pluginSupportsPlatform(platformWeb, plugin), isFalse); - expect(pluginSupportsPlatform(platformWindows, plugin), isFalse); - }); - - test('all platforms', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - platformLinux: const PlatformDetails(PlatformSupport.inline), - platformMacOS: const PlatformDetails(PlatformSupport.inline), - platformWeb: const PlatformDetails(PlatformSupport.inline), - platformWindows: const PlatformDetails(PlatformSupport.inline), - }); - - expect(pluginSupportsPlatform(platformAndroid, plugin), isTrue); - expect(pluginSupportsPlatform(platformIOS, plugin), isTrue); - expect(pluginSupportsPlatform(platformLinux, plugin), isTrue); - expect(pluginSupportsPlatform(platformMacOS, plugin), isTrue); - expect(pluginSupportsPlatform(platformWeb, plugin), isTrue); - expect(pluginSupportsPlatform(platformWindows, plugin), isTrue); - }); - - test('some platforms', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformLinux: const PlatformDetails(PlatformSupport.inline), - platformWeb: const PlatformDetails(PlatformSupport.inline), - }); - - expect(pluginSupportsPlatform(platformAndroid, plugin), isTrue); - expect(pluginSupportsPlatform(platformIOS, plugin), isFalse); - expect(pluginSupportsPlatform(platformLinux, plugin), isTrue); - expect(pluginSupportsPlatform(platformMacOS, plugin), isFalse); - expect(pluginSupportsPlatform(platformWeb, plugin), isTrue); - expect(pluginSupportsPlatform(platformWindows, plugin), isFalse); - }); - - test('inline plugins are only detected as inline', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - platformLinux: const PlatformDetails(PlatformSupport.inline), - platformMacOS: const PlatformDetails(PlatformSupport.inline), - platformWeb: const PlatformDetails(PlatformSupport.inline), - platformWindows: const PlatformDetails(PlatformSupport.inline), - }); - - expect( - pluginSupportsPlatform(platformAndroid, plugin, - requiredMode: PlatformSupport.inline), - isTrue); - expect( - pluginSupportsPlatform(platformAndroid, plugin, - requiredMode: PlatformSupport.federated), - isFalse); - expect( - pluginSupportsPlatform(platformIOS, plugin, - requiredMode: PlatformSupport.inline), - isTrue); - expect( - pluginSupportsPlatform(platformIOS, plugin, - requiredMode: PlatformSupport.federated), - isFalse); - expect( - pluginSupportsPlatform(platformLinux, plugin, - requiredMode: PlatformSupport.inline), - isTrue); - expect( - pluginSupportsPlatform(platformLinux, plugin, - requiredMode: PlatformSupport.federated), - isFalse); - expect( - pluginSupportsPlatform(platformMacOS, plugin, - requiredMode: PlatformSupport.inline), - isTrue); - expect( - pluginSupportsPlatform(platformMacOS, plugin, - requiredMode: PlatformSupport.federated), - isFalse); - expect( - pluginSupportsPlatform(platformWeb, plugin, - requiredMode: PlatformSupport.inline), - isTrue); - expect( - pluginSupportsPlatform(platformWeb, plugin, - requiredMode: PlatformSupport.federated), - isFalse); - expect( - pluginSupportsPlatform(platformWindows, plugin, - requiredMode: PlatformSupport.inline), - isTrue); - expect( - pluginSupportsPlatform(platformWindows, plugin, - requiredMode: PlatformSupport.federated), - isFalse); - }); - - test('federated plugins are only detected as federated', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.federated), - platformIOS: const PlatformDetails(PlatformSupport.federated), - platformLinux: const PlatformDetails(PlatformSupport.federated), - platformMacOS: const PlatformDetails(PlatformSupport.federated), - platformWeb: const PlatformDetails(PlatformSupport.federated), - platformWindows: const PlatformDetails(PlatformSupport.federated), - }); - - expect( - pluginSupportsPlatform(platformAndroid, plugin, - requiredMode: PlatformSupport.federated), - isTrue); - expect( - pluginSupportsPlatform(platformAndroid, plugin, - requiredMode: PlatformSupport.inline), - isFalse); - expect( - pluginSupportsPlatform(platformIOS, plugin, - requiredMode: PlatformSupport.federated), - isTrue); - expect( - pluginSupportsPlatform(platformIOS, plugin, - requiredMode: PlatformSupport.inline), - isFalse); - expect( - pluginSupportsPlatform(platformLinux, plugin, - requiredMode: PlatformSupport.federated), - isTrue); - expect( - pluginSupportsPlatform(platformLinux, plugin, - requiredMode: PlatformSupport.inline), - isFalse); - expect( - pluginSupportsPlatform(platformMacOS, plugin, - requiredMode: PlatformSupport.federated), - isTrue); - expect( - pluginSupportsPlatform(platformMacOS, plugin, - requiredMode: PlatformSupport.inline), - isFalse); - expect( - pluginSupportsPlatform(platformWeb, plugin, - requiredMode: PlatformSupport.federated), - isTrue); - expect( - pluginSupportsPlatform(platformWeb, plugin, - requiredMode: PlatformSupport.inline), - isFalse); - expect( - pluginSupportsPlatform(platformWindows, plugin, - requiredMode: PlatformSupport.federated), - isTrue); - expect( - pluginSupportsPlatform(platformWindows, plugin, - requiredMode: PlatformSupport.inline), - isFalse); - }); - }); - - group('pluginHasNativeCodeForPlatform', () { - test('returns false for web', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformWeb: const PlatformDetails(PlatformSupport.inline), - }, - ); - - expect(pluginHasNativeCodeForPlatform(platformWeb, plugin), isFalse); - }); - - test('returns false for a native-only plugin', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline), - platformMacOS: const PlatformDetails(PlatformSupport.inline), - platformWindows: const PlatformDetails(PlatformSupport.inline), - }, - ); - - expect(pluginHasNativeCodeForPlatform(platformLinux, plugin), isTrue); - expect(pluginHasNativeCodeForPlatform(platformMacOS, plugin), isTrue); - expect(pluginHasNativeCodeForPlatform(platformWindows, plugin), isTrue); - }); - - test('returns true for a native+Dart plugin', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline, hasDartCode: true), - platformMacOS: const PlatformDetails(PlatformSupport.inline, hasDartCode: true), - platformWindows: const PlatformDetails(PlatformSupport.inline, hasDartCode: true), - }, - ); - - expect(pluginHasNativeCodeForPlatform(platformLinux, plugin), isTrue); - expect(pluginHasNativeCodeForPlatform(platformMacOS, plugin), isTrue); - expect(pluginHasNativeCodeForPlatform(platformWindows, plugin), isTrue); - }); - - test('returns false for a Dart-only plugin', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline, - hasNativeCode: false, hasDartCode: true), - platformMacOS: const PlatformDetails(PlatformSupport.inline, - hasNativeCode: false, hasDartCode: true), - platformWindows: const PlatformDetails(PlatformSupport.inline, - hasNativeCode: false, hasDartCode: true), - }, - ); - - expect(pluginHasNativeCodeForPlatform(platformLinux, plugin), isFalse); - expect(pluginHasNativeCodeForPlatform(platformMacOS, plugin), isFalse); - expect(pluginHasNativeCodeForPlatform(platformWindows, plugin), isFalse); - }); - }); -} diff --git a/script/tool/test/common/pub_version_finder_test.dart b/script/tool/test/common/pub_version_finder_test.dart deleted file mode 100644 index 1692cf214abe..000000000000 --- a/script/tool/test/common/pub_version_finder_test.dart +++ /dev/null @@ -1,89 +0,0 @@ -// 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 'dart:convert'; -import 'dart:io'; - -import 'package:flutter_plugin_tools/src/common/pub_version_finder.dart'; -import 'package:http/http.dart' as http; -import 'package:http/testing.dart'; -import 'package:mockito/mockito.dart'; -import 'package:pub_semver/pub_semver.dart'; -import 'package:test/test.dart'; - -void main() { - test('Package does not exist.', () async { - final MockClient mockClient = MockClient((http.Request request) async { - return http.Response('', 404); - }); - final PubVersionFinder finder = PubVersionFinder(httpClient: mockClient); - final PubVersionFinderResponse response = - await finder.getPackageVersion(packageName: 'some_package'); - - expect(response.versions, isEmpty); - expect(response.result, PubVersionFinderResult.noPackageFound); - expect(response.httpResponse.statusCode, 404); - expect(response.httpResponse.body, ''); - }); - - test('HTTP error when getting versions from pub', () async { - final MockClient mockClient = MockClient((http.Request request) async { - return http.Response('', 400); - }); - final PubVersionFinder finder = PubVersionFinder(httpClient: mockClient); - final PubVersionFinderResponse response = - await finder.getPackageVersion(packageName: 'some_package'); - - expect(response.versions, isEmpty); - expect(response.result, PubVersionFinderResult.fail); - expect(response.httpResponse.statusCode, 400); - expect(response.httpResponse.body, ''); - }); - - test('Get a correct list of versions when http response is OK.', () async { - const Map httpResponse = { - 'name': 'some_package', - 'versions': [ - '0.0.1', - '0.0.2', - '0.0.2+2', - '0.1.1', - '0.0.1+1', - '0.1.0', - '0.2.0', - '0.1.0+1', - '0.0.2+1', - '2.0.0', - '1.2.0', - '1.0.0', - ], - }; - final MockClient mockClient = MockClient((http.Request request) async { - return http.Response(json.encode(httpResponse), 200); - }); - final PubVersionFinder finder = PubVersionFinder(httpClient: mockClient); - final PubVersionFinderResponse response = - await finder.getPackageVersion(packageName: 'some_package'); - - expect(response.versions, [ - Version.parse('2.0.0'), - Version.parse('1.2.0'), - Version.parse('1.0.0'), - Version.parse('0.2.0'), - Version.parse('0.1.1'), - Version.parse('0.1.0+1'), - Version.parse('0.1.0'), - Version.parse('0.0.2+2'), - Version.parse('0.0.2+1'), - Version.parse('0.0.2'), - Version.parse('0.0.1+1'), - Version.parse('0.0.1'), - ]); - expect(response.result, PubVersionFinderResult.success); - expect(response.httpResponse.statusCode, 200); - expect(response.httpResponse.body, json.encode(httpResponse)); - }); -} - -class MockProcessResult extends Mock implements ProcessResult {} diff --git a/script/tool/test/common/repository_package_test.dart b/script/tool/test/common/repository_package_test.dart deleted file mode 100644 index db519c008233..000000000000 --- a/script/tool/test/common/repository_package_test.dart +++ /dev/null @@ -1,220 +0,0 @@ -// 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 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:test/test.dart'; - -import '../util.dart'; - -void main() { - late FileSystem fileSystem; - late Directory packagesDir; - - setUp(() { - fileSystem = MemoryFileSystem(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - }); - - group('displayName', () { - test('prints packageDir-relative paths by default', () async { - expect( - RepositoryPackage(packagesDir.childDirectory('foo')).displayName, - 'foo', - ); - expect( - RepositoryPackage(packagesDir - .childDirectory('foo') - .childDirectory('bar') - .childDirectory('baz')) - .displayName, - 'foo/bar/baz', - ); - }); - - test('handles third_party/packages/', () async { - expect( - RepositoryPackage(packagesDir.parent - .childDirectory('third_party') - .childDirectory('packages') - .childDirectory('foo') - .childDirectory('bar') - .childDirectory('baz')) - .displayName, - 'foo/bar/baz', - ); - }); - - test('always uses Posix-style paths', () async { - final Directory windowsPackagesDir = createPackagesDirectory( - fileSystem: MemoryFileSystem(style: FileSystemStyle.windows)); - - expect( - RepositoryPackage(windowsPackagesDir.childDirectory('foo')).displayName, - 'foo', - ); - expect( - RepositoryPackage(windowsPackagesDir - .childDirectory('foo') - .childDirectory('bar') - .childDirectory('baz')) - .displayName, - 'foo/bar/baz', - ); - }); - - test('elides group name in grouped federated plugin structure', () async { - expect( - RepositoryPackage(packagesDir - .childDirectory('a_plugin') - .childDirectory('a_plugin_platform_interface')) - .displayName, - 'a_plugin_platform_interface', - ); - expect( - RepositoryPackage(packagesDir - .childDirectory('a_plugin') - .childDirectory('a_plugin_platform_web')) - .displayName, - 'a_plugin_platform_web', - ); - }); - - // The app-facing package doesn't get elided to avoid potential confusion - // with the group folder itself. - test('does not elide group name for app-facing packages', () async { - expect( - RepositoryPackage(packagesDir - .childDirectory('a_plugin') - .childDirectory('a_plugin')) - .displayName, - 'a_plugin/a_plugin', - ); - }); - }); - - group('getExamples', () { - test('handles a single Flutter example', () async { - final RepositoryPackage plugin = - createFakePlugin('a_plugin', packagesDir); - - final List examples = plugin.getExamples().toList(); - - expect(examples.length, 1); - expect(examples[0].path, getExampleDir(plugin).path); - }); - - test('handles multiple Flutter examples', () async { - final RepositoryPackage plugin = createFakePlugin('a_plugin', packagesDir, - examples: ['example1', 'example2']); - - final List examples = plugin.getExamples().toList(); - - expect(examples.length, 2); - expect(examples[0].path, - getExampleDir(plugin).childDirectory('example1').path); - expect(examples[1].path, - getExampleDir(plugin).childDirectory('example2').path); - }); - - test('handles a single non-Flutter example', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - final List examples = package.getExamples().toList(); - - expect(examples.length, 1); - expect(examples[0].path, getExampleDir(package).path); - }); - - test('handles multiple non-Flutter examples', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - examples: ['example1', 'example2']); - - final List examples = package.getExamples().toList(); - - expect(examples.length, 2); - expect(examples[0].path, - getExampleDir(package).childDirectory('example1').path); - expect(examples[1].path, - getExampleDir(package).childDirectory('example2').path); - }); - }); - - group('federated plugin queries', () { - test('all return false for a simple plugin', () { - final RepositoryPackage plugin = - createFakePlugin('a_plugin', packagesDir); - expect(plugin.isFederated, false); - expect(plugin.isAppFacing, false); - expect(plugin.isPlatformInterface, false); - expect(plugin.isFederated, false); - }); - - test('handle app-facing packages', () { - final RepositoryPackage plugin = - createFakePlugin('a_plugin', packagesDir.childDirectory('a_plugin')); - expect(plugin.isFederated, true); - expect(plugin.isAppFacing, true); - expect(plugin.isPlatformInterface, false); - expect(plugin.isPlatformImplementation, false); - }); - - test('handle platform interface packages', () { - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin_platform_interface', - packagesDir.childDirectory('a_plugin')); - expect(plugin.isFederated, true); - expect(plugin.isAppFacing, false); - expect(plugin.isPlatformInterface, true); - expect(plugin.isPlatformImplementation, false); - }); - - test('handle platform implementation packages', () { - // A platform interface can end with anything, not just one of the known - // platform names, because of cases like webview_flutter_wkwebview. - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin_foo', packagesDir.childDirectory('a_plugin')); - expect(plugin.isFederated, true); - expect(plugin.isAppFacing, false); - expect(plugin.isPlatformInterface, false); - expect(plugin.isPlatformImplementation, true); - }); - }); - - group('pubspec', () { - test('file', () async { - final RepositoryPackage plugin = - createFakePlugin('a_plugin', packagesDir); - - final File pubspecFile = plugin.pubspecFile; - - expect(pubspecFile.path, plugin.directory.childFile('pubspec.yaml').path); - }); - - test('parsing', () async { - final RepositoryPackage plugin = createFakePlugin('a_plugin', packagesDir, - examples: ['example1', 'example2']); - - final Pubspec pubspec = plugin.parsePubspec(); - - expect(pubspec.name, 'a_plugin'); - }); - }); - - group('requiresFlutter', () { - test('returns true for Flutter package', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, isFlutter: true); - expect(package.requiresFlutter(), true); - }); - - test('returns false for non-Flutter package', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - expect(package.requiresFlutter(), false); - }); - }); -} diff --git a/script/tool/test/common/xcode_test.dart b/script/tool/test/common/xcode_test.dart deleted file mode 100644 index 259d8ea36cd2..000000000000 --- a/script/tool/test/common/xcode_test.dart +++ /dev/null @@ -1,406 +0,0 @@ -// 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 'dart:convert'; -import 'dart:io' as io; - -import 'package:file/file.dart'; -import 'package:file/local.dart'; -import 'package:flutter_plugin_tools/src/common/xcode.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; -import '../util.dart'; - -void main() { - late RecordingProcessRunner processRunner; - late Xcode xcode; - - setUp(() { - processRunner = RecordingProcessRunner(); - xcode = Xcode(processRunner: processRunner); - }); - - group('findBestAvailableIphoneSimulator', () { - test('finds the newest device', () async { - const String expectedDeviceId = '1E76A0FD-38AC-4537-A989-EA639D7D012A'; - // Note: This uses `dynamic` deliberately, and should not be updated to - // Object, in order to ensure that the code correctly handles this return - // type from JSON decoding. - final Map devices = { - 'runtimes': >[ - { - 'bundlePath': - '/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.0.simruntime', - 'buildversion': '17A577', - 'runtimeRoot': - '/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.0.simruntime/Contents/Resources/RuntimeRoot', - 'identifier': 'com.apple.CoreSimulator.SimRuntime.iOS-13-0', - 'version': '13.0', - 'isAvailable': true, - 'name': 'iOS 13.0' - }, - { - 'bundlePath': - '/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.4.simruntime', - 'buildversion': '17L255', - 'runtimeRoot': - '/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.4.simruntime/Contents/Resources/RuntimeRoot', - 'identifier': 'com.apple.CoreSimulator.SimRuntime.iOS-13-4', - 'version': '13.4', - 'isAvailable': true, - 'name': 'iOS 13.4' - }, - { - 'bundlePath': - '/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime', - 'buildversion': '17T531', - 'runtimeRoot': - '/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime/Contents/Resources/RuntimeRoot', - 'identifier': 'com.apple.CoreSimulator.SimRuntime.watchOS-6-2', - 'version': '6.2.1', - 'isAvailable': true, - 'name': 'watchOS 6.2' - } - ], - 'devices': { - 'com.apple.CoreSimulator.SimRuntime.iOS-13-4': >[ - { - 'dataPath': - '/Users/xxx/Library/Developer/CoreSimulator/Devices/2706BBEB-1E01-403E-A8E9-70E8E5A24774/data', - 'logPath': - '/Users/xxx/Library/Logs/CoreSimulator/2706BBEB-1E01-403E-A8E9-70E8E5A24774', - 'udid': '2706BBEB-1E01-403E-A8E9-70E8E5A24774', - 'isAvailable': true, - 'deviceTypeIdentifier': - 'com.apple.CoreSimulator.SimDeviceType.iPhone-8', - 'state': 'Shutdown', - 'name': 'iPhone 8' - }, - { - 'dataPath': - '/Users/xxx/Library/Developer/CoreSimulator/Devices/1E76A0FD-38AC-4537-A989-EA639D7D012A/data', - 'logPath': - '/Users/xxx/Library/Logs/CoreSimulator/1E76A0FD-38AC-4537-A989-EA639D7D012A', - 'udid': expectedDeviceId, - 'isAvailable': true, - 'deviceTypeIdentifier': - 'com.apple.CoreSimulator.SimDeviceType.iPhone-8-Plus', - 'state': 'Shutdown', - 'name': 'iPhone 8 Plus' - } - ] - } - }; - - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(stdout: jsonEncode(devices)), - ]; - - expect(await xcode.findBestAvailableIphoneSimulator(), expectedDeviceId); - }); - - test('ignores non-iOS runtimes', () async { - // Note: This uses `dynamic` deliberately, and should not be updated to - // Object, in order to ensure that the code correctly handles this return - // type from JSON decoding. - final Map devices = { - 'runtimes': >[ - { - 'bundlePath': - '/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime', - 'buildversion': '17T531', - 'runtimeRoot': - '/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime/Contents/Resources/RuntimeRoot', - 'identifier': 'com.apple.CoreSimulator.SimRuntime.watchOS-6-2', - 'version': '6.2.1', - 'isAvailable': true, - 'name': 'watchOS 6.2' - } - ], - 'devices': { - 'com.apple.CoreSimulator.SimRuntime.watchOS-6-2': - >[ - { - 'dataPath': - '/Users/xxx/Library/Developer/CoreSimulator/Devices/1E76A0FD-38AC-4537-A989-EA639D7D012A/data', - 'logPath': - '/Users/xxx/Library/Logs/CoreSimulator/1E76A0FD-38AC-4537-A989-EA639D7D012A', - 'udid': '1E76A0FD-38AC-4537-A989-EA639D7D012A', - 'isAvailable': true, - 'deviceTypeIdentifier': - 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-38mm', - 'state': 'Shutdown', - 'name': 'Apple Watch' - } - ] - } - }; - - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(stdout: jsonEncode(devices)), - ]; - - expect(await xcode.findBestAvailableIphoneSimulator(), null); - }); - - test('returns null if simctl fails', () async { - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(exitCode: 1), - ]; - - expect(await xcode.findBestAvailableIphoneSimulator(), null); - }); - }); - - group('runXcodeBuild', () { - test('handles minimal arguments', () async { - final Directory directory = const LocalFileSystem().currentDirectory; - - final int exitCode = await xcode.runXcodeBuild( - directory, - workspace: 'A.xcworkspace', - scheme: 'AScheme', - ); - - expect(exitCode, 0); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'build', - '-workspace', - 'A.xcworkspace', - '-scheme', - 'AScheme', - ], - directory.path), - ])); - }); - - test('handles all arguments', () async { - final Directory directory = const LocalFileSystem().currentDirectory; - - final int exitCode = await xcode.runXcodeBuild(directory, - actions: ['action1', 'action2'], - workspace: 'A.xcworkspace', - scheme: 'AScheme', - configuration: 'Debug', - extraFlags: ['-a', '-b', 'c=d']); - - expect(exitCode, 0); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'action1', - 'action2', - '-workspace', - 'A.xcworkspace', - '-scheme', - 'AScheme', - '-configuration', - 'Debug', - '-a', - '-b', - 'c=d', - ], - directory.path), - ])); - }); - - test('returns error codes', () async { - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(exitCode: 1), - ]; - final Directory directory = const LocalFileSystem().currentDirectory; - - final int exitCode = await xcode.runXcodeBuild( - directory, - workspace: 'A.xcworkspace', - scheme: 'AScheme', - ); - - expect(exitCode, 1); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'build', - '-workspace', - 'A.xcworkspace', - '-scheme', - 'AScheme', - ], - directory.path), - ])); - }); - }); - - group('projectHasTarget', () { - test('returns true when present', () async { - const String stdout = ''' -{ - "project" : { - "configurations" : [ - "Debug", - "Release" - ], - "name" : "Runner", - "schemes" : [ - "Runner" - ], - "targets" : [ - "Runner", - "RunnerTests", - "RunnerUITests" - ] - } -}'''; - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(stdout: stdout), - ]; - - final Directory project = - const LocalFileSystem().directory('/foo.xcodeproj'); - expect(await xcode.projectHasTarget(project, 'RunnerTests'), true); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - [ - 'xcodebuild', - '-list', - '-json', - '-project', - project.path, - ], - null), - ])); - }); - - test('returns false when not present', () async { - const String stdout = ''' -{ - "project" : { - "configurations" : [ - "Debug", - "Release" - ], - "name" : "Runner", - "schemes" : [ - "Runner" - ], - "targets" : [ - "Runner", - "RunnerUITests" - ] - } -}'''; - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(stdout: stdout), - ]; - - final Directory project = - const LocalFileSystem().directory('/foo.xcodeproj'); - expect(await xcode.projectHasTarget(project, 'RunnerTests'), false); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - [ - 'xcodebuild', - '-list', - '-json', - '-project', - project.path, - ], - null), - ])); - }); - - test('returns null for unexpected output', () async { - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(stdout: '{}'), - ]; - - final Directory project = - const LocalFileSystem().directory('/foo.xcodeproj'); - expect(await xcode.projectHasTarget(project, 'RunnerTests'), null); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - [ - 'xcodebuild', - '-list', - '-json', - '-project', - project.path, - ], - null), - ])); - }); - - test('returns null for invalid output', () async { - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(stdout: ':)'), - ]; - - final Directory project = - const LocalFileSystem().directory('/foo.xcodeproj'); - expect(await xcode.projectHasTarget(project, 'RunnerTests'), null); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - [ - 'xcodebuild', - '-list', - '-json', - '-project', - project.path, - ], - null), - ])); - }); - - test('returns null for failure', () async { - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(exitCode: 1), // xcodebuild -list - ]; - - final Directory project = - const LocalFileSystem().directory('/foo.xcodeproj'); - expect(await xcode.projectHasTarget(project, 'RunnerTests'), null); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - [ - 'xcodebuild', - '-list', - '-json', - '-project', - project.path, - ], - null), - ])); - }); - }); -} diff --git a/script/tool/test/create_all_packages_app_command_test.dart b/script/tool/test/create_all_packages_app_command_test.dart deleted file mode 100644 index 54551cbc3712..000000000000 --- a/script/tool/test/create_all_packages_app_command_test.dart +++ /dev/null @@ -1,256 +0,0 @@ -// 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 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/local.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/create_all_packages_app_command.dart'; -import 'package:platform/platform.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - late CommandRunner runner; - late CreateAllPackagesAppCommand command; - late FileSystem fileSystem; - late Directory testRoot; - late Directory packagesDir; - late RecordingProcessRunner processRunner; - - setUp(() { - // Since the core of this command is a call to 'flutter create', the test - // has to use the real filesystem. Put everything possible in a unique - // temporary to minimize effect on the host system. - fileSystem = const LocalFileSystem(); - testRoot = fileSystem.systemTempDirectory.createTempSync(); - packagesDir = testRoot.childDirectory('packages'); - processRunner = RecordingProcessRunner(); - - command = CreateAllPackagesAppCommand( - packagesDir, - processRunner: processRunner, - pluginsRoot: testRoot, - ); - runner = CommandRunner( - 'create_all_test', 'Test for $CreateAllPackagesAppCommand'); - runner.addCommand(command); - }); - - tearDown(() { - testRoot.deleteSync(recursive: true); - }); - - group('non-macOS host', () { - setUp(() { - command = CreateAllPackagesAppCommand( - packagesDir, - processRunner: processRunner, - // Set isWindows or not based on the actual host, so that - // `flutterCommand` works, since these tests actually call 'flutter'. - // The important thing is that isMacOS always returns false. - platform: MockPlatform(isWindows: const LocalPlatform().isWindows), - pluginsRoot: testRoot, - ); - runner = CommandRunner( - 'create_all_test', 'Test for $CreateAllPackagesAppCommand'); - runner.addCommand(command); - }); - - test('pubspec includes all plugins', () async { - createFakePlugin('plugina', packagesDir); - createFakePlugin('pluginb', packagesDir); - createFakePlugin('pluginc', packagesDir); - - await runCapturingPrint(runner, ['create-all-packages-app']); - final List pubspec = command.app.pubspecFile.readAsLinesSync(); - - expect( - pubspec, - containsAll([ - contains(RegExp('path: .*/packages/plugina')), - contains(RegExp('path: .*/packages/pluginb')), - contains(RegExp('path: .*/packages/pluginc')), - ])); - }); - - test('pubspec has overrides for all plugins', () async { - createFakePlugin('plugina', packagesDir); - createFakePlugin('pluginb', packagesDir); - createFakePlugin('pluginc', packagesDir); - - await runCapturingPrint(runner, ['create-all-packages-app']); - final List pubspec = command.app.pubspecFile.readAsLinesSync(); - - expect( - pubspec, - containsAllInOrder([ - contains('dependency_overrides:'), - contains(RegExp('path: .*/packages/plugina')), - contains(RegExp('path: .*/packages/pluginb')), - contains(RegExp('path: .*/packages/pluginc')), - ])); - }); - - test('pubspec preserves existing Dart SDK version', () async { - const String baselineProjectName = 'baseline'; - final Directory baselineProjectDirectory = - testRoot.childDirectory(baselineProjectName); - io.Process.runSync( - getFlutterCommand(const LocalPlatform()), - [ - 'create', - '--template=app', - '--project-name=$baselineProjectName', - baselineProjectDirectory.path, - ], - ); - final Pubspec baselinePubspec = - RepositoryPackage(baselineProjectDirectory).parsePubspec(); - - createFakePlugin('plugina', packagesDir); - - await runCapturingPrint(runner, ['create-all-packages-app']); - final Pubspec generatedPubspec = command.app.parsePubspec(); - - const String dartSdkKey = 'sdk'; - expect(generatedPubspec.environment?[dartSdkKey], - baselinePubspec.environment?[dartSdkKey]); - }); - - test('macOS deployment target is modified in pbxproj', () async { - createFakePlugin('plugina', packagesDir); - - await runCapturingPrint(runner, ['create-all-packages-app']); - final List pbxproj = command.app - .platformDirectory(FlutterPlatform.macos) - .childDirectory('Runner.xcodeproj') - .childFile('project.pbxproj') - .readAsLinesSync(); - - expect( - pbxproj, - everyElement((String line) => - !line.contains('MACOSX_DEPLOYMENT_TARGET') || - line.contains('10.15'))); - }); - - test('calls flutter pub get', () async { - createFakePlugin('plugina', packagesDir); - - await runCapturingPrint(runner, ['create-all-packages-app']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(const LocalPlatform()), - const ['pub', 'get'], - testRoot.childDirectory('all_packages').path), - ])); - }, - // See comment about Windows in create_all_packages_app_command.dart - skip: io.Platform.isWindows); - - test('fails if flutter pub get fails', () async { - createFakePlugin('plugina', packagesDir); - - processRunner.mockProcessesForExecutable[ - getFlutterCommand(const LocalPlatform())] = [ - MockProcess(exitCode: 1) - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['create-all-packages-app'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - "Failed to generate native build files via 'flutter pub get'"), - ])); - }, - // See comment about Windows in create_all_packages_app_command.dart - skip: io.Platform.isWindows); - - test('handles --output-dir', () async { - createFakePlugin('plugina', packagesDir); - - final Directory customOutputDir = - fileSystem.systemTempDirectory.createTempSync(); - await runCapturingPrint(runner, [ - 'create-all-packages-app', - '--output-dir=${customOutputDir.path}' - ]); - - expect(command.app.path, - customOutputDir.childDirectory('all_packages').path); - }); - - test('logs exclusions', () async { - createFakePlugin('plugina', packagesDir); - createFakePlugin('pluginb', packagesDir); - createFakePlugin('pluginc', packagesDir); - - final List output = await runCapturingPrint(runner, - ['create-all-packages-app', '--exclude=pluginb,pluginc']); - - expect( - output, - containsAllInOrder([ - 'Exluding the following plugins from the combined build:', - ' pluginb', - ' pluginc', - ])); - }); - }); - - group('macOS host', () { - setUp(() { - command = CreateAllPackagesAppCommand( - packagesDir, - processRunner: processRunner, - platform: MockPlatform(isMacOS: true), - pluginsRoot: testRoot, - ); - runner = CommandRunner( - 'create_all_test', 'Test for $CreateAllPackagesAppCommand'); - runner.addCommand(command); - }); - - test('macOS deployment target is modified in Podfile', () async { - createFakePlugin('plugina', packagesDir); - - final File podfileFile = RepositoryPackage( - command.packagesDir.parent.childDirectory('all_packages')) - .platformDirectory(FlutterPlatform.macos) - .childFile('Podfile'); - podfileFile.createSync(recursive: true); - podfileFile.writeAsStringSync(""" -platform :osx, '10.11' -# some other line -"""); - - await runCapturingPrint(runner, ['create-all-packages-app']); - final List podfile = command.app - .platformDirectory(FlutterPlatform.macos) - .childFile('Podfile') - .readAsLinesSync(); - - expect( - podfile, - everyElement((String line) => - !line.contains('platform :osx') || line.contains("'10.15'"))); - }, - // Podfile is only generated (and thus only edited) on macOS. - skip: !io.Platform.isMacOS); - }); -} diff --git a/script/tool/test/custom_test_command_test.dart b/script/tool/test/custom_test_command_test.dart deleted file mode 100644 index 8b0c021b1255..000000000000 --- a/script/tool/test/custom_test_command_test.dart +++ /dev/null @@ -1,328 +0,0 @@ -// 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 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/custom_test_command.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late RecordingProcessRunner processRunner; - late CommandRunner runner; - - group('posix', () { - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final CustomTestCommand analyzeCommand = CustomTestCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner( - 'custom_test_command', 'Test for custom_test_command'); - runner.addCommand(analyzeCommand); - }); - - test('runs both new and legacy when both are present', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, extraFiles: [ - 'tool/run_tests.dart', - 'run_tests.sh', - ]); - - final List output = - await runCapturingPrint(runner, ['custom-test']); - - expect( - processRunner.recordedCalls, - containsAll([ - ProcessCall(package.directory.childFile('run_tests.sh').path, - const [], package.path), - ProcessCall('dart', const ['run', 'tool/run_tests.dart'], - package.path), - ])); - - expect( - output, - containsAllInOrder([ - contains('Ran for 1 package(s)'), - ])); - }); - - test('runs when only new is present', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - extraFiles: ['tool/run_tests.dart']); - - final List output = - await runCapturingPrint(runner, ['custom-test']); - - expect( - processRunner.recordedCalls, - containsAll([ - ProcessCall('dart', const ['run', 'tool/run_tests.dart'], - package.path), - ])); - - expect( - output, - containsAllInOrder([ - contains('Ran for 1 package(s)'), - ])); - }); - - test('runs pub get before running Dart test script', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - extraFiles: ['tool/run_tests.dart']); - - await runCapturingPrint(runner, ['custom-test']); - - expect( - processRunner.recordedCalls, - containsAll([ - ProcessCall('dart', const ['pub', 'get'], package.path), - ProcessCall('dart', const ['run', 'tool/run_tests.dart'], - package.path), - ])); - }); - - test('runs when only legacy is present', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - extraFiles: ['run_tests.sh']); - - final List output = - await runCapturingPrint(runner, ['custom-test']); - - expect( - processRunner.recordedCalls, - containsAll([ - ProcessCall(package.directory.childFile('run_tests.sh').path, - const [], package.path), - ])); - - expect( - output, - containsAllInOrder([ - contains('Ran for 1 package(s)'), - ])); - }); - - test('skips when neither is present', () async { - createFakePackage('a_package', packagesDir); - - final List output = - await runCapturingPrint(runner, ['custom-test']); - - expect(processRunner.recordedCalls, isEmpty); - - expect( - output, - containsAllInOrder([ - contains('Skipped 1 package(s)'), - ])); - }); - - test('fails if new fails', () async { - createFakePackage('a_package', packagesDir, extraFiles: [ - 'tool/run_tests.dart', - 'run_tests.sh', - ]); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(), // pub get - MockProcess(exitCode: 1), // test script - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['custom-test'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains('a_package') - ])); - }); - - test('fails if pub get fails', () async { - createFakePackage('a_package', packagesDir, extraFiles: [ - 'tool/run_tests.dart', - 'run_tests.sh', - ]); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['custom-test'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains('a_package:\n' - ' Unable to get script dependencies') - ])); - }); - - test('fails if legacy fails', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, extraFiles: [ - 'tool/run_tests.dart', - 'run_tests.sh', - ]); - - processRunner.mockProcessesForExecutable[ - package.directory.childFile('run_tests.sh').path] = [ - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['custom-test'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains('a_package') - ])); - }); - }); - - group('Windows', () { - setUp(() { - fileSystem = MemoryFileSystem(style: FileSystemStyle.windows); - mockPlatform = MockPlatform(isWindows: true); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final CustomTestCommand analyzeCommand = CustomTestCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner( - 'custom_test_command', 'Test for custom_test_command'); - runner.addCommand(analyzeCommand); - }); - - test('runs new and skips old when both are present', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, extraFiles: [ - 'tool/run_tests.dart', - 'run_tests.sh', - ]); - - final List output = - await runCapturingPrint(runner, ['custom-test']); - - expect( - processRunner.recordedCalls, - containsAll([ - ProcessCall('dart', const ['run', 'tool/run_tests.dart'], - package.path), - ])); - - expect( - output, - containsAllInOrder([ - contains('Ran for 1 package(s)'), - ])); - }); - - test('runs when only new is present', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - extraFiles: ['tool/run_tests.dart']); - - final List output = - await runCapturingPrint(runner, ['custom-test']); - - expect( - processRunner.recordedCalls, - containsAll([ - ProcessCall('dart', const ['run', 'tool/run_tests.dart'], - package.path), - ])); - - expect( - output, - containsAllInOrder([ - contains('Ran for 1 package(s)'), - ])); - }); - - test('skips package when only legacy is present', () async { - createFakePackage('a_package', packagesDir, - extraFiles: ['run_tests.sh']); - - final List output = - await runCapturingPrint(runner, ['custom-test']); - - expect(processRunner.recordedCalls, isEmpty); - - expect( - output, - containsAllInOrder([ - contains('run_tests.sh is not supported on Windows'), - contains('Skipped 1 package(s)'), - ])); - }); - - test('fails if new fails', () async { - createFakePackage('a_package', packagesDir, extraFiles: [ - 'tool/run_tests.dart', - 'run_tests.sh', - ]); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(), // pub get - MockProcess(exitCode: 1), // test script - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['custom-test'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains('a_package') - ])); - }); - }); -} diff --git a/script/tool/test/dependabot_check_command_test.dart b/script/tool/test/dependabot_check_command_test.dart deleted file mode 100644 index 39dd8f4fcb92..000000000000 --- a/script/tool/test/dependabot_check_command_test.dart +++ /dev/null @@ -1,141 +0,0 @@ -// 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 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/dependabot_check_command.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -import 'common/package_command_test.mocks.dart'; -import 'util.dart'; - -void main() { - late CommandRunner runner; - late FileSystem fileSystem; - late Directory root; - late Directory packagesDir; - - setUp(() { - fileSystem = MemoryFileSystem(); - root = fileSystem.currentDirectory; - packagesDir = root.childDirectory('packages'); - - final MockGitDir gitDir = MockGitDir(); - when(gitDir.path).thenReturn(root.path); - - final DependabotCheckCommand command = DependabotCheckCommand( - packagesDir, - gitDir: gitDir, - ); - runner = CommandRunner( - 'dependabot_test', 'Test for $DependabotCheckCommand'); - runner.addCommand(command); - }); - - void setDependabotCoverage({ - Iterable gradleDirs = const [], - }) { - final Iterable gradleEntries = - gradleDirs.map((String directory) => ''' - - package-ecosystem: "gradle" - directory: "/$directory" - schedule: - interval: "daily" -'''); - final File configFile = - root.childDirectory('.github').childFile('dependabot.yml'); - configFile.createSync(recursive: true); - configFile.writeAsStringSync(''' -version: 2 -updates: -${gradleEntries.join('\n')} -'''); - } - - test('skips with no supported ecosystems', () async { - setDependabotCoverage(); - createFakePackage('a_package', packagesDir); - - final List output = - await runCapturingPrint(runner, ['dependabot-check']); - - expect( - output, - containsAllInOrder([ - contains('SKIPPING: No supported package ecosystems'), - ])); - }); - - test('fails for app missing Gradle coverage', () async { - setDependabotCoverage(); - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - package.directory - .childDirectory('example') - .childDirectory('android') - .childDirectory('app') - .createSync(recursive: true); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['dependabot-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Missing Gradle coverage.'), - contains('a_package/example:\n' - ' Missing Gradle coverage') - ])); - }); - - test('fails for plugin missing Gradle coverage', () async { - setDependabotCoverage(); - final RepositoryPackage plugin = createFakePlugin('a_plugin', packagesDir); - plugin.directory.childDirectory('android').createSync(recursive: true); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['dependabot-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Missing Gradle coverage.'), - contains('a_plugin:\n' - ' Missing Gradle coverage') - ])); - }); - - test('passes for correct Gradle coverage', () async { - setDependabotCoverage(gradleDirs: [ - 'packages/a_plugin/android', - 'packages/a_plugin/example/android/app', - ]); - final RepositoryPackage plugin = createFakePlugin('a_plugin', packagesDir); - // Test the plugin. - plugin.directory.childDirectory('android').createSync(recursive: true); - // And its example app. - plugin.directory - .childDirectory('example') - .childDirectory('android') - .childDirectory('app') - .createSync(recursive: true); - - final List output = - await runCapturingPrint(runner, ['dependabot-check']); - - expect(output, - containsAllInOrder([contains('Ran for 2 package(s)')])); - }); -} diff --git a/script/tool/test/drive_examples_command_test.dart b/script/tool/test/drive_examples_command_test.dart deleted file mode 100644 index 0b6082098ae8..000000000000 --- a/script/tool/test/drive_examples_command_test.dart +++ /dev/null @@ -1,1257 +0,0 @@ -// 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 'dart:convert'; -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; -import 'package:flutter_plugin_tools/src/drive_examples_command.dart'; -import 'package:platform/platform.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -const String _fakeIOSDevice = '67d5c3d1-8bdf-46ad-8f6b-b00e2a972dda'; -const String _fakeAndroidDevice = 'emulator-1234'; - -void main() { - group('test drive_example_command', () { - late FileSystem fileSystem; - late Platform mockPlatform; - late Directory packagesDir; - late CommandRunner runner; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final DriveExamplesCommand command = DriveExamplesCommand(packagesDir, - processRunner: processRunner, platform: mockPlatform); - - runner = CommandRunner( - 'drive_examples_command', 'Test for drive_example_command'); - runner.addCommand(command); - }); - - void setMockFlutterDevicesOutput({ - bool hasIOSDevice = true, - bool hasAndroidDevice = true, - bool includeBanner = false, - }) { - const String updateBanner = ''' -╔════════════════════════════════════════════════════════════════════════════╗ -║ A new version of Flutter is available! ║ -║ ║ -║ To update to the latest version, run "flutter upgrade". ║ -╚════════════════════════════════════════════════════════════════════════════╝ -'''; - final List devices = [ - if (hasIOSDevice) '{"id": "$_fakeIOSDevice", "targetPlatform": "ios"}', - if (hasAndroidDevice) - '{"id": "$_fakeAndroidDevice", "targetPlatform": "android-x86"}', - ]; - final String output = - '''${includeBanner ? updateBanner : ''}[${devices.join(',')}]'''; - - final MockProcess mockDevicesProcess = - MockProcess(stdout: output, stdoutEncoding: utf8); - processRunner - .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] = - [mockDevicesProcess]; - } - - test('fails if no platforms are provided', () async { - setMockFlutterDevicesOutput(); - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Exactly one of'), - ]), - ); - }); - - test('fails if multiple platforms are provided', () async { - setMockFlutterDevicesOutput(); - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples', '--ios', '--macos'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Exactly one of'), - ]), - ); - }); - - test('fails for iOS if no iOS devices are present', () async { - setMockFlutterDevicesOutput(hasIOSDevice: false); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples', '--ios'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No iOS devices'), - ]), - ); - }); - - test('handles flutter tool banners when checking devices', () async { - createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/integration_test.dart', - 'example/integration_test/foo_test.dart', - 'example/ios/ios.m', - ], - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - setMockFlutterDevicesOutput(includeBanner: true); - final List output = - await runCapturingPrint(runner, ['drive-examples', '--ios']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No issues found!'), - ]), - ); - }); - - test('fails for iOS if getting devices fails', () async { - // Simulate failure from `flutter devices`. - processRunner - .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] = - [MockProcess(exitCode: 1)]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples', '--ios'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No iOS devices'), - ]), - ); - }); - - test('fails for Android if no Android devices are present', () async { - setMockFlutterDevicesOutput(hasAndroidDevice: false); - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples', '--android'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No Android devices'), - ]), - ); - }); - - test('driving under folder "test_driver"', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - 'example/android/android.java', - 'example/ios/ios.m', - ], - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - setMockFlutterDevicesOutput(); - final List output = - await runCapturingPrint(runner, ['drive-examples', '--ios']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['devices', '--machine'], null), - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - _fakeIOSDevice, - '--driver', - 'test_driver/plugin_test.dart', - '--target', - 'test_driver/plugin.dart' - ], - pluginExampleDirectory.path), - ])); - }); - - test('driving under folder "test_driver" when test files are missing"', - () async { - setMockFlutterDevicesOutput(); - createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/android/android.java', - 'example/ios/ios.m', - ], - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples', '--android'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No driver tests were run (1 example(s) found).'), - contains('No test files for example/test_driver/plugin_test.dart'), - ]), - ); - }); - - test('a plugin without any integration test files is reported as an error', - () async { - setMockFlutterDevicesOutput(); - createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/lib/main.dart', - 'example/android/android.java', - 'example/ios/ios.m', - ], - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples', '--android'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No driver tests were run (1 example(s) found).'), - contains('No tests ran'), - ]), - ); - }); - - test('integration tests using test(...) fail validation', () async { - setMockFlutterDevicesOutput(); - final RepositoryPackage package = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/integration_test.dart', - 'example/integration_test/foo_test.dart', - 'example/android/android.java', - ], - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - package.directory - .childDirectory('example') - .childDirectory('integration_test') - .childFile('foo_test.dart') - .writeAsStringSync(''' - test('this is the wrong kind of test!'), () { - ... - } -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples', '--android'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('foo_test.dart failed validation'), - ]), - ); - }); - - test( - 'driving under folder "test_driver" when targets are under "integration_test"', - () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/integration_test.dart', - 'example/integration_test/bar_test.dart', - 'example/integration_test/foo_test.dart', - 'example/integration_test/ignore_me.dart', - 'example/android/android.java', - 'example/ios/ios.m', - ], - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - setMockFlutterDevicesOutput(); - final List output = - await runCapturingPrint(runner, ['drive-examples', '--ios']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['devices', '--machine'], null), - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - _fakeIOSDevice, - '--driver', - 'test_driver/integration_test.dart', - '--target', - 'integration_test/bar_test.dart', - ], - pluginExampleDirectory.path), - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - _fakeIOSDevice, - '--driver', - 'test_driver/integration_test.dart', - '--target', - 'integration_test/foo_test.dart', - ], - pluginExampleDirectory.path), - ])); - }); - - test('driving when plugin does not support Linux is a no-op', () async { - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - ]); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--linux', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Skipping unsupported platform linux...'), - contains('No issues found!'), - ]), - ); - - // Output should be empty since running drive-examples --linux on a non-Linux - // plugin is a no-op. - expect(processRunner.recordedCalls, []); - }); - - test('driving on a Linux plugin', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - 'example/linux/linux.cc', - ], - platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline), - }, - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--linux', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - 'linux', - '--driver', - 'test_driver/plugin_test.dart', - '--target', - 'test_driver/plugin.dart' - ], - pluginExampleDirectory.path), - ])); - }); - - test('driving when plugin does not suppport macOS is a no-op', () async { - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - ]); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--macos', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Skipping unsupported platform macos...'), - contains('No issues found!'), - ]), - ); - - // Output should be empty since running drive-examples --macos with no macos - // implementation is a no-op. - expect(processRunner.recordedCalls, []); - }); - - test('driving on a macOS plugin', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - 'example/macos/macos.swift', - ], - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--macos', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - 'macos', - '--driver', - 'test_driver/plugin_test.dart', - '--target', - 'test_driver/plugin.dart' - ], - pluginExampleDirectory.path), - ])); - }); - - test('driving when plugin does not suppport web is a no-op', () async { - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - ]); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--web', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No issues found!'), - ]), - ); - - // Output should be empty since running drive-examples --web on a non-web - // plugin is a no-op. - expect(processRunner.recordedCalls, []); - }); - - test('driving a web plugin', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - 'example/web/index.html', - ], - platformSupport: { - platformWeb: const PlatformDetails(PlatformSupport.inline), - }, - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--web', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - 'web-server', - '--web-port=7357', - '--browser-name=chrome', - '--driver', - 'test_driver/plugin_test.dart', - '--target', - 'test_driver/plugin.dart' - ], - pluginExampleDirectory.path), - ])); - }); - - test('driving a web plugin with CHROME_EXECUTABLE', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - 'example/web/index.html', - ], - platformSupport: { - platformWeb: const PlatformDetails(PlatformSupport.inline), - }, - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - mockPlatform.environment['CHROME_EXECUTABLE'] = '/path/to/chrome'; - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--web', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - 'web-server', - '--web-port=7357', - '--browser-name=chrome', - '--chrome-binary=/path/to/chrome', - '--driver', - 'test_driver/plugin_test.dart', - '--target', - 'test_driver/plugin.dart' - ], - pluginExampleDirectory.path), - ])); - }); - - test('driving when plugin does not suppport Windows is a no-op', () async { - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - ]); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--windows', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Skipping unsupported platform windows...'), - contains('No issues found!'), - ]), - ); - - // Output should be empty since running drive-examples --windows on a - // non-Windows plugin is a no-op. - expect(processRunner.recordedCalls, []); - }); - - test('driving on a Windows plugin', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - 'example/windows/windows.cpp', - ], - platformSupport: { - platformWindows: const PlatformDetails(PlatformSupport.inline), - }, - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--windows', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - 'windows', - '--driver', - 'test_driver/plugin_test.dart', - '--target', - 'test_driver/plugin.dart' - ], - pluginExampleDirectory.path), - ])); - }); - - test('driving on an Android plugin', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - 'example/android/android.java', - ], - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - }, - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - setMockFlutterDevicesOutput(); - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--android', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['devices', '--machine'], null), - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - _fakeAndroidDevice, - '--driver', - 'test_driver/plugin_test.dart', - '--target', - 'test_driver/plugin.dart' - ], - pluginExampleDirectory.path), - ])); - }); - - test('driving when plugin does not support Android is no-op', () async { - createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - ], - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - setMockFlutterDevicesOutput(); - final List output = await runCapturingPrint( - runner, ['drive-examples', '--android']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Skipping unsupported platform android...'), - contains('No issues found!'), - ]), - ); - - // Output should be empty other than the device query. - expect(processRunner.recordedCalls, [ - ProcessCall(getFlutterCommand(mockPlatform), - const ['devices', '--machine'], null), - ]); - }); - - test('driving when plugin does not support iOS is no-op', () async { - createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - ], - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - setMockFlutterDevicesOutput(); - final List output = - await runCapturingPrint(runner, ['drive-examples', '--ios']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Skipping unsupported platform ios...'), - contains('No issues found!'), - ]), - ); - - // Output should be empty other than the device query. - expect(processRunner.recordedCalls, [ - ProcessCall(getFlutterCommand(mockPlatform), - const ['devices', '--machine'], null), - ]); - }); - - test('platform interface plugins are silently skipped', () async { - createFakePlugin('aplugin_platform_interface', packagesDir, - examples: []); - - setMockFlutterDevicesOutput(); - final List output = await runCapturingPrint( - runner, ['drive-examples', '--macos']); - - expect( - output, - containsAllInOrder([ - contains('Running for aplugin_platform_interface'), - contains( - 'SKIPPING: Platform interfaces are not expected to have integration tests.'), - contains('No issues found!'), - ]), - ); - - // Output should be empty since it's skipped. - expect(processRunner.recordedCalls, []); - }); - - test('enable-experiment flag', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - 'example/android/android.java', - 'example/ios/ios.m', - ], - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - setMockFlutterDevicesOutput(); - await runCapturingPrint(runner, [ - 'drive-examples', - '--ios', - '--enable-experiment=exp1', - ]); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['devices', '--machine'], null), - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - _fakeIOSDevice, - '--enable-experiment=exp1', - '--driver', - 'test_driver/plugin_test.dart', - '--target', - 'test_driver/plugin.dart' - ], - pluginExampleDirectory.path), - ])); - }); - - test('fails when no example is present', () async { - createFakePlugin( - 'plugin', - packagesDir, - examples: [], - platformSupport: { - platformWeb: const PlatformDetails(PlatformSupport.inline), - }, - ); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples', '--web'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No driver tests were run (0 example(s) found).'), - contains('The following packages had errors:'), - contains(' plugin:\n' - ' No tests ran (use --exclude if this is intentional)'), - ]), - ); - }); - - test('fails when no driver is present', () async { - createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/integration_test/bar_test.dart', - 'example/integration_test/foo_test.dart', - 'example/web/index.html', - ], - platformSupport: { - platformWeb: const PlatformDetails(PlatformSupport.inline), - }, - ); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples', '--web'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No driver tests found for plugin/example'), - contains('No driver tests were run (1 example(s) found).'), - contains('The following packages had errors:'), - contains(' plugin:\n' - ' No tests ran (use --exclude if this is intentional)'), - ]), - ); - }); - - test('fails when no integration tests are present', () async { - createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/integration_test.dart', - 'example/web/index.html', - ], - platformSupport: { - platformWeb: const PlatformDetails(PlatformSupport.inline), - }, - ); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples', '--web'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Found example/test_driver/integration_test.dart, but no ' - 'integration_test/*_test.dart files.'), - contains('No driver tests were run (1 example(s) found).'), - contains('The following packages had errors:'), - contains(' plugin:\n' - ' No test files for example/test_driver/integration_test.dart\n' - ' No tests ran (use --exclude if this is intentional)'), - ]), - ); - }); - - test('reports test failures', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/integration_test.dart', - 'example/integration_test/bar_test.dart', - 'example/integration_test/foo_test.dart', - 'example/macos/macos.swift', - ], - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - // Simulate failure from `flutter drive`. - processRunner - .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] = - [ - // No mock for 'devices', since it's running for macOS. - MockProcess(exitCode: 1), // 'drive' #1 - MockProcess(exitCode: 1), // 'drive' #2 - ]; - - Error? commandError; - final List output = - await runCapturingPrint(runner, ['drive-examples', '--macos'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('The following packages had errors:'), - contains(' plugin:\n' - ' example/integration_test/bar_test.dart\n' - ' example/integration_test/foo_test.dart'), - ]), - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - 'macos', - '--driver', - 'test_driver/integration_test.dart', - '--target', - 'integration_test/bar_test.dart', - ], - pluginExampleDirectory.path), - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - 'macos', - '--driver', - 'test_driver/integration_test.dart', - '--target', - 'integration_test/foo_test.dart', - ], - pluginExampleDirectory.path), - ])); - }); - - group('packages', () { - test('can be driven', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, extraFiles: [ - 'example/integration_test/foo_test.dart', - 'example/test_driver/integration_test.dart', - 'example/web/index.html', - ]); - final Directory exampleDirectory = getExampleDir(package); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--web', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for a_package'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - 'web-server', - '--web-port=7357', - '--browser-name=chrome', - '--driver', - 'test_driver/integration_test.dart', - '--target', - 'integration_test/foo_test.dart' - ], - exampleDirectory.path), - ])); - }); - - test('are skipped when example does not support platform', () async { - createFakePackage('a_package', packagesDir, - isFlutter: true, - extraFiles: [ - 'example/integration_test/foo_test.dart', - 'example/test_driver/integration_test.dart', - ]); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--web', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for a_package'), - contains('Skipping a_package/example; does not support any ' - 'requested platforms'), - contains('SKIPPING: No example supports requested platform(s).'), - ]), - ); - - expect(processRunner.recordedCalls.isEmpty, true); - }); - - test('drive only supported examples if there is more than one', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - isFlutter: true, - examples: [ - 'with_web', - 'without_web' - ], - extraFiles: [ - 'example/with_web/integration_test/foo_test.dart', - 'example/with_web/test_driver/integration_test.dart', - 'example/with_web/web/index.html', - 'example/without_web/integration_test/foo_test.dart', - 'example/without_web/test_driver/integration_test.dart', - ]); - final Directory supportedExampleDirectory = - getExampleDir(package).childDirectory('with_web'); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--web', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for a_package'), - contains( - 'Skipping a_package/example/without_web; does not support any requested platforms.'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - 'web-server', - '--web-port=7357', - '--browser-name=chrome', - '--driver', - 'test_driver/integration_test.dart', - '--target', - 'integration_test/foo_test.dart' - ], - supportedExampleDirectory.path), - ])); - }); - - test('are skipped when there is no integration testing', () async { - createFakePackage('a_package', packagesDir, - isFlutter: true, extraFiles: ['example/web/index.html']); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--web', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for a_package'), - contains('SKIPPING: No example is configured for driver tests.'), - ]), - ); - - expect(processRunner.recordedCalls.isEmpty, true); - }); - }); - }); -} diff --git a/script/tool/test/federation_safety_check_command_test.dart b/script/tool/test/federation_safety_check_command_test.dart deleted file mode 100644 index 6b6b1a514531..000000000000 --- a/script/tool/test/federation_safety_check_command_test.dart +++ /dev/null @@ -1,411 +0,0 @@ -// 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 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/federation_safety_check_command.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -import 'common/package_command_test.mocks.dart'; -import 'mocks.dart'; -import 'util.dart'; - -void main() { - FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late CommandRunner runner; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - - final MockGitDir gitDir = MockGitDir(); - when(gitDir.path).thenReturn(packagesDir.parent.path); - when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) - .thenAnswer((Invocation invocation) { - final List arguments = - invocation.positionalArguments[0]! as List; - // Route git calls through the process runner, to make mock output - // consistent with other processes. Attach the first argument to the - // command to make targeting the mock results easier. - final String gitCommand = arguments.removeAt(0); - return processRunner.run('git-$gitCommand', arguments); - }); - - processRunner = RecordingProcessRunner(); - final FederationSafetyCheckCommand command = FederationSafetyCheckCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - gitDir: gitDir); - - runner = CommandRunner('federation_safety_check_command', - 'Test for $FederationSafetyCheckCommand'); - runner.addCommand(command); - }); - - test('skips non-plugin packages', () async { - final RepositoryPackage package = createFakePackage('foo', packagesDir); - - final String changedFileOutput = [ - package.libDirectory.childFile('foo.dart'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - final List output = - await runCapturingPrint(runner, ['federation-safety-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for foo...'), - contains('Not a plugin'), - contains('Skipped 1 package(s)'), - ]), - ); - }); - - test('skips unfederated plugins', () async { - final RepositoryPackage package = createFakePlugin('foo', packagesDir); - - final String changedFileOutput = [ - package.libDirectory.childFile('foo.dart'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - final List output = - await runCapturingPrint(runner, ['federation-safety-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for foo...'), - contains('Not a federated plugin'), - contains('Skipped 1 package(s)'), - ]), - ); - }); - - test('skips interface packages', () async { - final Directory pluginGroupDir = packagesDir.childDirectory('foo'); - final RepositoryPackage platformInterface = - createFakePlugin('foo_platform_interface', pluginGroupDir); - - final String changedFileOutput = [ - platformInterface.libDirectory.childFile('foo.dart'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - final List output = - await runCapturingPrint(runner, ['federation-safety-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for foo_platform_interface...'), - contains('Platform interface changes are not validated.'), - contains('Skipped 1 package(s)'), - ]), - ); - }); - - test('allows changes to just an interface package', () async { - final Directory pluginGroupDir = packagesDir.childDirectory('foo'); - final RepositoryPackage platformInterface = - createFakePlugin('foo_platform_interface', pluginGroupDir); - createFakePlugin('foo', pluginGroupDir); - createFakePlugin('foo_ios', pluginGroupDir); - createFakePlugin('foo_android', pluginGroupDir); - - final String changedFileOutput = [ - platformInterface.libDirectory.childFile('foo.dart'), - platformInterface.pubspecFile, - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - final List output = - await runCapturingPrint(runner, ['federation-safety-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for foo/foo...'), - contains('No Dart changes.'), - contains('Running for foo_android...'), - contains('No Dart changes.'), - contains('Running for foo_ios...'), - contains('No Dart changes.'), - contains('Running for foo_platform_interface...'), - contains('Ran for 3 package(s)'), - contains('Skipped 1 package(s)'), - ]), - ); - expect( - output, - isNot(contains([ - contains('No published changes for foo_platform_interface'), - ])), - ); - }); - - test('allows changes to multiple non-interface packages', () async { - final Directory pluginGroupDir = packagesDir.childDirectory('foo'); - final RepositoryPackage appFacing = createFakePlugin('foo', pluginGroupDir); - final RepositoryPackage implementation = - createFakePlugin('foo_bar', pluginGroupDir); - createFakePlugin('foo_platform_interface', pluginGroupDir); - - final String changedFileOutput = [ - appFacing.libDirectory.childFile('foo.dart'), - implementation.libDirectory.childFile('foo.dart'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - final List output = - await runCapturingPrint(runner, ['federation-safety-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for foo/foo...'), - contains('No published changes for foo_platform_interface.'), - contains('Running for foo_bar...'), - contains('No published changes for foo_platform_interface.'), - ]), - ); - }); - - test( - 'fails on changes to interface and non-interface packages in the same plugin', - () async { - final Directory pluginGroupDir = packagesDir.childDirectory('foo'); - final RepositoryPackage appFacing = createFakePlugin('foo', pluginGroupDir); - final RepositoryPackage implementation = - createFakePlugin('foo_bar', pluginGroupDir); - final RepositoryPackage platformInterface = - createFakePlugin('foo_platform_interface', pluginGroupDir); - - final String changedFileOutput = [ - appFacing.libDirectory.childFile('foo.dart'), - implementation.libDirectory.childFile('foo.dart'), - platformInterface.pubspecFile, - platformInterface.libDirectory.childFile('foo.dart'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['federation-safety-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running for foo/foo...'), - contains('Dart changes are not allowed to other packages in foo in the ' - 'same PR as changes to public Dart code in foo_platform_interface, ' - 'as this can cause accidental breaking changes to be missed by ' - 'automated checks. Please split the changes to these two packages ' - 'into separate PRs.'), - contains('Running for foo_bar...'), - contains('Dart changes are not allowed to other packages in foo'), - contains('The following packages had errors:'), - contains('foo/foo:\n' - ' foo_platform_interface changed.'), - contains('foo_bar:\n' - ' foo_platform_interface changed.'), - ]), - ); - }); - - test('ignores test-only changes to interface packages', () async { - final Directory pluginGroupDir = packagesDir.childDirectory('foo'); - final RepositoryPackage appFacing = createFakePlugin('foo', pluginGroupDir); - final RepositoryPackage implementation = - createFakePlugin('foo_bar', pluginGroupDir); - final RepositoryPackage platformInterface = - createFakePlugin('foo_platform_interface', pluginGroupDir); - - final String changedFileOutput = [ - appFacing.libDirectory.childFile('foo.dart'), - implementation.libDirectory.childFile('foo.dart'), - platformInterface.pubspecFile, - platformInterface.testDirectory.childFile('foo.dart'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - final List output = - await runCapturingPrint(runner, ['federation-safety-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for foo/foo...'), - contains('No public code changes for foo_platform_interface.'), - contains('Running for foo_bar...'), - contains('No public code changes for foo_platform_interface.'), - ]), - ); - }); - - test('ignores unpublished changes to interface packages', () async { - final Directory pluginGroupDir = packagesDir.childDirectory('foo'); - final RepositoryPackage appFacing = createFakePlugin('foo', pluginGroupDir); - final RepositoryPackage implementation = - createFakePlugin('foo_bar', pluginGroupDir); - final RepositoryPackage platformInterface = - createFakePlugin('foo_platform_interface', pluginGroupDir); - - final String changedFileOutput = [ - appFacing.libDirectory.childFile('foo.dart'), - implementation.libDirectory.childFile('foo.dart'), - platformInterface.pubspecFile, - platformInterface.libDirectory.childFile('foo.dart'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - // Simulate no change to the version in the interface's pubspec.yaml. - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: platformInterface.pubspecFile.readAsStringSync()), - ]; - - final List output = - await runCapturingPrint(runner, ['federation-safety-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for foo/foo...'), - contains('No published changes for foo_platform_interface.'), - contains('Running for foo_bar...'), - contains('No published changes for foo_platform_interface.'), - ]), - ); - }); - - test('allows things that look like mass changes, with warning', () async { - final Directory pluginGroupDir = packagesDir.childDirectory('foo'); - final RepositoryPackage appFacing = createFakePlugin('foo', pluginGroupDir); - final RepositoryPackage implementation = - createFakePlugin('foo_bar', pluginGroupDir); - final RepositoryPackage platformInterface = - createFakePlugin('foo_platform_interface', pluginGroupDir); - - final RepositoryPackage otherPlugin1 = createFakePlugin('bar', packagesDir); - final RepositoryPackage otherPlugin2 = createFakePlugin('baz', packagesDir); - - final String changedFileOutput = [ - appFacing.libDirectory.childFile('foo.dart'), - implementation.libDirectory.childFile('foo.dart'), - platformInterface.pubspecFile, - platformInterface.libDirectory.childFile('foo.dart'), - otherPlugin1.libDirectory.childFile('bar.dart'), - otherPlugin2.libDirectory.childFile('baz.dart'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - final List output = - await runCapturingPrint(runner, ['federation-safety-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for foo/foo...'), - contains( - 'Ignoring potentially dangerous change, as this appears to be a mass change.'), - contains('Running for foo_bar...'), - contains( - 'Ignoring potentially dangerous change, as this appears to be a mass change.'), - contains('Ran for 2 package(s) (2 with warnings)'), - ]), - ); - }); - - test('handles top-level files that match federated package heuristics', - () async { - final RepositoryPackage plugin = createFakePlugin('foo', packagesDir); - - final String changedFileOutput = [ - // This should be picked up as a change to 'foo', and not crash. - plugin.directory.childFile('foo_bar.baz'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - final List output = - await runCapturingPrint(runner, ['federation-safety-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for foo...'), - ]), - ); - }); - - test('handles deletion of an entire plugin', () async { - // Simulate deletion, in the form of diffs for packages that don't exist in - // the filesystem. - final String changedFileOutput = [ - packagesDir.childDirectory('foo').childFile('pubspec.yaml'), - packagesDir - .childDirectory('foo') - .childDirectory('lib') - .childFile('foo.dart'), - packagesDir - .childDirectory('foo_platform_interface') - .childFile('pubspec.yaml'), - packagesDir - .childDirectory('foo_platform_interface') - .childDirectory('lib') - .childFile('foo.dart'), - packagesDir.childDirectory('foo_web').childFile('pubspec.yaml'), - packagesDir - .childDirectory('foo_web') - .childDirectory('lib') - .childFile('foo.dart'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - final List output = - await runCapturingPrint(runner, ['federation-safety-check']); - - expect( - output, - containsAllInOrder([ - contains('Ran for 0 package(s)'), - ]), - ); - }); -} diff --git a/script/tool/test/firebase_test_lab_command_test.dart b/script/tool/test/firebase_test_lab_command_test.dart deleted file mode 100644 index 68ea62b2334f..000000000000 --- a/script/tool/test/firebase_test_lab_command_test.dart +++ /dev/null @@ -1,795 +0,0 @@ -// 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 'dart:io'; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/file_utils.dart'; -import 'package:flutter_plugin_tools/src/firebase_test_lab_command.dart'; -import 'package:path/path.dart' as p; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - group('FirebaseTestLabCommand', () { - FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late CommandRunner runner; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final FirebaseTestLabCommand command = FirebaseTestLabCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner( - 'firebase_test_lab_command', 'Test for $FirebaseTestLabCommand'); - runner.addCommand(command); - }); - - void writeJavaTestFile(RepositoryPackage plugin, String relativeFilePath, - {String runnerClass = 'FlutterTestRunner'}) { - childFileWithSubcomponents( - plugin.directory, p.posix.split(relativeFilePath)) - .writeAsStringSync(''' -@DartIntegrationTest -@RunWith($runnerClass.class) -public class MainActivityTest { - @Rule - public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); -} -'''); - } - - test('fails if gcloud auth fails', () async { - processRunner.mockProcessesForExecutable['gcloud'] = [ - MockProcess(exitCode: 1) - ]; - - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/integration_test/foo_test.dart', - 'example/android/gradlew', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['firebase-test-lab'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to activate gcloud account.'), - ])); - }); - - test('retries gcloud set', () async { - processRunner.mockProcessesForExecutable['gcloud'] = [ - MockProcess(), // auth - MockProcess(exitCode: 1), // config - ]; - - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/integration_test/foo_test.dart', - 'example/android/gradlew', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - final List output = - await runCapturingPrint(runner, ['firebase-test-lab']); - - expect( - output, - containsAllInOrder([ - contains( - 'Warning: gcloud config set returned a non-zero exit code. Continuing anyway.'), - ])); - }); - - test('only runs gcloud configuration once', () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir, extraFiles: [ - 'test/plugin_test.dart', - 'example/integration_test/foo_test.dart', - 'example/android/gradlew', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin1, javaTestFileRelativePath); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir, extraFiles: [ - 'test/plugin_test.dart', - 'example/integration_test/bar_test.dart', - 'example/android/gradlew', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin2, javaTestFileRelativePath); - - final List output = await runCapturingPrint(runner, [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - '--device', - 'model=seoul,version=26', - '--test-run-id', - 'testRunId', - '--build-id', - 'buildId', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin1'), - contains('Firebase project configured.'), - contains('Testing example/integration_test/foo_test.dart...'), - contains('Running for plugin2'), - contains('Testing example/integration_test/bar_test.dart...'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'gcloud', - 'auth activate-service-account --key-file=${Platform.environment['HOME']}/gcloud-service-key.json' - .split(' '), - null), - ProcessCall( - 'gcloud', 'config set project flutter-cirrus'.split(' '), null), - ProcessCall( - '/packages/plugin1/example/android/gradlew', - 'app:assembleAndroidTest -Pverbose=true'.split(' '), - '/packages/plugin1/example/android'), - ProcessCall( - '/packages/plugin1/example/android/gradlew', - 'app:assembleDebug -Pverbose=true -Ptarget=/packages/plugin1/example/integration_test/foo_test.dart' - .split(' '), - '/packages/plugin1/example/android'), - ProcessCall( - 'gcloud', - 'firebase test android run --type instrumentation --app build/app/outputs/apk/debug/app-debug.apk --test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk --timeout 7m --results-bucket=gs://flutter_cirrus_testlab --results-dir=plugins_android_test/plugin1/buildId/testRunId/example/0/ --device model=redfin,version=30 --device model=seoul,version=26' - .split(' '), - '/packages/plugin1/example'), - ProcessCall( - '/packages/plugin2/example/android/gradlew', - 'app:assembleAndroidTest -Pverbose=true'.split(' '), - '/packages/plugin2/example/android'), - ProcessCall( - '/packages/plugin2/example/android/gradlew', - 'app:assembleDebug -Pverbose=true -Ptarget=/packages/plugin2/example/integration_test/bar_test.dart' - .split(' '), - '/packages/plugin2/example/android'), - ProcessCall( - 'gcloud', - 'firebase test android run --type instrumentation --app build/app/outputs/apk/debug/app-debug.apk --test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk --timeout 7m --results-bucket=gs://flutter_cirrus_testlab --results-dir=plugins_android_test/plugin2/buildId/testRunId/example/0/ --device model=redfin,version=30 --device model=seoul,version=26' - .split(' '), - '/packages/plugin2/example'), - ]), - ); - }); - - test('runs integration tests', () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'test/plugin_test.dart', - 'example/integration_test/bar_test.dart', - 'example/integration_test/foo_test.dart', - 'example/integration_test/should_not_run.dart', - 'example/android/gradlew', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - final List output = await runCapturingPrint(runner, [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - '--device', - 'model=seoul,version=26', - '--test-run-id', - 'testRunId', - '--build-id', - 'buildId', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Firebase project configured.'), - contains('Testing example/integration_test/bar_test.dart...'), - contains('Testing example/integration_test/foo_test.dart...'), - ]), - ); - expect(output, isNot(contains('test/plugin_test.dart'))); - expect(output, - isNot(contains('example/integration_test/should_not_run.dart'))); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'gcloud', - 'auth activate-service-account --key-file=${Platform.environment['HOME']}/gcloud-service-key.json' - .split(' '), - null), - ProcessCall( - 'gcloud', 'config set project flutter-cirrus'.split(' '), null), - ProcessCall( - '/packages/plugin/example/android/gradlew', - 'app:assembleAndroidTest -Pverbose=true'.split(' '), - '/packages/plugin/example/android'), - ProcessCall( - '/packages/plugin/example/android/gradlew', - 'app:assembleDebug -Pverbose=true -Ptarget=/packages/plugin/example/integration_test/bar_test.dart' - .split(' '), - '/packages/plugin/example/android'), - ProcessCall( - 'gcloud', - 'firebase test android run --type instrumentation --app build/app/outputs/apk/debug/app-debug.apk --test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk --timeout 7m --results-bucket=gs://flutter_cirrus_testlab --results-dir=plugins_android_test/plugin/buildId/testRunId/example/0/ --device model=redfin,version=30 --device model=seoul,version=26' - .split(' '), - '/packages/plugin/example'), - ProcessCall( - '/packages/plugin/example/android/gradlew', - 'app:assembleDebug -Pverbose=true -Ptarget=/packages/plugin/example/integration_test/foo_test.dart' - .split(' '), - '/packages/plugin/example/android'), - ProcessCall( - 'gcloud', - 'firebase test android run --type instrumentation --app build/app/outputs/apk/debug/app-debug.apk --test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk --timeout 7m --results-bucket=gs://flutter_cirrus_testlab --results-dir=plugins_android_test/plugin/buildId/testRunId/example/1/ --device model=redfin,version=30 --device model=seoul,version=26' - .split(' '), - '/packages/plugin/example'), - ]), - ); - }); - - test('runs for all examples', () async { - const List examples = ['example1', 'example2']; - const String javaTestFileExampleRelativePath = - 'android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - examples: examples, - extraFiles: [ - for (final String example in examples) ...[ - 'example/$example/integration_test/a_test.dart', - 'example/$example/android/gradlew', - 'example/$example/$javaTestFileExampleRelativePath', - ], - ]); - for (final String example in examples) { - writeJavaTestFile( - plugin, 'example/$example/$javaTestFileExampleRelativePath'); - } - - final List output = await runCapturingPrint(runner, [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - '--device', - 'model=seoul,version=26', - '--test-run-id', - 'testRunId', - '--build-id', - 'buildId', - ]); - - expect( - output, - containsAllInOrder([ - contains('Testing example/example1/integration_test/a_test.dart...'), - contains('Testing example/example2/integration_test/a_test.dart...'), - ]), - ); - - expect( - processRunner.recordedCalls, - containsAll([ - ProcessCall( - '/packages/plugin/example/example1/android/gradlew', - 'app:assembleDebug -Pverbose=true -Ptarget=/packages/plugin/example/example1/integration_test/a_test.dart' - .split(' '), - '/packages/plugin/example/example1/android'), - ProcessCall( - 'gcloud', - 'firebase test android run --type instrumentation --app build/app/outputs/apk/debug/app-debug.apk --test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk --timeout 7m --results-bucket=gs://flutter_cirrus_testlab --results-dir=plugins_android_test/plugin/buildId/testRunId/example1/0/ --device model=redfin,version=30 --device model=seoul,version=26' - .split(' '), - '/packages/plugin/example/example1'), - ProcessCall( - '/packages/plugin/example/example2/android/gradlew', - 'app:assembleDebug -Pverbose=true -Ptarget=/packages/plugin/example/example2/integration_test/a_test.dart' - .split(' '), - '/packages/plugin/example/example2/android'), - ProcessCall( - 'gcloud', - 'firebase test android run --type instrumentation --app build/app/outputs/apk/debug/app-debug.apk --test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk --timeout 7m --results-bucket=gs://flutter_cirrus_testlab --results-dir=plugins_android_test/plugin/buildId/testRunId/example2/0/ --device model=redfin,version=30 --device model=seoul,version=26' - .split(' '), - '/packages/plugin/example/example2'), - ]), - ); - }); - - test('fails if a test fails twice', () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/integration_test/bar_test.dart', - 'example/integration_test/foo_test.dart', - 'example/android/gradlew', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - processRunner.mockProcessesForExecutable['gcloud'] = [ - MockProcess(), // auth - MockProcess(), // config - MockProcess(exitCode: 1), // integration test #1 - MockProcess(exitCode: 1), // integration test #1 retry - MockProcess(), // integration test #2 - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, - [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - ], - errorHandler: (Error e) { - commandError = e; - }, - ); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Testing example/integration_test/bar_test.dart...'), - contains('Testing example/integration_test/foo_test.dart...'), - contains('plugin:\n' - ' example/integration_test/bar_test.dart failed tests'), - ]), - ); - }); - - test('passes with warning if a test fails once, then passes on retry', - () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/integration_test/bar_test.dart', - 'example/integration_test/foo_test.dart', - 'example/android/gradlew', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - processRunner.mockProcessesForExecutable['gcloud'] = [ - MockProcess(), // auth - MockProcess(), // config - MockProcess(exitCode: 1), // integration test #1 - MockProcess(), // integration test #1 retry - MockProcess(), // integration test #2 - ]; - - final List output = await runCapturingPrint(runner, [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - ]); - - expect( - output, - containsAllInOrder([ - contains('Testing example/integration_test/bar_test.dart...'), - contains('bar_test.dart failed on attempt 1. Retrying...'), - contains('Testing example/integration_test/foo_test.dart...'), - contains('Ran for 1 package(s) (1 with warnings)'), - ]), - ); - }); - - test('fails for packages with no androidTest directory', () async { - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/integration_test/foo_test.dart', - 'example/android/gradlew', - ]); - - Error? commandError; - final List output = await runCapturingPrint( - runner, - [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - ], - errorHandler: (Error e) { - commandError = e; - }, - ); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No androidTest directory found.'), - contains('The following packages had errors:'), - contains('plugin:\n' - ' No tests ran (use --exclude if this is intentional).'), - ]), - ); - }); - - test('fails for packages with no integration test files', () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/android/gradlew', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - Error? commandError; - final List output = await runCapturingPrint( - runner, - [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - ], - errorHandler: (Error e) { - commandError = e; - }, - ); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No integration tests were run'), - contains('The following packages had errors:'), - contains('plugin:\n' - ' No tests ran (use --exclude if this is intentional).'), - ]), - ); - }); - - test('fails for packages with no integration_test runner', () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'test/plugin_test.dart', - 'example/integration_test/bar_test.dart', - 'example/integration_test/foo_test.dart', - 'example/integration_test/should_not_run.dart', - 'example/android/gradlew', - javaTestFileRelativePath, - ]); - // Use the wrong @RunWith annotation. - writeJavaTestFile(plugin, javaTestFileRelativePath, - runnerClass: 'AndroidJUnit4.class'); - - Error? commandError; - final List output = await runCapturingPrint( - runner, - [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - ], - errorHandler: (Error e) { - commandError = e; - }, - ); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No integration_test runner found. ' - 'See the integration_test package README for setup instructions.'), - contains('plugin:\n' - ' No integration_test runner.'), - ]), - ); - }); - - test('skips packages with no android directory', () async { - createFakePackage('package', packagesDir, extraFiles: [ - 'example/integration_test/foo_test.dart', - ]); - - final List output = await runCapturingPrint(runner, [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for package'), - contains('No examples support Android'), - ]), - ); - expect(output, - isNot(contains('Testing example/integration_test/foo_test.dart...'))); - - expect( - processRunner.recordedCalls, - orderedEquals([]), - ); - }); - - test('builds if gradlew is missing', () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/integration_test/foo_test.dart', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - final List output = await runCapturingPrint(runner, [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - '--test-run-id', - 'testRunId', - '--build-id', - 'buildId', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Running flutter build apk...'), - contains('Firebase project configured.'), - contains('Testing example/integration_test/foo_test.dart...'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'flutter', - 'build apk'.split(' '), - '/packages/plugin/example/android', - ), - ProcessCall( - 'gcloud', - 'auth activate-service-account --key-file=${Platform.environment['HOME']}/gcloud-service-key.json' - .split(' '), - null), - ProcessCall( - 'gcloud', 'config set project flutter-cirrus'.split(' '), null), - ProcessCall( - '/packages/plugin/example/android/gradlew', - 'app:assembleAndroidTest -Pverbose=true'.split(' '), - '/packages/plugin/example/android'), - ProcessCall( - '/packages/plugin/example/android/gradlew', - 'app:assembleDebug -Pverbose=true -Ptarget=/packages/plugin/example/integration_test/foo_test.dart' - .split(' '), - '/packages/plugin/example/android'), - ProcessCall( - 'gcloud', - 'firebase test android run --type instrumentation --app build/app/outputs/apk/debug/app-debug.apk --test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk --timeout 7m --results-bucket=gs://flutter_cirrus_testlab --results-dir=plugins_android_test/plugin/buildId/testRunId/example/0/ --device model=redfin,version=30' - .split(' '), - '/packages/plugin/example'), - ]), - ); - }); - - test('fails if building to generate gradlew fails', () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/integration_test/foo_test.dart', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - processRunner.mockProcessesForExecutable['flutter'] = [ - MockProcess(exitCode: 1) // flutter build - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, - [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - ], - errorHandler: (Error e) { - commandError = e; - }, - ); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to build example apk'), - ])); - }); - - test('fails if assembleAndroidTest fails', () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/integration_test/foo_test.dart', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - final String gradlewPath = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android) - .childFile('gradlew') - .path; - processRunner.mockProcessesForExecutable[gradlewPath] = [ - MockProcess(exitCode: 1) - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, - [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - ], - errorHandler: (Error e) { - commandError = e; - }, - ); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to assemble androidTest'), - ])); - }); - - test('fails if assembleDebug fails', () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/integration_test/foo_test.dart', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - final String gradlewPath = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android) - .childFile('gradlew') - .path; - processRunner.mockProcessesForExecutable[gradlewPath] = [ - MockProcess(), // assembleAndroidTest - MockProcess(exitCode: 1), // assembleDebug - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, - [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - ], - errorHandler: (Error e) { - commandError = e; - }, - ); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Could not build example/integration_test/foo_test.dart'), - contains('The following packages had errors:'), - contains(' plugin:\n' - ' example/integration_test/foo_test.dart failed to build'), - ])); - }); - - test('experimental flag', () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/integration_test/foo_test.dart', - 'example/android/gradlew', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - await runCapturingPrint(runner, [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - '--test-run-id', - 'testRunId', - '--build-id', - 'buildId', - '--enable-experiment=exp1', - ]); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'gcloud', - 'auth activate-service-account --key-file=${Platform.environment['HOME']}/gcloud-service-key.json' - .split(' '), - null), - ProcessCall( - 'gcloud', 'config set project flutter-cirrus'.split(' '), null), - ProcessCall( - '/packages/plugin/example/android/gradlew', - 'app:assembleAndroidTest -Pverbose=true -Pextra-front-end-options=--enable-experiment%3Dexp1 -Pextra-gen-snapshot-options=--enable-experiment%3Dexp1' - .split(' '), - '/packages/plugin/example/android'), - ProcessCall( - '/packages/plugin/example/android/gradlew', - 'app:assembleDebug -Pverbose=true -Ptarget=/packages/plugin/example/integration_test/foo_test.dart -Pextra-front-end-options=--enable-experiment%3Dexp1 -Pextra-gen-snapshot-options=--enable-experiment%3Dexp1' - .split(' '), - '/packages/plugin/example/android'), - ProcessCall( - 'gcloud', - 'firebase test android run --type instrumentation --app build/app/outputs/apk/debug/app-debug.apk --test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk --timeout 7m --results-bucket=gs://flutter_cirrus_testlab --results-dir=plugins_android_test/plugin/buildId/testRunId/example/0/ --device model=redfin,version=30' - .split(' '), - '/packages/plugin/example'), - ]), - ); - }); - }); -} diff --git a/script/tool/test/fix_command_test.dart b/script/tool/test/fix_command_test.dart deleted file mode 100644 index 16061d2206cd..000000000000 --- a/script/tool/test/fix_command_test.dart +++ /dev/null @@ -1,78 +0,0 @@ -// 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 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/fix_command.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late RecordingProcessRunner processRunner; - late CommandRunner runner; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final FixCommand command = FixCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner('fix_command', 'Test for fix_command'); - runner.addCommand(command); - }); - - test('runs fix in top-level packages and subpackages', () async { - final RepositoryPackage package = createFakePackage('a', packagesDir); - final RepositoryPackage plugin = createFakePlugin('b', packagesDir); - - await runCapturingPrint(runner, ['fix']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall('dart', const ['fix', '--apply'], package.path), - ProcessCall('dart', const ['fix', '--apply'], - package.getExamples().first.path), - ProcessCall('dart', const ['fix', '--apply'], plugin.path), - ProcessCall('dart', const ['fix', '--apply'], - plugin.getExamples().first.path), - ])); - }); - - test('fails if "dart fix" fails', () async { - createFakePlugin('foo', packagesDir); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint(runner, ['fix'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to automatically fix package.'), - ]), - ); - }); -} diff --git a/script/tool/test/format_command_test.dart b/script/tool/test/format_command_test.dart deleted file mode 100644 index 634a996bccc6..000000000000 --- a/script/tool/test/format_command_test.dart +++ /dev/null @@ -1,663 +0,0 @@ -// 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 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/file_utils.dart'; -import 'package:flutter_plugin_tools/src/format_command.dart'; -import 'package:path/path.dart' as p; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late RecordingProcessRunner processRunner; - late FormatCommand analyzeCommand; - late CommandRunner runner; - late String javaFormatPath; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - analyzeCommand = FormatCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - // Create the java formatter file that the command checks for, to avoid - // a download. - final p.Context path = analyzeCommand.path; - javaFormatPath = path.join(path.dirname(path.fromUri(mockPlatform.script)), - 'google-java-format-1.3-all-deps.jar'); - fileSystem.file(javaFormatPath).createSync(recursive: true); - - runner = CommandRunner('format_command', 'Test for format_command'); - runner.addCommand(analyzeCommand); - }); - - /// Returns a modified version of a list of [relativePaths] that are relative - /// to [package] to instead be relative to [packagesDir]. - List getPackagesDirRelativePaths( - RepositoryPackage package, List relativePaths) { - final p.Context path = analyzeCommand.path; - final String relativeBase = - path.relative(package.path, from: packagesDir.path); - return relativePaths - .map((String relativePath) => path.join(relativeBase, relativePath)) - .toList(); - } - - /// Returns a list of [count] relative paths to pass to [createFakePlugin] - /// or [createFakePackage] with name [packageName] such that each path will - /// be 99 characters long relative to [packagesDir]. - /// - /// This is for each of testing batching, since it means each file will - /// consume 100 characters of the batch length. - List get99CharacterPathExtraFiles(String packageName, int count) { - final int padding = 99 - - packageName.length - - 1 - // the path separator after the package name - 1 - // the path separator after the padding - 10; // the file name - const int filenameBase = 10000; - - final p.Context path = analyzeCommand.path; - return [ - for (int i = filenameBase; i < filenameBase + count; ++i) - path.join('a' * padding, '$i.dart'), - ]; - } - - test('formats .dart files', () async { - const List files = [ - 'lib/a.dart', - 'lib/src/b.dart', - 'lib/src/c.dart', - ]; - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - extraFiles: files, - ); - - await runCapturingPrint(runner, ['format']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'dart', - ['format', ...getPackagesDirRelativePaths(plugin, files)], - packagesDir.path), - ])); - }); - - test('does not format .dart files with pragma', () async { - const List formattedFiles = [ - 'lib/a.dart', - 'lib/src/b.dart', - 'lib/src/c.dart', - ]; - const String unformattedFile = 'lib/src/d.dart'; - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - extraFiles: [ - ...formattedFiles, - unformattedFile, - ], - ); - - final p.Context posixContext = p.posix; - childFileWithSubcomponents( - plugin.directory, posixContext.split(unformattedFile)) - .writeAsStringSync( - '// copyright bla bla\n// This file is hand-formatted.\ncode...'); - - await runCapturingPrint(runner, ['format']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'dart', - [ - 'format', - ...getPackagesDirRelativePaths(plugin, formattedFiles) - ], - packagesDir.path), - ])); - }); - - test('fails if dart format fails', () async { - const List files = [ - 'lib/a.dart', - 'lib/src/b.dart', - 'lib/src/c.dart', - ]; - createFakePlugin('a_plugin', packagesDir, extraFiles: files); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(exitCode: 1) - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['format'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Failed to format Dart files: exit code 1.'), - ])); - }); - - test('formats .java files', () async { - const List files = [ - 'android/src/main/java/io/flutter/plugins/a_plugin/a.java', - 'android/src/main/java/io/flutter/plugins/a_plugin/b.java', - ]; - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - extraFiles: files, - ); - - await runCapturingPrint(runner, ['format']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - const ProcessCall('java', ['-version'], null), - ProcessCall( - 'java', - [ - '-jar', - javaFormatPath, - '--replace', - ...getPackagesDirRelativePaths(plugin, files) - ], - packagesDir.path), - ])); - }); - - test('fails with a clear message if Java is not in the path', () async { - const List files = [ - 'android/src/main/java/io/flutter/plugins/a_plugin/a.java', - 'android/src/main/java/io/flutter/plugins/a_plugin/b.java', - ]; - createFakePlugin('a_plugin', packagesDir, extraFiles: files); - - processRunner.mockProcessesForExecutable['java'] = [ - MockProcess(exitCode: 1) - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['format'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Unable to run "java". Make sure that it is in your path, or ' - 'provide a full path with --java.'), - ])); - }); - - test('fails if Java formatter fails', () async { - const List files = [ - 'android/src/main/java/io/flutter/plugins/a_plugin/a.java', - 'android/src/main/java/io/flutter/plugins/a_plugin/b.java', - ]; - createFakePlugin('a_plugin', packagesDir, extraFiles: files); - - processRunner.mockProcessesForExecutable['java'] = [ - MockProcess(), // check for working java - MockProcess(exitCode: 1), // format - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['format'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Failed to format Java files: exit code 1.'), - ])); - }); - - test('honors --java flag', () async { - const List files = [ - 'android/src/main/java/io/flutter/plugins/a_plugin/a.java', - 'android/src/main/java/io/flutter/plugins/a_plugin/b.java', - ]; - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - extraFiles: files, - ); - - await runCapturingPrint(runner, ['format', '--java=/path/to/java']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - const ProcessCall('/path/to/java', ['--version'], null), - ProcessCall( - '/path/to/java', - [ - '-jar', - javaFormatPath, - '--replace', - ...getPackagesDirRelativePaths(plugin, files) - ], - packagesDir.path), - ])); - }); - - test('formats c-ish files', () async { - const List files = [ - 'ios/Classes/Foo.h', - 'ios/Classes/Foo.m', - 'linux/foo_plugin.cc', - 'macos/Classes/Foo.h', - 'macos/Classes/Foo.mm', - 'windows/foo_plugin.cpp', - ]; - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - extraFiles: files, - ); - - await runCapturingPrint(runner, ['format']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - const ProcessCall('clang-format', ['--version'], null), - ProcessCall( - 'clang-format', - [ - '-i', - '--style=file', - ...getPackagesDirRelativePaths(plugin, files) - ], - packagesDir.path), - ])); - }); - - test('fails with a clear message if clang-format is not in the path', - () async { - const List files = [ - 'linux/foo_plugin.cc', - 'macos/Classes/Foo.h', - ]; - createFakePlugin('a_plugin', packagesDir, extraFiles: files); - - processRunner.mockProcessesForExecutable['clang-format'] = [ - MockProcess(exitCode: 1) - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['format'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to run "clang-format". Make sure that it is in your ' - 'path, or provide a full path with --clang-format.'), - ])); - }); - - test('falls back to working clang-format in the path', () async { - const List files = [ - 'linux/foo_plugin.cc', - 'macos/Classes/Foo.h', - ]; - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - extraFiles: files, - ); - - processRunner.mockProcessesForExecutable['clang-format'] = [ - MockProcess(exitCode: 1) - ]; - processRunner.mockProcessesForExecutable['which'] = [ - MockProcess( - stdout: '/usr/local/bin/clang-format\n/path/to/working-clang-format') - ]; - processRunner.mockProcessesForExecutable['/usr/local/bin/clang-format'] = - [MockProcess(exitCode: 1)]; - await runCapturingPrint(runner, ['format']); - - expect( - processRunner.recordedCalls, - containsAll([ - const ProcessCall( - '/path/to/working-clang-format', ['--version'], null), - ProcessCall( - '/path/to/working-clang-format', - [ - '-i', - '--style=file', - ...getPackagesDirRelativePaths(plugin, files) - ], - packagesDir.path), - ])); - }); - - test('honors --clang-format flag', () async { - const List files = [ - 'windows/foo_plugin.cpp', - ]; - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - extraFiles: files, - ); - - await runCapturingPrint( - runner, ['format', '--clang-format=/path/to/clang-format']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - const ProcessCall( - '/path/to/clang-format', ['--version'], null), - ProcessCall( - '/path/to/clang-format', - [ - '-i', - '--style=file', - ...getPackagesDirRelativePaths(plugin, files) - ], - packagesDir.path), - ])); - }); - - test('fails if clang-format fails', () async { - const List files = [ - 'linux/foo_plugin.cc', - 'macos/Classes/Foo.h', - ]; - createFakePlugin('a_plugin', packagesDir, extraFiles: files); - - processRunner.mockProcessesForExecutable['clang-format'] = [ - MockProcess(), // check for working clang-format - MockProcess(exitCode: 1), // format - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['format'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Failed to format C, C++, and Objective-C files: exit code 1.'), - ])); - }); - - test('skips known non-repo files', () async { - const List skipFiles = [ - '/example/build/SomeFramework.framework/Headers/SomeFramework.h', - '/example/Pods/APod.framework/Headers/APod.h', - '.dart_tool/internals/foo.cc', - '.dart_tool/internals/Bar.java', - '.dart_tool/internals/baz.dart', - ]; - const List clangFiles = ['ios/Classes/Foo.h']; - const List dartFiles = ['lib/a.dart']; - const List javaFiles = [ - 'android/src/main/java/io/flutter/plugins/a_plugin/a.java' - ]; - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - extraFiles: [ - ...skipFiles, - // Include some files that should be formatted to validate that it's - // correctly filtering even when running the commands. - ...clangFiles, - ...dartFiles, - ...javaFiles, - ], - ); - - await runCapturingPrint(runner, ['format']); - - expect( - processRunner.recordedCalls, - containsAll([ - ProcessCall( - 'clang-format', - [ - '-i', - '--style=file', - ...getPackagesDirRelativePaths(plugin, clangFiles) - ], - packagesDir.path), - ProcessCall( - 'dart', - [ - 'format', - ...getPackagesDirRelativePaths(plugin, dartFiles) - ], - packagesDir.path), - ProcessCall( - 'java', - [ - '-jar', - javaFormatPath, - '--replace', - ...getPackagesDirRelativePaths(plugin, javaFiles) - ], - packagesDir.path), - ])); - }); - - test('fails if files are changed with --fail-on-change', () async { - const List files = [ - 'linux/foo_plugin.cc', - 'macos/Classes/Foo.h', - ]; - createFakePlugin('a_plugin', packagesDir, extraFiles: files); - - const String changedFilePath = 'packages/a_plugin/linux/foo_plugin.cc'; - processRunner.mockProcessesForExecutable['git'] = [ - MockProcess(stdout: changedFilePath), - ]; - - Error? commandError; - final List output = - await runCapturingPrint(runner, ['format', '--fail-on-change'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('These files are not formatted correctly'), - contains(changedFilePath), - contains('patch -p1 < files = [ - 'linux/foo_plugin.cc', - 'macos/Classes/Foo.h', - ]; - createFakePlugin('a_plugin', packagesDir, extraFiles: files); - - processRunner.mockProcessesForExecutable['git'] = [ - MockProcess(exitCode: 1) - ]; - Error? commandError; - final List output = - await runCapturingPrint(runner, ['format', '--fail-on-change'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to determine changed files.'), - ])); - }); - - test('reports git diff failures', () async { - const List files = [ - 'linux/foo_plugin.cc', - 'macos/Classes/Foo.h', - ]; - createFakePlugin('a_plugin', packagesDir, extraFiles: files); - - const String changedFilePath = 'packages/a_plugin/linux/foo_plugin.cc'; - processRunner.mockProcessesForExecutable['git'] = [ - MockProcess(stdout: changedFilePath), // ls-files - MockProcess(exitCode: 1), // diff - ]; - - Error? commandError; - final List output = - await runCapturingPrint(runner, ['format', '--fail-on-change'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('These files are not formatted correctly'), - contains(changedFilePath), - contains('Unable to determine diff.'), - ])); - }); - - test('Batches moderately long file lists on Windows', () async { - mockPlatform.isWindows = true; - - const String pluginName = 'a_plugin'; - // -1 since the command itself takes some length. - const int batchSize = (windowsCommandLineMax ~/ 100) - 1; - - // Make the file list one file longer than would fit in the batch. - final List batch1 = - get99CharacterPathExtraFiles(pluginName, batchSize + 1); - final String extraFile = batch1.removeLast(); - - createFakePlugin( - pluginName, - packagesDir, - extraFiles: [...batch1, extraFile], - ); - - await runCapturingPrint(runner, ['format']); - - // Ensure that it was batched... - expect(processRunner.recordedCalls.length, 2); - // ... and that the spillover into the second batch was only one file. - expect( - processRunner.recordedCalls, - contains( - ProcessCall( - 'dart', - [ - 'format', - '$pluginName\\$extraFile', - ], - packagesDir.path), - )); - }); - - // Validates that the Windows limit--which is much lower than the limit on - // other platforms--isn't being used on all platforms, as that would make - // formatting slower on Linux and macOS. - test('Does not batch moderately long file lists on non-Windows', () async { - const String pluginName = 'a_plugin'; - // -1 since the command itself takes some length. - const int batchSize = (windowsCommandLineMax ~/ 100) - 1; - - // Make the file list one file longer than would fit in a Windows batch. - final List batch = - get99CharacterPathExtraFiles(pluginName, batchSize + 1); - - createFakePlugin( - pluginName, - packagesDir, - extraFiles: batch, - ); - - await runCapturingPrint(runner, ['format']); - - expect(processRunner.recordedCalls.length, 1); - }); - - test('Batches extremely long file lists on non-Windows', () async { - const String pluginName = 'a_plugin'; - // -1 since the command itself takes some length. - const int batchSize = (nonWindowsCommandLineMax ~/ 100) - 1; - - // Make the file list one file longer than would fit in the batch. - final List batch1 = - get99CharacterPathExtraFiles(pluginName, batchSize + 1); - final String extraFile = batch1.removeLast(); - - createFakePlugin( - pluginName, - packagesDir, - extraFiles: [...batch1, extraFile], - ); - - await runCapturingPrint(runner, ['format']); - - // Ensure that it was batched... - expect(processRunner.recordedCalls.length, 2); - // ... and that the spillover into the second batch was only one file. - expect( - processRunner.recordedCalls, - contains( - ProcessCall( - 'dart', - [ - 'format', - '$pluginName/$extraFile', - ], - packagesDir.path), - )); - }); -} diff --git a/script/tool/test/license_check_command_test.dart b/script/tool/test/license_check_command_test.dart deleted file mode 100644 index 09841df74e70..000000000000 --- a/script/tool/test/license_check_command_test.dart +++ /dev/null @@ -1,613 +0,0 @@ -// 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 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/license_check_command.dart'; -import 'package:mockito/mockito.dart'; -import 'package:platform/platform.dart'; -import 'package:test/test.dart'; - -import 'common/package_command_test.mocks.dart'; -import 'mocks.dart'; -import 'util.dart'; - -void main() { - group('LicenseCheckCommand', () { - late CommandRunner runner; - late FileSystem fileSystem; - late Platform platform; - late Directory root; - - setUp(() { - fileSystem = MemoryFileSystem(); - platform = MockPlatformWithSeparator(); - final Directory packagesDir = - fileSystem.currentDirectory.childDirectory('packages'); - root = packagesDir.parent; - - final MockGitDir gitDir = MockGitDir(); - when(gitDir.path).thenReturn(packagesDir.parent.path); - - final LicenseCheckCommand command = LicenseCheckCommand( - packagesDir, - platform: platform, - gitDir: gitDir, - ); - runner = - CommandRunner('license_test', 'Test for $LicenseCheckCommand'); - runner.addCommand(command); - }); - - /// Writes a copyright+license block to [file], defaulting to a standard - /// block for this repository. - /// - /// [commentString] is added to the start of each line. - /// [prefix] is added to the start of the entire block. - /// [suffix] is added to the end of the entire block. - void writeLicense( - File file, { - String comment = '// ', - String prefix = '', - String suffix = '', - String copyright = - 'Copyright 2013 The Flutter Authors. All rights reserved.', - List license = const [ - 'Use of this source code is governed by a BSD-style license that can be', - 'found in the LICENSE file.', - ], - bool useCrlf = false, - }) { - final List lines = ['$prefix$comment$copyright']; - for (final String line in license) { - lines.add('$comment$line'); - } - final String newline = useCrlf ? '\r\n' : '\n'; - file.writeAsStringSync(lines.join(newline) + suffix + newline); - } - - test('looks at only expected extensions', () async { - final Map extensions = { - 'c': true, - 'cc': true, - 'cpp': true, - 'dart': true, - 'h': true, - 'html': true, - 'java': true, - 'json': false, - 'kt': true, - 'm': true, - 'md': false, - 'mm': true, - 'png': false, - 'swift': true, - 'sh': true, - 'yaml': false, - }; - - const String filenameBase = 'a_file'; - for (final String fileExtension in extensions.keys) { - root.childFile('$filenameBase.$fileExtension').createSync(); - } - - final List output = await runCapturingPrint( - runner, ['license-check'], errorHandler: (Error e) { - // Ignore failure; the files are empty so the check is expected to fail, - // but this test isn't for that behavior. - }); - - extensions.forEach((String fileExtension, bool shouldCheck) { - final Matcher logLineMatcher = - contains('Checking $filenameBase.$fileExtension'); - expect(output, shouldCheck ? logLineMatcher : isNot(logLineMatcher)); - }); - }); - - test('ignore list overrides extension matches', () async { - final List ignoredFiles = [ - // Ignored base names. - 'flutter_export_environment.sh', - 'GeneratedPluginRegistrant.java', - 'GeneratedPluginRegistrant.m', - 'generated_plugin_registrant.cc', - 'generated_plugin_registrant.cpp', - // Ignored path suffixes. - 'foo.g.dart', - 'foo.mocks.dart', - // Ignored files. - 'resource.h', - ]; - - for (final String name in ignoredFiles) { - root.childFile(name).createSync(); - } - - final List output = - await runCapturingPrint(runner, ['license-check']); - - for (final String name in ignoredFiles) { - expect(output, isNot(contains('Checking $name'))); - } - }); - - test('ignores submodules', () async { - const String submoduleName = 'a_submodule'; - - final File submoduleSpec = root.childFile('.gitmodules'); - submoduleSpec.writeAsStringSync(''' -[submodule "$submoduleName"] - path = $submoduleName - url = https://github.com/foo/$submoduleName -'''); - - const List submoduleFiles = [ - '$submoduleName/foo.dart', - '$submoduleName/a/b/bar.dart', - '$submoduleName/LICENSE', - ]; - for (final String filePath in submoduleFiles) { - root.childFile(filePath).createSync(recursive: true); - } - - final List output = - await runCapturingPrint(runner, ['license-check']); - - for (final String filePath in submoduleFiles) { - expect(output, isNot(contains('Checking $filePath'))); - } - }); - - test('passes if all checked files have license blocks', () async { - final File checked = root.childFile('checked.cc'); - checked.createSync(); - writeLicense(checked); - final File notChecked = root.childFile('not_checked.md'); - notChecked.createSync(); - - final List output = - await runCapturingPrint(runner, ['license-check']); - - // Sanity check that the test did actually check a file. - expect( - output, - containsAllInOrder([ - contains('Checking checked.cc'), - contains('All files passed validation!'), - ])); - }); - - test('passes correct license blocks on Windows', () async { - final File checked = root.childFile('checked.cc'); - checked.createSync(); - writeLicense(checked, useCrlf: true); - - final List output = - await runCapturingPrint(runner, ['license-check']); - - // Sanity check that the test did actually check a file. - expect( - output, - containsAllInOrder([ - contains('Checking checked.cc'), - contains('All files passed validation!'), - ])); - }); - - test('handles the comment styles for all supported languages', () async { - final File fileA = root.childFile('file_a.cc'); - fileA.createSync(); - writeLicense(fileA); - final File fileB = root.childFile('file_b.sh'); - fileB.createSync(); - writeLicense(fileB, comment: '# '); - final File fileC = root.childFile('file_c.html'); - fileC.createSync(); - writeLicense(fileC, comment: '', prefix: ''); - - final List output = - await runCapturingPrint(runner, ['license-check']); - - // Sanity check that the test did actually check the files. - expect( - output, - containsAllInOrder([ - contains('Checking file_a.cc'), - contains('Checking file_b.sh'), - contains('Checking file_c.html'), - contains('All files passed validation!'), - ])); - }); - - test('fails if any checked files are missing license blocks', () async { - final File goodA = root.childFile('good.cc'); - goodA.createSync(); - writeLicense(goodA); - final File goodB = root.childFile('good.h'); - goodB.createSync(); - writeLicense(goodB); - root.childFile('bad.cc').createSync(); - root.childFile('bad.h').createSync(); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['license-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - // Failure should give information about the problematic files. - expect( - output, - containsAllInOrder([ - contains( - 'The license block for these files is missing or incorrect:'), - contains(' bad.cc'), - contains(' bad.h'), - ])); - // Failure shouldn't print the success message. - expect(output, isNot(contains(contains('All files passed validation!')))); - }); - - test('fails if any checked files are missing just the copyright', () async { - final File good = root.childFile('good.cc'); - good.createSync(); - writeLicense(good); - final File bad = root.childFile('bad.cc'); - bad.createSync(); - writeLicense(bad, copyright: ''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['license-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - // Failure should give information about the problematic files. - expect( - output, - containsAllInOrder([ - contains( - 'The license block for these files is missing or incorrect:'), - contains(' bad.cc'), - ])); - // Failure shouldn't print the success message. - expect(output, isNot(contains(contains('All files passed validation!')))); - }); - - test('fails if any checked files are missing just the license', () async { - final File good = root.childFile('good.cc'); - good.createSync(); - writeLicense(good); - final File bad = root.childFile('bad.cc'); - bad.createSync(); - writeLicense(bad, license: []); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['license-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - // Failure should give information about the problematic files. - expect( - output, - containsAllInOrder([ - contains( - 'The license block for these files is missing or incorrect:'), - contains(' bad.cc'), - ])); - // Failure shouldn't print the success message. - expect(output, isNot(contains(contains('All files passed validation!')))); - }); - - test('fails if any third-party code is not in a third_party directory', - () async { - final File thirdPartyFile = root.childFile('third_party.cc'); - thirdPartyFile.createSync(); - writeLicense(thirdPartyFile, copyright: 'Copyright 2017 Someone Else'); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['license-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - // Failure should give information about the problematic files. - expect( - output, - containsAllInOrder([ - contains( - 'The license block for these files is missing or incorrect:'), - contains(' third_party.cc'), - ])); - // Failure shouldn't print the success message. - expect(output, isNot(contains(contains('All files passed validation!')))); - }); - - test('succeeds for third-party code in a third_party directory', () async { - final File thirdPartyFile = root - .childDirectory('a_plugin') - .childDirectory('lib') - .childDirectory('src') - .childDirectory('third_party') - .childFile('file.cc'); - thirdPartyFile.createSync(recursive: true); - writeLicense(thirdPartyFile, - copyright: 'Copyright 2017 Workiva Inc.', - license: [ - 'Licensed under the Apache License, Version 2.0 (the "License");', - 'you may not use this file except in compliance with the License.' - ]); - - final List output = - await runCapturingPrint(runner, ['license-check']); - - // Sanity check that the test did actually check the file. - expect( - output, - containsAllInOrder([ - contains('Checking a_plugin/lib/src/third_party/file.cc'), - contains('All files passed validation!'), - ])); - }); - - test('allows first-party code in a third_party directory', () async { - final File firstPartyFileInThirdParty = root - .childDirectory('a_plugin') - .childDirectory('lib') - .childDirectory('src') - .childDirectory('third_party') - .childFile('first_party.cc'); - firstPartyFileInThirdParty.createSync(recursive: true); - writeLicense(firstPartyFileInThirdParty); - - final List output = - await runCapturingPrint(runner, ['license-check']); - - // Sanity check that the test did actually check the file. - expect( - output, - containsAllInOrder([ - contains('Checking a_plugin/lib/src/third_party/first_party.cc'), - contains('All files passed validation!'), - ])); - }); - - test('fails for licenses that the tool does not expect', () async { - final File good = root.childFile('good.cc'); - good.createSync(); - writeLicense(good); - final File bad = root.childDirectory('third_party').childFile('bad.cc'); - bad.createSync(recursive: true); - writeLicense(bad, license: [ - 'This program is free software: you can redistribute it and/or modify', - 'it under the terms of the GNU General Public License', - ]); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['license-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - // Failure should give information about the problematic files. - expect( - output, - containsAllInOrder([ - contains( - 'No recognized license was found for the following third-party files:'), - contains(' third_party/bad.cc'), - ])); - // Failure shouldn't print the success message. - expect(output, isNot(contains(contains('All files passed validation!')))); - }); - - test('Apache is not recognized for new authors without validation changes', - () async { - final File good = root.childFile('good.cc'); - good.createSync(); - writeLicense(good); - final File bad = root.childDirectory('third_party').childFile('bad.cc'); - bad.createSync(recursive: true); - writeLicense( - bad, - copyright: 'Copyright 2017 Some New Authors.', - license: [ - 'Licensed under the Apache License, Version 2.0 (the "License");', - 'you may not use this file except in compliance with the License.' - ], - ); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['license-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - // Failure should give information about the problematic files. - expect( - output, - containsAllInOrder([ - contains( - 'No recognized license was found for the following third-party files:'), - contains(' third_party/bad.cc'), - ])); - // Failure shouldn't print the success message. - expect(output, isNot(contains(contains('All files passed validation!')))); - }); - - test('passes if all first-party LICENSE files are correctly formatted', - () async { - final File license = root.childFile('LICENSE'); - license.createSync(); - license.writeAsStringSync(_correctLicenseFileText); - - final List output = - await runCapturingPrint(runner, ['license-check']); - - // Sanity check that the test did actually check the file. - expect( - output, - containsAllInOrder([ - contains('Checking LICENSE'), - contains('All files passed validation!'), - ])); - }); - - test('passes correct LICENSE files on Windows', () async { - final File license = root.childFile('LICENSE'); - license.createSync(); - license - .writeAsStringSync(_correctLicenseFileText.replaceAll('\n', '\r\n')); - - final List output = - await runCapturingPrint(runner, ['license-check']); - - // Sanity check that the test did actually check the file. - expect( - output, - containsAllInOrder([ - contains('Checking LICENSE'), - contains('All files passed validation!'), - ])); - }); - - test('fails if any first-party LICENSE files are incorrectly formatted', - () async { - final File license = root.childFile('LICENSE'); - license.createSync(); - license.writeAsStringSync(_incorrectLicenseFileText); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['license-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect(output, isNot(contains(contains('All files passed validation!')))); - }); - - test('ignores third-party LICENSE format', () async { - final File license = - root.childDirectory('third_party').childFile('LICENSE'); - license.createSync(recursive: true); - license.writeAsStringSync(_incorrectLicenseFileText); - - final List output = - await runCapturingPrint(runner, ['license-check']); - - // The file shouldn't be checked. - expect(output, isNot(contains(contains('Checking third_party/LICENSE')))); - }); - - test('outputs all errors at the end', () async { - root.childFile('bad.cc').createSync(); - root - .childDirectory('third_party') - .childFile('bad.cc') - .createSync(recursive: true); - final File license = root.childFile('LICENSE'); - license.createSync(); - license.writeAsStringSync(_incorrectLicenseFileText); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['license-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Checking LICENSE'), - contains('Checking bad.cc'), - contains('Checking third_party/bad.cc'), - contains( - 'The following LICENSE files do not follow the expected format:'), - contains(' LICENSE'), - contains( - 'The license block for these files is missing or incorrect:'), - contains(' bad.cc'), - contains( - 'No recognized license was found for the following third-party files:'), - contains(' third_party/bad.cc'), - ])); - }); - }); -} - -class MockPlatformWithSeparator extends MockPlatform { - @override - String get pathSeparator => isWindows ? r'\' : '/'; -} - -const String _correctLicenseFileText = ''' -Copyright 2013 The Flutter Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -'''; - -// A common incorrect version created by copying text intended for a code file, -// with comment markers. -const String _incorrectLicenseFileText = ''' -// Copyright 2013 The Flutter Authors. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -'''; diff --git a/script/tool/test/lint_android_command_test.dart b/script/tool/test/lint_android_command_test.dart deleted file mode 100644 index e4a6c5c859e4..000000000000 --- a/script/tool/test/lint_android_command_test.dart +++ /dev/null @@ -1,205 +0,0 @@ -// 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 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; -import 'package:flutter_plugin_tools/src/lint_android_command.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - group('LintAndroidCommand', () { - FileSystem fileSystem; - late Directory packagesDir; - late CommandRunner runner; - late MockPlatform mockPlatform; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - mockPlatform = MockPlatform(); - processRunner = RecordingProcessRunner(); - final LintAndroidCommand command = LintAndroidCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner( - 'lint_android_test', 'Test for $LintAndroidCommand'); - runner.addCommand(command); - }); - - test('runs gradle lint', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin1', packagesDir, extraFiles: [ - 'example/android/gradlew', - ], platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }); - - final Directory androidDir = - plugin.getExamples().first.platformDirectory(FlutterPlatform.android); - - final List output = - await runCapturingPrint(runner, ['lint-android']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - androidDir.childFile('gradlew').path, - const ['plugin1:lintDebug'], - androidDir.path, - ), - ]), - ); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin1'), - contains('No issues found!'), - ])); - }); - - test('runs on all examples', () async { - final List examples = ['example1', 'example2']; - final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, - examples: examples, - extraFiles: [ - 'example/example1/android/gradlew', - 'example/example2/android/gradlew', - ], - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }); - - final Iterable exampleAndroidDirs = plugin.getExamples().map( - (RepositoryPackage example) => - example.platformDirectory(FlutterPlatform.android)); - - final List output = - await runCapturingPrint(runner, ['lint-android']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - for (final Directory directory in exampleAndroidDirs) - ProcessCall( - directory.childFile('gradlew').path, - const ['plugin1:lintDebug'], - directory.path, - ), - ]), - ); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin1'), - contains('No issues found!'), - ])); - }); - - test('fails if gradlew is missing', () async { - createFakePlugin('plugin1', packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['lint-android'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder( - [ - contains('Build examples before linting'), - ], - )); - }); - - test('fails if linting finds issues', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin1', packagesDir, extraFiles: [ - 'example/android/gradlew', - ], platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }); - - final String gradlewPath = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android) - .childFile('gradlew') - .path; - processRunner.mockProcessesForExecutable[gradlewPath] = [ - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['lint-android'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder( - [ - contains('The following packages had errors:'), - ], - )); - }); - - test('skips non-Android plugins', () async { - createFakePlugin('plugin1', packagesDir); - - final List output = - await runCapturingPrint(runner, ['lint-android']); - - expect( - output, - containsAllInOrder( - [ - contains( - 'SKIPPING: Plugin does not have an Android implementation.') - ], - )); - }); - - test('skips non-inline plugins', () async { - createFakePlugin('plugin1', packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.federated) - }); - - final List output = - await runCapturingPrint(runner, ['lint-android']); - - expect( - output, - containsAllInOrder( - [ - contains( - 'SKIPPING: Plugin does not have an Android implementation.') - ], - )); - }); - }); -} diff --git a/script/tool/test/list_command_test.dart b/script/tool/test/list_command_test.dart deleted file mode 100644 index f19215c89b9e..000000000000 --- a/script/tool/test/list_command_test.dart +++ /dev/null @@ -1,197 +0,0 @@ -// 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 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/list_command.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - group('ListCommand', () { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late CommandRunner runner; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - final ListCommand command = - ListCommand(packagesDir, platform: mockPlatform); - - runner = CommandRunner('list_test', 'Test for $ListCommand'); - runner.addCommand(command); - }); - - test('lists top-level packages', () async { - createFakePackage('package1', packagesDir); - createFakePlugin('plugin2', packagesDir); - - final List plugins = - await runCapturingPrint(runner, ['list', '--type=package']); - - expect( - plugins, - orderedEquals([ - '/packages/package1', - '/packages/plugin2', - ]), - ); - }); - - test('lists examples', () async { - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir, - examples: ['example1', 'example2']); - createFakePlugin('plugin3', packagesDir, examples: []); - - final List examples = - await runCapturingPrint(runner, ['list', '--type=example']); - - expect( - examples, - orderedEquals([ - '/packages/plugin1/example', - '/packages/plugin2/example/example1', - '/packages/plugin2/example/example2', - ]), - ); - }); - - test('lists packages and subpackages', () async { - createFakePackage('package1', packagesDir); - createFakePlugin('plugin2', packagesDir, - examples: ['example1', 'example2']); - createFakePlugin('plugin3', packagesDir, examples: []); - - final List packages = await runCapturingPrint( - runner, ['list', '--type=package-or-subpackage']); - - expect( - packages, - unorderedEquals([ - '/packages/package1', - '/packages/package1/example', - '/packages/plugin2', - '/packages/plugin2/example/example1', - '/packages/plugin2/example/example2', - '/packages/plugin3', - ]), - ); - }); - - test('lists files', () async { - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir, - examples: ['example1', 'example2']); - createFakePlugin('plugin3', packagesDir, examples: []); - - final List examples = - await runCapturingPrint(runner, ['list', '--type=file']); - - expect( - examples, - unorderedEquals([ - '/packages/plugin1/pubspec.yaml', - '/packages/plugin1/AUTHORS', - '/packages/plugin1/CHANGELOG.md', - '/packages/plugin1/README.md', - '/packages/plugin1/example/pubspec.yaml', - '/packages/plugin2/pubspec.yaml', - '/packages/plugin2/AUTHORS', - '/packages/plugin2/CHANGELOG.md', - '/packages/plugin2/README.md', - '/packages/plugin2/example/example1/pubspec.yaml', - '/packages/plugin2/example/example2/pubspec.yaml', - '/packages/plugin3/pubspec.yaml', - '/packages/plugin3/AUTHORS', - '/packages/plugin3/CHANGELOG.md', - '/packages/plugin3/README.md', - ]), - ); - }); - - test('lists plugins using federated plugin layout', () async { - createFakePlugin('plugin1', packagesDir); - - // Create a federated plugin by creating a directory under the packages - // directory with several packages underneath. - final Directory federatedPluginDir = - packagesDir.childDirectory('my_plugin')..createSync(); - createFakePlugin('my_plugin', federatedPluginDir); - createFakePlugin('my_plugin_web', federatedPluginDir); - createFakePlugin('my_plugin_macos', federatedPluginDir); - - // Test without specifying `--type`. - final List plugins = - await runCapturingPrint(runner, ['list']); - - expect( - plugins, - unorderedEquals([ - '/packages/plugin1', - '/packages/my_plugin/my_plugin', - '/packages/my_plugin/my_plugin_web', - '/packages/my_plugin/my_plugin_macos', - ]), - ); - }); - - test('can filter plugins with the --packages argument', () async { - createFakePlugin('plugin1', packagesDir); - - // Create a federated plugin by creating a directory under the packages - // directory with several packages underneath. - final Directory federatedPluginDir = - packagesDir.childDirectory('my_plugin')..createSync(); - createFakePlugin('my_plugin', federatedPluginDir); - createFakePlugin('my_plugin_web', federatedPluginDir); - createFakePlugin('my_plugin_macos', federatedPluginDir); - - List plugins = await runCapturingPrint( - runner, ['list', '--packages=plugin1']); - expect( - plugins, - unorderedEquals([ - '/packages/plugin1', - ]), - ); - - plugins = await runCapturingPrint( - runner, ['list', '--packages=my_plugin']); - expect( - plugins, - unorderedEquals([ - '/packages/my_plugin/my_plugin', - '/packages/my_plugin/my_plugin_web', - '/packages/my_plugin/my_plugin_macos', - ]), - ); - - plugins = await runCapturingPrint( - runner, ['list', '--packages=my_plugin/my_plugin_web']); - expect( - plugins, - unorderedEquals([ - '/packages/my_plugin/my_plugin_web', - ]), - ); - - plugins = await runCapturingPrint(runner, - ['list', '--packages=my_plugin/my_plugin_web,plugin1']); - expect( - plugins, - unorderedEquals([ - '/packages/plugin1', - '/packages/my_plugin/my_plugin_web', - ]), - ); - }); - }); -} diff --git a/script/tool/test/make_deps_path_based_command_test.dart b/script/tool/test/make_deps_path_based_command_test.dart deleted file mode 100644 index e846a63fc68e..000000000000 --- a/script/tool/test/make_deps_path_based_command_test.dart +++ /dev/null @@ -1,483 +0,0 @@ -// 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 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/make_deps_path_based_command.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -import 'common/package_command_test.mocks.dart'; -import 'mocks.dart'; -import 'util.dart'; - -void main() { - FileSystem fileSystem; - late Directory packagesDir; - late CommandRunner runner; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - - final MockGitDir gitDir = MockGitDir(); - when(gitDir.path).thenReturn(packagesDir.parent.path); - when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) - .thenAnswer((Invocation invocation) { - final List arguments = - invocation.positionalArguments[0]! as List; - // Route git calls through the process runner, to make mock output - // consistent with other processes. Attach the first argument to the - // command to make targeting the mock results easier. - final String gitCommand = arguments.removeAt(0); - return processRunner.run('git-$gitCommand', arguments); - }); - - processRunner = RecordingProcessRunner(); - final MakeDepsPathBasedCommand command = - MakeDepsPathBasedCommand(packagesDir, gitDir: gitDir); - - runner = CommandRunner( - 'make-deps-path-based_command', 'Test for $MakeDepsPathBasedCommand'); - runner.addCommand(command); - }); - - /// Adds dummy 'dependencies:' entries for each package in [dependencies] - /// to [package]. - void addDependencies( - RepositoryPackage package, Iterable dependencies) { - final List lines = package.pubspecFile.readAsLinesSync(); - final int dependenciesStartIndex = lines.indexOf('dependencies:'); - assert(dependenciesStartIndex != -1); - lines.insertAll(dependenciesStartIndex + 1, [ - for (final String dependency in dependencies) ' $dependency: ^1.0.0', - ]); - package.pubspecFile.writeAsStringSync(lines.join('\n')); - } - - /// Adds a 'dev_dependencies:' section with entries for each package in - /// [dependencies] to [package]. - void addDevDependenciesSection( - RepositoryPackage package, Iterable devDependencies) { - final String originalContent = package.pubspecFile.readAsStringSync(); - package.pubspecFile.writeAsStringSync(''' -$originalContent - -dev_dependencies: -${devDependencies.map((String dep) => ' $dep: ^1.0.0').join('\n')} -'''); - } - - test('no-ops for no plugins', () async { - createFakePackage('foo', packagesDir, isFlutter: true); - final RepositoryPackage packageBar = - createFakePackage('bar', packagesDir, isFlutter: true); - addDependencies(packageBar, ['foo']); - final String originalPubspecContents = - packageBar.pubspecFile.readAsStringSync(); - - final List output = - await runCapturingPrint(runner, ['make-deps-path-based']); - - expect( - output, - containsAllInOrder([ - contains('No target dependencies'), - ]), - ); - // The 'foo' reference should not have been modified. - expect(packageBar.pubspecFile.readAsStringSync(), originalPubspecContents); - }); - - test('rewrites "dependencies" references', () async { - final RepositoryPackage simplePackage = - createFakePackage('foo', packagesDir, isFlutter: true); - final Directory pluginGroup = packagesDir.childDirectory('bar'); - - createFakePackage('bar_platform_interface', pluginGroup, isFlutter: true); - final RepositoryPackage pluginImplementation = - createFakePlugin('bar_android', pluginGroup); - final RepositoryPackage pluginAppFacing = - createFakePlugin('bar', pluginGroup); - - addDependencies(simplePackage, [ - 'bar', - 'bar_android', - 'bar_platform_interface', - ]); - addDependencies(pluginAppFacing, [ - 'bar_platform_interface', - 'bar_android', - ]); - addDependencies(pluginImplementation, [ - 'bar_platform_interface', - ]); - - final List output = await runCapturingPrint(runner, [ - 'make-deps-path-based', - '--target-dependencies=bar,bar_platform_interface' - ]); - - expect( - output, - containsAll([ - 'Rewriting references to: bar, bar_platform_interface...', - ' Modified packages/bar/bar/pubspec.yaml', - ' Modified packages/bar/bar_android/pubspec.yaml', - ' Modified packages/foo/pubspec.yaml', - ])); - expect( - output, - isNot(contains( - ' Modified packages/bar/bar_platform_interface/pubspec.yaml'))); - - expect( - simplePackage.pubspecFile.readAsLinesSync(), - containsAllInOrder([ - '# FOR TESTING ONLY. DO NOT MERGE.', - 'dependency_overrides:', - ' bar:', - ' path: ../bar/bar', - ' bar_platform_interface:', - ' path: ../bar/bar_platform_interface', - ])); - expect( - pluginAppFacing.pubspecFile.readAsLinesSync(), - containsAllInOrder([ - 'dependency_overrides:', - ' bar_platform_interface:', - ' path: ../../bar/bar_platform_interface', - ])); - }); - - test('rewrites "dev_dependencies" references', () async { - createFakePackage('foo', packagesDir); - final RepositoryPackage builderPackage = - createFakePackage('foo_builder', packagesDir); - - addDevDependenciesSection(builderPackage, [ - 'foo', - ]); - - final List output = await runCapturingPrint( - runner, ['make-deps-path-based', '--target-dependencies=foo']); - - expect( - output, - containsAll([ - 'Rewriting references to: foo...', - ' Modified packages/foo_builder/pubspec.yaml', - ])); - - expect( - builderPackage.pubspecFile.readAsLinesSync(), - containsAllInOrder([ - '# FOR TESTING ONLY. DO NOT MERGE.', - 'dependency_overrides:', - ' foo:', - ' path: ../foo', - ])); - }); - - test( - 'alphabetizes overrides from different sectinos to avoid lint warnings in analysis', - () async { - createFakePackage('a', packagesDir); - createFakePackage('b', packagesDir); - createFakePackage('c', packagesDir); - final RepositoryPackage targetPackage = - createFakePackage('target', packagesDir); - - addDependencies(targetPackage, ['a', 'c']); - addDevDependenciesSection(targetPackage, ['b']); - - final List output = await runCapturingPrint(runner, - ['make-deps-path-based', '--target-dependencies=c,a,b']); - - expect( - output, - containsAllInOrder([ - 'Rewriting references to: c, a, b...', - ' Modified packages/target/pubspec.yaml', - ])); - - expect( - targetPackage.pubspecFile.readAsLinesSync(), - containsAllInOrder([ - '# FOR TESTING ONLY. DO NOT MERGE.', - 'dependency_overrides:', - ' a:', - ' path: ../a', - ' b:', - ' path: ../b', - ' c:', - ' path: ../c', - ])); - }); - - // This test case ensures that running CI using this command on an interim - // PR that itself used this command won't fail on the rewrite step. - test('running a second time no-ops without failing', () async { - final RepositoryPackage simplePackage = - createFakePackage('foo', packagesDir, isFlutter: true); - final Directory pluginGroup = packagesDir.childDirectory('bar'); - - createFakePackage('bar_platform_interface', pluginGroup, isFlutter: true); - final RepositoryPackage pluginImplementation = - createFakePlugin('bar_android', pluginGroup); - final RepositoryPackage pluginAppFacing = - createFakePlugin('bar', pluginGroup); - - addDependencies(simplePackage, [ - 'bar', - 'bar_android', - 'bar_platform_interface', - ]); - addDependencies(pluginAppFacing, [ - 'bar_platform_interface', - 'bar_android', - ]); - addDependencies(pluginImplementation, [ - 'bar_platform_interface', - ]); - - await runCapturingPrint(runner, [ - 'make-deps-path-based', - '--target-dependencies=bar,bar_platform_interface' - ]); - final List output = await runCapturingPrint(runner, [ - 'make-deps-path-based', - '--target-dependencies=bar,bar_platform_interface' - ]); - - expect( - output, - containsAll([ - 'Rewriting references to: bar, bar_platform_interface...', - ' Skipped packages/bar/bar/pubspec.yaml - Already rewritten', - ' Skipped packages/bar/bar_android/pubspec.yaml - Already rewritten', - ' Skipped packages/foo/pubspec.yaml - Already rewritten', - ])); - }); - - group('target-dependencies-with-non-breaking-updates', () { - test('no-ops for no published changes', () async { - final RepositoryPackage package = createFakePackage('foo', packagesDir); - - final String changedFileOutput = [ - package.pubspecFile, - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - // Simulate no change to the version in the interface's pubspec.yaml. - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: package.pubspecFile.readAsStringSync()), - ]; - - final List output = await runCapturingPrint(runner, [ - 'make-deps-path-based', - '--target-dependencies-with-non-breaking-updates' - ]); - - expect( - output, - containsAllInOrder([ - contains('No target dependencies'), - ]), - ); - }); - - test('no-ops for no deleted packages', () async { - final String changedFileOutput = [ - // A change for a file that's not on disk simulates a deletion. - packagesDir.childDirectory('foo').childFile('pubspec.yaml'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - final List output = await runCapturingPrint(runner, [ - 'make-deps-path-based', - '--target-dependencies-with-non-breaking-updates' - ]); - - expect( - output, - containsAllInOrder([ - contains('Skipping foo; deleted.'), - contains('No target dependencies'), - ]), - ); - }); - - test('includes bugfix version changes as targets', () async { - const String newVersion = '1.0.1'; - final RepositoryPackage package = - createFakePackage('foo', packagesDir, version: newVersion); - - final File pubspecFile = package.pubspecFile; - final String changedFileOutput = [ - pubspecFile, - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - final String gitPubspecContents = - pubspecFile.readAsStringSync().replaceAll(newVersion, '1.0.0'); - // Simulate no change to the version in the interface's pubspec.yaml. - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: gitPubspecContents), - ]; - - final List output = await runCapturingPrint(runner, [ - 'make-deps-path-based', - '--target-dependencies-with-non-breaking-updates' - ]); - - expect( - output, - containsAllInOrder([ - contains('Rewriting references to: foo...'), - ]), - ); - }); - - test('includes minor version changes to 1.0+ as targets', () async { - const String newVersion = '1.1.0'; - final RepositoryPackage package = - createFakePackage('foo', packagesDir, version: newVersion); - - final File pubspecFile = package.pubspecFile; - final String changedFileOutput = [ - pubspecFile, - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - final String gitPubspecContents = - pubspecFile.readAsStringSync().replaceAll(newVersion, '1.0.0'); - // Simulate no change to the version in the interface's pubspec.yaml. - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: gitPubspecContents), - ]; - - final List output = await runCapturingPrint(runner, [ - 'make-deps-path-based', - '--target-dependencies-with-non-breaking-updates' - ]); - - expect( - output, - containsAllInOrder([ - contains('Rewriting references to: foo...'), - ]), - ); - }); - - test('does not include major version changes as targets', () async { - const String newVersion = '2.0.0'; - final RepositoryPackage package = - createFakePackage('foo', packagesDir, version: newVersion); - - final File pubspecFile = package.pubspecFile; - final String changedFileOutput = [ - pubspecFile, - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - final String gitPubspecContents = - pubspecFile.readAsStringSync().replaceAll(newVersion, '1.0.0'); - // Simulate no change to the version in the interface's pubspec.yaml. - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: gitPubspecContents), - ]; - - final List output = await runCapturingPrint(runner, [ - 'make-deps-path-based', - '--target-dependencies-with-non-breaking-updates' - ]); - - expect( - output, - containsAllInOrder([ - contains('No target dependencies'), - ]), - ); - }); - - test('does not include minor version changes to 0.x as targets', () async { - const String newVersion = '0.8.0'; - final RepositoryPackage package = - createFakePackage('foo', packagesDir, version: newVersion); - - final File pubspecFile = package.pubspecFile; - final String changedFileOutput = [ - pubspecFile, - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - final String gitPubspecContents = - pubspecFile.readAsStringSync().replaceAll(newVersion, '0.7.0'); - // Simulate no change to the version in the interface's pubspec.yaml. - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: gitPubspecContents), - ]; - - final List output = await runCapturingPrint(runner, [ - 'make-deps-path-based', - '--target-dependencies-with-non-breaking-updates' - ]); - - expect( - output, - containsAllInOrder([ - contains('No target dependencies'), - ]), - ); - }); - - test('skips anything outside of the packages directory', () async { - final Directory toolDir = packagesDir.parent.childDirectory('tool'); - const String newVersion = '1.1.0'; - final RepositoryPackage package = createFakePackage( - 'flutter_plugin_tools', toolDir, - version: newVersion); - - // Simulate a minor version change so it would be a target. - final File pubspecFile = package.pubspecFile; - final String changedFileOutput = [ - pubspecFile, - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - final String gitPubspecContents = - pubspecFile.readAsStringSync().replaceAll(newVersion, '1.0.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: gitPubspecContents), - ]; - - final List output = await runCapturingPrint(runner, [ - 'make-deps-path-based', - '--target-dependencies-with-non-breaking-updates' - ]); - - expect( - output, - containsAllInOrder([ - contains( - 'Skipping /tool/flutter_plugin_tools/pubspec.yaml; not in packages directory.'), - contains('No target dependencies'), - ]), - ); - }); - }); -} diff --git a/script/tool/test/mocks.dart b/script/tool/test/mocks.dart deleted file mode 100644 index f6333ebd367d..000000000000 --- a/script/tool/test/mocks.dart +++ /dev/null @@ -1,89 +0,0 @@ -// 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 'dart:async'; -import 'dart:convert'; -import 'dart:io' as io; - -import 'package:file/file.dart'; -import 'package:mockito/mockito.dart'; -import 'package:platform/platform.dart'; - -class MockPlatform extends Mock implements Platform { - MockPlatform({ - this.isLinux = false, - this.isMacOS = false, - this.isWindows = false, - }); - - @override - bool isLinux; - - @override - bool isMacOS; - - @override - bool isWindows; - - @override - Uri get script => isWindows - ? Uri.file(r'C:\foo\bar', windows: true) - : Uri.file('/foo/bar', windows: false); - - @override - Map environment = {}; -} - -class MockProcess extends Mock implements io.Process { - /// Creates a mock process with the given results. - /// - /// The default encodings match the ProcessRunner defaults; mocks for - /// processes run with a different encoding will need to be created with - /// the matching encoding. - MockProcess({ - int exitCode = 0, - String? stdout, - String? stderr, - Encoding stdoutEncoding = io.systemEncoding, - Encoding stderrEncoding = io.systemEncoding, - }) : _exitCode = exitCode { - if (stdout != null) { - _stdoutController.add(stdoutEncoding.encoder.convert(stdout)); - } - if (stderr != null) { - _stderrController.add(stderrEncoding.encoder.convert(stderr)); - } - _stdoutController.close(); - _stderrController.close(); - } - - final int _exitCode; - final StreamController> _stdoutController = - StreamController>(); - final StreamController> _stderrController = - StreamController>(); - final MockIOSink stdinMock = MockIOSink(); - - @override - int get pid => 99; - - @override - Future get exitCode async => _exitCode; - - @override - Stream> get stdout => _stdoutController.stream; - - @override - Stream> get stderr => _stderrController.stream; - - @override - IOSink get stdin => stdinMock; -} - -class MockIOSink extends Mock implements IOSink { - List lines = []; - - @override - void writeln([Object? obj = '']) => lines.add(obj.toString()); -} diff --git a/script/tool/test/native_test_command_test.dart b/script/tool/test/native_test_command_test.dart deleted file mode 100644 index f24d014bbfea..000000000000 --- a/script/tool/test/native_test_command_test.dart +++ /dev/null @@ -1,1796 +0,0 @@ -// 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 'dart:convert'; -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/cmake.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/file_utils.dart'; -import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; -import 'package:flutter_plugin_tools/src/native_test_command.dart'; -import 'package:platform/platform.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -const String _androidIntegrationTestFilter = - '-Pandroid.testInstrumentationRunnerArguments.' - 'notAnnotation=io.flutter.plugins.DartIntegrationTest'; - -final Map _kDeviceListMap = { - 'runtimes': >[ - { - 'bundlePath': - '/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.4.simruntime', - 'buildversion': '17L255', - 'runtimeRoot': - '/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.4.simruntime/Contents/Resources/RuntimeRoot', - 'identifier': 'com.apple.CoreSimulator.SimRuntime.iOS-13-4', - 'version': '13.4', - 'isAvailable': true, - 'name': 'iOS 13.4' - }, - ], - 'devices': { - 'com.apple.CoreSimulator.SimRuntime.iOS-13-4': >[ - { - 'dataPath': - '/Users/xxx/Library/Developer/CoreSimulator/Devices/1E76A0FD-38AC-4537-A989-EA639D7D012A/data', - 'logPath': - '/Users/xxx/Library/Logs/CoreSimulator/1E76A0FD-38AC-4537-A989-EA639D7D012A', - 'udid': '1E76A0FD-38AC-4537-A989-EA639D7D012A', - 'isAvailable': true, - 'deviceTypeIdentifier': - 'com.apple.CoreSimulator.SimDeviceType.iPhone-8-Plus', - 'state': 'Shutdown', - 'name': 'iPhone 8 Plus' - } - ] - } -}; - -const String _fakeCmakeCommand = 'path/to/cmake'; - -void _createFakeCMakeCache(RepositoryPackage plugin, Platform platform) { - final CMakeProject project = CMakeProject(getExampleDir(plugin), - platform: platform, buildMode: 'Release'); - final File cache = project.buildDirectory.childFile('CMakeCache.txt'); - cache.createSync(recursive: true); - cache.writeAsStringSync('CMAKE_COMMAND:INTERNAL=$_fakeCmakeCommand'); -} - -// TODO(stuartmorgan): Rework these tests to use a mock Xcode instead of -// doing all the process mocking and validation. -void main() { - const String kDestination = '--ios-destination'; - - group('test native_test_command on Posix', () { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late CommandRunner runner; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - // iOS and macOS tests expect macOS, Linux tests expect Linux; nothing - // needs to distinguish between Linux and macOS, so set both to true to - // allow them to share a setup group. - mockPlatform = MockPlatform(isMacOS: true, isLinux: true); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final NativeTestCommand command = NativeTestCommand(packagesDir, - processRunner: processRunner, platform: mockPlatform); - - runner = CommandRunner( - 'native_test_command', 'Test for native_test_command'); - runner.addCommand(command); - }); - - // Returns a MockProcess to provide for "xcrun xcodebuild -list" for a - // project that contains [targets]. - MockProcess getMockXcodebuildListProcess(List targets) { - final Map projects = { - 'project': { - 'targets': targets, - } - }; - return MockProcess(stdout: jsonEncode(projects)); - } - - // Returns the ProcessCall to expect for checking the targets present in - // the [package]'s [platform]/Runner.xcodeproj. - ProcessCall getTargetCheckCall(Directory package, String platform) { - return ProcessCall( - 'xcrun', - [ - 'xcodebuild', - '-list', - '-json', - '-project', - package - .childDirectory(platform) - .childDirectory('Runner.xcodeproj') - .path, - ], - null); - } - - // Returns the ProcessCall to expect for running the tests in the - // workspace [platform]/Runner.xcworkspace, with the given extra flags. - ProcessCall getRunTestCall( - Directory package, - String platform, { - String? destination, - List extraFlags = const [], - }) { - return ProcessCall( - 'xcrun', - [ - 'xcodebuild', - 'test', - '-workspace', - '$platform/Runner.xcworkspace', - '-scheme', - 'Runner', - '-configuration', - 'Debug', - if (destination != null) ...['-destination', destination], - ...extraFlags, - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - package.path); - } - - // Returns the ProcessCall to expect for build the Linux unit tests for the - // given plugin. - ProcessCall getLinuxBuildCall(RepositoryPackage plugin) { - return ProcessCall( - 'cmake', - [ - '--build', - getExampleDir(plugin) - .childDirectory('build') - .childDirectory('linux') - .childDirectory('x64') - .childDirectory('release') - .path, - '--target', - 'unit_tests' - ], - null); - } - - test('fails if no platforms are provided', () async { - Error? commandError; - final List output = await runCapturingPrint( - runner, ['native-test'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('At least one platform flag must be provided.'), - ]), - ); - }); - - test('fails if all test types are disabled', () async { - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--macos', - '--no-unit', - '--no-integration', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('At least one test type must be enabled.'), - ]), - ); - }); - - test('reports skips with no tests', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess(['RunnerTests', 'RunnerUITests']), - // Exit code 66 from testing indicates no tests. - MockProcess(exitCode: 66), - ]; - final List output = await runCapturingPrint( - runner, ['native-test', '--macos', '--no-unit']); - - expect( - output, - containsAllInOrder([ - contains('No tests found.'), - contains('Skipped 1 package(s)'), - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getTargetCheckCall(pluginExampleDirectory, 'macos'), - getRunTestCall(pluginExampleDirectory, 'macos', - extraFlags: ['-only-testing:RunnerUITests']), - ])); - }); - - group('iOS', () { - test('skip if iOS is not supported', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final List output = await runCapturingPrint(runner, - ['native-test', '--ios', kDestination, 'foo_destination']); - expect( - output, - containsAllInOrder([ - contains('No implementation for iOS.'), - contains('SKIPPING: Nothing to test for target platform(s).'), - ])); - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('skip if iOS is implemented in a federated package', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.federated) - }); - - final List output = await runCapturingPrint(runner, - ['native-test', '--ios', kDestination, 'foo_destination']); - expect( - output, - containsAllInOrder([ - contains('No implementation for iOS.'), - contains('SKIPPING: Nothing to test for target platform(s).'), - ])); - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('running with correct destination', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline) - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess( - ['RunnerTests', 'RunnerUITests']), - ]; - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--ios', - kDestination, - 'foo_destination', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Successfully ran iOS xctest for plugin/example') - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getTargetCheckCall(pluginExampleDirectory, 'ios'), - getRunTestCall(pluginExampleDirectory, 'ios', - destination: 'foo_destination'), - ])); - }); - - test('Not specifying --ios-destination assigns an available simulator', - () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline) - }); - final Directory pluginExampleDirectory = getExampleDir(plugin); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(stdout: jsonEncode(_kDeviceListMap)), // simctl - getMockXcodebuildListProcess( - ['RunnerTests', 'RunnerUITests']), - ]; - - await runCapturingPrint(runner, ['native-test', '--ios']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - const ProcessCall( - 'xcrun', - [ - 'simctl', - 'list', - 'devices', - 'runtimes', - 'available', - '--json', - ], - null), - getTargetCheckCall(pluginExampleDirectory, 'ios'), - getRunTestCall(pluginExampleDirectory, 'ios', - destination: 'id=1E76A0FD-38AC-4537-A989-EA639D7D012A'), - ])); - }); - }); - - group('macOS', () { - test('skip if macOS is not supported', () async { - createFakePlugin('plugin', packagesDir); - - final List output = - await runCapturingPrint(runner, ['native-test', '--macos']); - - expect( - output, - containsAllInOrder([ - contains('No implementation for macOS.'), - contains('SKIPPING: Nothing to test for target platform(s).'), - ])); - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('skip if macOS is implemented in a federated package', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.federated), - }); - - final List output = - await runCapturingPrint(runner, ['native-test', '--macos']); - - expect( - output, - containsAllInOrder([ - contains('No implementation for macOS.'), - contains('SKIPPING: Nothing to test for target platform(s).'), - ])); - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('runs for macOS plugin', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess( - ['RunnerTests', 'RunnerUITests']), - ]; - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--macos', - ]); - - expect( - output, - contains( - contains('Successfully ran macOS xctest for plugin/example'))); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getTargetCheckCall(pluginExampleDirectory, 'macos'), - getRunTestCall(pluginExampleDirectory, 'macos'), - ])); - }); - }); - - group('Android', () { - test('runs Java unit tests in Android implementation folder', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'example/android/gradlew', - 'android/src/test/example_test.java', - ], - ); - - await runCapturingPrint(runner, ['native-test', '--android']); - - final Directory androidFolder = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - androidFolder.childFile('gradlew').path, - const ['testDebugUnitTest'], - androidFolder.path, - ), - ]), - ); - }); - - test('runs Java unit tests in example folder', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'example/android/gradlew', - 'example/android/app/src/test/example_test.java', - ], - ); - - await runCapturingPrint(runner, ['native-test', '--android']); - - final Directory androidFolder = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - androidFolder.childFile('gradlew').path, - const ['testDebugUnitTest'], - androidFolder.path, - ), - ]), - ); - }); - - test('runs Java integration tests', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'example/android/gradlew', - 'example/android/app/src/androidTest/IntegrationTest.java', - ], - ); - - await runCapturingPrint( - runner, ['native-test', '--android', '--no-unit']); - - final Directory androidFolder = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - androidFolder.childFile('gradlew').path, - const [ - 'app:connectedAndroidTest', - _androidIntegrationTestFilter, - ], - androidFolder.path, - ), - ]), - ); - }); - - test( - 'ignores Java integration test files associated with integration_test', - () async { - createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'example/android/gradlew', - 'example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java', - 'example/android/app/src/androidTest/java/io/flutter/plugins/plugin/FlutterActivityTest.java', - 'example/android/app/src/androidTest/java/io/flutter/plugins/plugin/MainActivityTest.java', - ], - ); - - await runCapturingPrint( - runner, ['native-test', '--android', '--no-unit']); - - // Nothing should run since those files are all - // integration_test-specific. - expect( - processRunner.recordedCalls, - orderedEquals([]), - ); - }); - - test('runs all tests when present', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'android/src/test/example_test.java', - 'example/android/gradlew', - 'example/android/app/src/androidTest/IntegrationTest.java', - ], - ); - - await runCapturingPrint(runner, ['native-test', '--android']); - - final Directory androidFolder = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - androidFolder.childFile('gradlew').path, - const ['testDebugUnitTest'], - androidFolder.path, - ), - ProcessCall( - androidFolder.childFile('gradlew').path, - const [ - 'app:connectedAndroidTest', - _androidIntegrationTestFilter, - ], - androidFolder.path, - ), - ]), - ); - }); - - test('honors --no-unit', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'android/src/test/example_test.java', - 'example/android/gradlew', - 'example/android/app/src/androidTest/IntegrationTest.java', - ], - ); - - await runCapturingPrint( - runner, ['native-test', '--android', '--no-unit']); - - final Directory androidFolder = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - androidFolder.childFile('gradlew').path, - const [ - 'app:connectedAndroidTest', - _androidIntegrationTestFilter, - ], - androidFolder.path, - ), - ]), - ); - }); - - test('honors --no-integration', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'android/src/test/example_test.java', - 'example/android/gradlew', - 'example/android/app/src/androidTest/IntegrationTest.java', - ], - ); - - await runCapturingPrint( - runner, ['native-test', '--android', '--no-integration']); - - final Directory androidFolder = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - androidFolder.childFile('gradlew').path, - const ['testDebugUnitTest'], - androidFolder.path, - ), - ]), - ); - }); - - test('fails when the app needs to be built', () async { - createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'example/android/app/src/test/example_test.java', - ], - ); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['native-test', '--android'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder([ - contains('ERROR: Run "flutter build apk" on plugin/example'), - contains('plugin:\n' - ' Examples must be built before testing.') - ]), - ); - }); - - test('logs missing test types', () async { - // No unit tests. - createFakePlugin( - 'plugin1', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'example/android/gradlew', - 'example/android/app/src/androidTest/IntegrationTest.java', - ], - ); - // No integration tests. - createFakePlugin( - 'plugin2', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'android/src/test/example_test.java', - 'example/android/gradlew', - ], - ); - - final List output = await runCapturingPrint( - runner, ['native-test', '--android'], - errorHandler: (Error e) { - // Having no unit tests is fatal, but that's not the point of this - // test so just ignore the failure. - }); - - expect( - output, - containsAllInOrder([ - contains('No Android unit tests found for plugin1/example'), - contains('Running integration tests...'), - contains( - 'No Android integration tests found for plugin2/example'), - contains('Running unit tests...'), - ])); - }); - - test('fails when a unit test fails', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'example/android/gradlew', - 'example/android/app/src/test/example_test.java', - ], - ); - - final String gradlewPath = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android) - .childFile('gradlew') - .path; - processRunner.mockProcessesForExecutable[gradlewPath] = [ - MockProcess(exitCode: 1) - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['native-test', '--android'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder([ - contains('plugin/example unit tests failed.'), - contains('The following packages had errors:'), - contains('plugin') - ]), - ); - }); - - test('fails when an integration test fails', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'example/android/gradlew', - 'example/android/app/src/test/example_test.java', - 'example/android/app/src/androidTest/IntegrationTest.java', - ], - ); - - final String gradlewPath = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android) - .childFile('gradlew') - .path; - processRunner.mockProcessesForExecutable[gradlewPath] = [ - MockProcess(), // unit passes - MockProcess(exitCode: 1), // integration fails - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['native-test', '--android'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder([ - contains('plugin/example integration tests failed.'), - contains('The following packages had errors:'), - contains('plugin') - ]), - ); - }); - - test('fails if there are no unit tests', () async { - createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'example/android/gradlew', - 'example/android/app/src/androidTest/IntegrationTest.java', - ], - ); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['native-test', '--android'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder([ - contains('No Android unit tests found for plugin/example'), - contains( - 'No unit tests ran. Plugins are required to have unit tests.'), - contains('The following packages had errors:'), - contains('plugin:\n' - ' No unit tests ran (use --exclude if this is intentional).') - ]), - ); - }); - - test('skips if Android is not supported', () async { - createFakePlugin( - 'plugin', - packagesDir, - ); - - final List output = await runCapturingPrint( - runner, ['native-test', '--android']); - - expect( - output, - containsAllInOrder([ - contains('No implementation for Android.'), - contains('SKIPPING: Nothing to test for target platform(s).'), - ]), - ); - }); - - test('skips when running no tests in integration-only mode', () async { - createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - ); - - final List output = await runCapturingPrint( - runner, ['native-test', '--android', '--no-unit']); - - expect( - output, - containsAllInOrder([ - contains('No Android integration tests found for plugin/example'), - contains('SKIPPING: No tests found.'), - ]), - ); - }); - }); - - group('Linux', () { - test('builds and runs unit tests', () async { - const String testBinaryRelativePath = - 'build/linux/x64/release/bar/plugin_test'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/$testBinaryRelativePath' - ], platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline), - }); - _createFakeCMakeCache(plugin, mockPlatform); - - final File testBinary = childFileWithSubcomponents(plugin.directory, - ['example', ...testBinaryRelativePath.split('/')]); - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--linux', - '--no-integration', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running plugin_test...'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getLinuxBuildCall(plugin), - ProcessCall(testBinary.path, const [], null), - ])); - }); - - test('only runs release unit tests', () async { - const String debugTestBinaryRelativePath = - 'build/linux/x64/debug/bar/plugin_test'; - const String releaseTestBinaryRelativePath = - 'build/linux/x64/release/bar/plugin_test'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/$debugTestBinaryRelativePath', - 'example/$releaseTestBinaryRelativePath' - ], platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline), - }); - _createFakeCMakeCache(plugin, mockPlatform); - - final File releaseTestBinary = childFileWithSubcomponents( - plugin.directory, - ['example', ...releaseTestBinaryRelativePath.split('/')]); - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--linux', - '--no-integration', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running plugin_test...'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getLinuxBuildCall(plugin), - ProcessCall(releaseTestBinary.path, const [], null), - ])); - }); - - test('fails if CMake has not been configured', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline), - }); - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--linux', - '--no-integration', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('plugin:\n' - ' Examples must be built before testing.') - ]), - ); - - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('fails if there are no unit tests', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline), - }); - _createFakeCMakeCache(plugin, mockPlatform); - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--linux', - '--no-integration', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No test binaries found.'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getLinuxBuildCall(plugin), - ])); - }); - - test('fails if a unit test fails', () async { - const String testBinaryRelativePath = - 'build/linux/x64/release/bar/plugin_test'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/$testBinaryRelativePath' - ], platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline), - }); - _createFakeCMakeCache(plugin, mockPlatform); - - final File testBinary = childFileWithSubcomponents(plugin.directory, - ['example', ...testBinaryRelativePath.split('/')]); - - processRunner.mockProcessesForExecutable[testBinary.path] = - [MockProcess(exitCode: 1)]; - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--linux', - '--no-integration', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running plugin_test...'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getLinuxBuildCall(plugin), - ProcessCall(testBinary.path, const [], null), - ])); - }); - }); - - // Tests behaviors of implementation that is shared between iOS and macOS. - group('iOS/macOS', () { - test('fails if xcrun fails', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(exitCode: 1) - ]; - - Error? commandError; - final List output = - await runCapturingPrint(runner, ['native-test', '--macos'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains(' plugin'), - ]), - ); - }); - - test('honors unit-only', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess( - ['RunnerTests', 'RunnerUITests']), - ]; - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--macos', - '--no-integration', - ]); - - expect( - output, - contains( - contains('Successfully ran macOS xctest for plugin/example'))); - - // --no-integration should translate to '-only-testing:RunnerTests'. - expect( - processRunner.recordedCalls, - orderedEquals([ - getTargetCheckCall(pluginExampleDirectory, 'macos'), - getRunTestCall(pluginExampleDirectory, 'macos', - extraFlags: ['-only-testing:RunnerTests']), - ])); - }); - - test('honors integration-only', () async { - final RepositoryPackage plugin1 = createFakePlugin( - 'plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin1); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess( - ['RunnerTests', 'RunnerUITests']), - ]; - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--macos', - '--no-unit', - ]); - - expect( - output, - contains( - contains('Successfully ran macOS xctest for plugin/example'))); - - // --no-unit should translate to '-only-testing:RunnerUITests'. - expect( - processRunner.recordedCalls, - orderedEquals([ - getTargetCheckCall(pluginExampleDirectory, 'macos'), - getRunTestCall(pluginExampleDirectory, 'macos', - extraFlags: ['-only-testing:RunnerUITests']), - ])); - }); - - test('skips when the requested target is not present', () async { - final RepositoryPackage plugin1 = createFakePlugin( - 'plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin1); - - // Simulate a project with unit tests but no integration tests... - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess(['RunnerTests']), - ]; - - // ... then try to run only integration tests. - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--macos', - '--no-unit', - ]); - - expect( - output, - containsAllInOrder([ - contains( - 'No "RunnerUITests" target in plugin/example; skipping.'), - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getTargetCheckCall(pluginExampleDirectory, 'macos'), - ])); - }); - - test('fails if there are no unit tests', () async { - final RepositoryPackage plugin1 = createFakePlugin( - 'plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin1); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess(['RunnerUITests']), - ]; - - Error? commandError; - final List output = - await runCapturingPrint(runner, ['native-test', '--macos'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No "RunnerTests" target in plugin/example; skipping.'), - contains( - 'No unit tests ran. Plugins are required to have unit tests.'), - contains('The following packages had errors:'), - contains('plugin:\n' - ' No unit tests ran (use --exclude if this is intentional).'), - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getTargetCheckCall(pluginExampleDirectory, 'macos'), - ])); - }); - - test('fails if unable to check for requested target', () async { - final RepositoryPackage plugin1 = createFakePlugin( - 'plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin1); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(exitCode: 1), // xcodebuild -list - ]; - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--macos', - '--no-integration', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to check targets for plugin/example.'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getTargetCheckCall(pluginExampleDirectory, 'macos'), - ])); - }); - }); - - group('multiplatform', () { - test('runs all platfroms when supported', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/android/gradlew', - 'android/src/test/example_test.java', - ], - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - final Directory androidFolder = - pluginExampleDirectory.childDirectory('android'); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess( - ['RunnerTests', 'RunnerUITests']), // iOS list - MockProcess(), // iOS run - getMockXcodebuildListProcess( - ['RunnerTests', 'RunnerUITests']), // macOS list - MockProcess(), // macOS run - ]; - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--android', - '--ios', - '--macos', - kDestination, - 'foo_destination', - ]); - - expect( - output, - containsAll([ - contains('Running Android tests for plugin/example'), - contains('Successfully ran iOS xctest for plugin/example'), - contains('Successfully ran macOS xctest for plugin/example'), - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(androidFolder.childFile('gradlew').path, - const ['testDebugUnitTest'], androidFolder.path), - getTargetCheckCall(pluginExampleDirectory, 'ios'), - getRunTestCall(pluginExampleDirectory, 'ios', - destination: 'foo_destination'), - getTargetCheckCall(pluginExampleDirectory, 'macos'), - getRunTestCall(pluginExampleDirectory, 'macos'), - ])); - }); - - test('runs only macOS for a macOS plugin', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess( - ['RunnerTests', 'RunnerUITests']), - ]; - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--ios', - '--macos', - kDestination, - 'foo_destination', - ]); - - expect( - output, - containsAllInOrder([ - contains('No implementation for iOS.'), - contains('Successfully ran macOS xctest for plugin/example'), - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getTargetCheckCall(pluginExampleDirectory, 'macos'), - getRunTestCall(pluginExampleDirectory, 'macos'), - ])); - }); - - test('runs only iOS for a iOS plugin', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline) - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess( - ['RunnerTests', 'RunnerUITests']), - ]; - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--ios', - '--macos', - kDestination, - 'foo_destination', - ]); - - expect( - output, - containsAllInOrder([ - contains('No implementation for macOS.'), - contains('Successfully ran iOS xctest for plugin/example') - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getTargetCheckCall(pluginExampleDirectory, 'ios'), - getRunTestCall(pluginExampleDirectory, 'ios', - destination: 'foo_destination'), - ])); - }); - - test('skips when nothing is supported', () async { - createFakePlugin('plugin', packagesDir); - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--android', - '--ios', - '--macos', - '--windows', - kDestination, - 'foo_destination', - ]); - - expect( - output, - containsAllInOrder([ - contains('No implementation for Android.'), - contains('No implementation for iOS.'), - contains('No implementation for macOS.'), - contains('SKIPPING: Nothing to test for target platform(s).'), - ])); - - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('skips Dart-only plugins', () async { - createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline, - hasDartCode: true, hasNativeCode: false), - platformWindows: const PlatformDetails(PlatformSupport.inline, - hasDartCode: true, hasNativeCode: false), - }, - ); - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--macos', - '--windows', - kDestination, - 'foo_destination', - ]); - - expect( - output, - containsAllInOrder([ - contains('No native code for macOS.'), - contains('No native code for Windows.'), - contains('SKIPPING: Nothing to test for target platform(s).'), - ])); - - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('failing one platform does not stop the tests', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - }, - extraFiles: [ - 'example/android/gradlew', - 'example/android/app/src/test/example_test.java', - ], - ); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess( - ['RunnerTests', 'RunnerUITests']), - ]; - - // Simulate failing Android, but not iOS. - final String gradlewPath = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android) - .childFile('gradlew') - .path; - processRunner.mockProcessesForExecutable[gradlewPath] = [ - MockProcess(exitCode: 1) - ]; - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--android', - '--ios', - '--ios-destination', - 'foo_destination', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder([ - contains('Running tests for Android...'), - contains('plugin/example unit tests failed.'), - contains('Running tests for iOS...'), - contains('Successfully ran iOS xctest for plugin/example'), - contains('The following packages had errors:'), - contains('plugin:\n' - ' Android') - ]), - ); - }); - - test('failing multiple platforms reports multiple failures', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - }, - extraFiles: [ - 'example/android/gradlew', - 'example/android/app/src/test/example_test.java', - ], - ); - - // Simulate failing Android. - final String gradlewPath = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android) - .childFile('gradlew') - .path; - processRunner.mockProcessesForExecutable[gradlewPath] = [ - MockProcess(exitCode: 1) - ]; - // Simulate failing Android. - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(exitCode: 1) - ]; - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--android', - '--ios', - '--ios-destination', - 'foo_destination', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder([ - contains('Running tests for Android...'), - contains('Running tests for iOS...'), - contains('The following packages had errors:'), - contains('plugin:\n' - ' Android\n' - ' iOS') - ]), - ); - }); - }); - }); - - group('test native_test_command on Windows', () { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late CommandRunner runner; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(style: FileSystemStyle.windows); - mockPlatform = MockPlatform(isWindows: true); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final NativeTestCommand command = NativeTestCommand(packagesDir, - processRunner: processRunner, platform: mockPlatform); - - runner = CommandRunner( - 'native_test_command', 'Test for native_test_command'); - runner.addCommand(command); - }); - - // Returns the ProcessCall to expect for build the Windows unit tests for - // the given plugin. - ProcessCall getWindowsBuildCall(RepositoryPackage plugin) { - return ProcessCall( - _fakeCmakeCommand, - [ - '--build', - getExampleDir(plugin) - .childDirectory('build') - .childDirectory('windows') - .path, - '--target', - 'unit_tests', - '--config', - 'Debug' - ], - null); - } - - group('Windows', () { - test('runs unit tests', () async { - const String testBinaryRelativePath = - 'build/windows/Debug/bar/plugin_test.exe'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/$testBinaryRelativePath' - ], platformSupport: { - platformWindows: const PlatformDetails(PlatformSupport.inline), - }); - _createFakeCMakeCache(plugin, mockPlatform); - - final File testBinary = childFileWithSubcomponents(plugin.directory, - ['example', ...testBinaryRelativePath.split('/')]); - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--windows', - '--no-integration', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running plugin_test.exe...'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getWindowsBuildCall(plugin), - ProcessCall(testBinary.path, const [], null), - ])); - }); - - test('only runs debug unit tests', () async { - const String debugTestBinaryRelativePath = - 'build/windows/Debug/bar/plugin_test.exe'; - const String releaseTestBinaryRelativePath = - 'build/windows/Release/bar/plugin_test.exe'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/$debugTestBinaryRelativePath', - 'example/$releaseTestBinaryRelativePath' - ], platformSupport: { - platformWindows: const PlatformDetails(PlatformSupport.inline), - }); - _createFakeCMakeCache(plugin, mockPlatform); - - final File debugTestBinary = childFileWithSubcomponents( - plugin.directory, - ['example', ...debugTestBinaryRelativePath.split('/')]); - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--windows', - '--no-integration', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running plugin_test.exe...'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getWindowsBuildCall(plugin), - ProcessCall(debugTestBinary.path, const [], null), - ])); - }); - - test('fails if CMake has not been configured', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformWindows: const PlatformDetails(PlatformSupport.inline), - }); - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--windows', - '--no-integration', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('plugin:\n' - ' Examples must be built before testing.') - ]), - ); - - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('fails if there are no unit tests', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformWindows: const PlatformDetails(PlatformSupport.inline), - }); - _createFakeCMakeCache(plugin, mockPlatform); - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--windows', - '--no-integration', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No test binaries found.'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getWindowsBuildCall(plugin), - ])); - }); - - test('fails if a unit test fails', () async { - const String testBinaryRelativePath = - 'build/windows/Debug/bar/plugin_test.exe'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/$testBinaryRelativePath' - ], platformSupport: { - platformWindows: const PlatformDetails(PlatformSupport.inline), - }); - _createFakeCMakeCache(plugin, mockPlatform); - - final File testBinary = childFileWithSubcomponents(plugin.directory, - ['example', ...testBinaryRelativePath.split('/')]); - - processRunner.mockProcessesForExecutable[testBinary.path] = - [MockProcess(exitCode: 1)]; - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--windows', - '--no-integration', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running plugin_test.exe...'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getWindowsBuildCall(plugin), - ProcessCall(testBinary.path, const [], null), - ])); - }); - }); - }); -} diff --git a/script/tool/test/podspec_check_command_test.dart b/script/tool/test/podspec_check_command_test.dart deleted file mode 100644 index c31ffd46a4b7..000000000000 --- a/script/tool/test/podspec_check_command_test.dart +++ /dev/null @@ -1,428 +0,0 @@ -// 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 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/podspec_check_command.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -/// Adds a fake podspec to [plugin]'s [platform] directory. -/// -/// If [includeSwiftWorkaround] is set, the xcconfig additions to make Swift -/// libraries work in apps that have no Swift will be included. If -/// [scopeSwiftWorkaround] is set, it will be specific to the iOS configuration. -void _writeFakePodspec(RepositoryPackage plugin, String platform, - {bool includeSwiftWorkaround = false, bool scopeSwiftWorkaround = false}) { - final String pluginName = plugin.directory.basename; - final File file = plugin.directory - .childDirectory(platform) - .childFile('$pluginName.podspec'); - final String swiftWorkaround = includeSwiftWorkaround - ? ''' - s.${scopeSwiftWorkaround ? 'ios.' : ''}xcconfig = { - 'LIBRARY_SEARCH_PATHS' => '\$(TOOLCHAIN_DIR)/usr/lib/swift/\$(PLATFORM_NAME)/ \$(SDKROOT)/usr/lib/swift', - 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', - } -''' - : ''; - file.createSync(recursive: true); - file.writeAsStringSync(''' -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html -# -Pod::Spec.new do |s| - s.name = 'shared_preferences_foundation' - s.version = '0.0.1' - s.summary = 'iOS and macOS implementation of the shared_preferences plugin.' - s.description = <<-DESC -Wraps NSUserDefaults, providing a persistent store for simple key-value pairs. - DESC - s.homepage = 'https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_foundation' - s.license = { :type => 'BSD', :file => '../LICENSE' } - s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' } - s.source = { :http => 'https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_foundation' } - s.source_files = 'Classes/**/*' - s.ios.dependency 'Flutter' - s.osx.dependency 'FlutterMacOS' - s.ios.deployment_target = '9.0' - s.osx.deployment_target = '10.11' - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } - $swiftWorkaround - s.swift_version = '5.0' - -end -'''); -} - -void main() { - group('PodspecCheckCommand', () { - FileSystem fileSystem; - late Directory packagesDir; - late CommandRunner runner; - late MockPlatform mockPlatform; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - - mockPlatform = MockPlatform(isMacOS: true); - processRunner = RecordingProcessRunner(); - final PodspecCheckCommand command = PodspecCheckCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = - CommandRunner('podspec_test', 'Test for $PodspecCheckCommand'); - runner.addCommand(command); - }); - - test('only runs on macOS', () async { - createFakePlugin('plugin1', packagesDir, - extraFiles: ['plugin1.podspec']); - mockPlatform.isMacOS = false; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['podspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - processRunner.recordedCalls, - equals([]), - ); - - expect( - output, - containsAllInOrder( - [contains('only supported on macOS')], - )); - }); - - test('runs pod lib lint on a podspec', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin1', - packagesDir, - extraFiles: [ - 'bogus.dart', // Ignore non-podspecs. - ], - ); - _writeFakePodspec(plugin, 'ios'); - - processRunner.mockProcessesForExecutable['pod'] = [ - MockProcess(stdout: 'Foo', stderr: 'Bar'), - MockProcess(), - ]; - - final List output = - await runCapturingPrint(runner, ['podspec-check']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall('which', const ['pod'], packagesDir.path), - ProcessCall( - 'pod', - [ - 'lib', - 'lint', - plugin - .platformDirectory(FlutterPlatform.ios) - .childFile('plugin1.podspec') - .path, - '--configuration=Debug', - '--skip-tests', - '--use-modular-headers', - '--use-libraries' - ], - packagesDir.path), - ProcessCall( - 'pod', - [ - 'lib', - 'lint', - plugin - .platformDirectory(FlutterPlatform.ios) - .childFile('plugin1.podspec') - .path, - '--configuration=Debug', - '--skip-tests', - '--use-modular-headers', - ], - packagesDir.path), - ]), - ); - - expect(output, contains('Linting plugin1.podspec')); - expect(output, contains('Foo')); - expect(output, contains('Bar')); - }); - - test('fails if pod is missing', () async { - final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir); - _writeFakePodspec(plugin, 'ios'); - - // Simulate failure from `which pod`. - processRunner.mockProcessesForExecutable['which'] = [ - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['podspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder( - [ - contains('Unable to find "pod". Make sure it is in your path.'), - ], - )); - }); - - test('fails if linting as a framework fails', () async { - final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir); - _writeFakePodspec(plugin, 'ios'); - - // Simulate failure from `pod`. - processRunner.mockProcessesForExecutable['pod'] = [ - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['podspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder( - [ - contains('The following packages had errors:'), - contains('plugin1:\n' - ' plugin1.podspec') - ], - )); - }); - - test('fails if linting as a static library fails', () async { - final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir); - _writeFakePodspec(plugin, 'ios'); - - // Simulate failure from the second call to `pod`. - processRunner.mockProcessesForExecutable['pod'] = [ - MockProcess(), - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['podspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder( - [ - contains('The following packages had errors:'), - contains('plugin1:\n' - ' plugin1.podspec') - ], - )); - }); - - test('fails if an iOS Swift plugin is missing the search paths workaround', - () async { - final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, - extraFiles: ['ios/Classes/SomeSwift.swift']); - _writeFakePodspec(plugin, 'ios'); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['podspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder( - [ - contains(r''' - s.xcconfig = { - 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', - 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', - }'''), - contains('The following packages had errors:'), - contains('plugin1:\n' - ' plugin1.podspec') - ], - )); - }); - - test( - 'fails if a shared-source Swift plugin is missing the search paths workaround', - () async { - final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, - extraFiles: ['darwin/Classes/SomeSwift.swift']); - _writeFakePodspec(plugin, 'darwin'); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['podspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder( - [ - contains(r''' - s.xcconfig = { - 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', - 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', - }'''), - contains('The following packages had errors:'), - contains('plugin1:\n' - ' plugin1.podspec') - ], - )); - }); - - test('does not require the search paths workaround for macOS plugins', - () async { - final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, - extraFiles: ['macos/Classes/SomeSwift.swift']); - _writeFakePodspec(plugin, 'macos'); - - final List output = - await runCapturingPrint(runner, ['podspec-check']); - - expect( - output, - containsAllInOrder( - [ - contains('Ran for 1 package(s)'), - ], - )); - }); - - test('does not require the search paths workaround for ObjC iOS plugins', - () async { - final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, - extraFiles: [ - 'ios/Classes/SomeObjC.h', - 'ios/Classes/SomeObjC.m' - ]); - _writeFakePodspec(plugin, 'ios'); - - final List output = - await runCapturingPrint(runner, ['podspec-check']); - - expect( - output, - containsAllInOrder( - [ - contains('Ran for 1 package(s)'), - ], - )); - }); - - test('passes if the search paths workaround is present', () async { - final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, - extraFiles: ['ios/Classes/SomeSwift.swift']); - _writeFakePodspec(plugin, 'ios', includeSwiftWorkaround: true); - - final List output = - await runCapturingPrint(runner, ['podspec-check']); - - expect( - output, - containsAllInOrder( - [ - contains('Ran for 1 package(s)'), - ], - )); - }); - - test('passes if the search paths workaround is present for iOS only', - () async { - final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, - extraFiles: ['ios/Classes/SomeSwift.swift']); - _writeFakePodspec(plugin, 'ios', - includeSwiftWorkaround: true, scopeSwiftWorkaround: true); - - final List output = - await runCapturingPrint(runner, ['podspec-check']); - - expect( - output, - containsAllInOrder( - [ - contains('Ran for 1 package(s)'), - ], - )); - }); - - test('does not require the search paths workaround for Swift example code', - () async { - final RepositoryPackage plugin = - createFakePlugin('plugin1', packagesDir, extraFiles: [ - 'ios/Classes/SomeObjC.h', - 'ios/Classes/SomeObjC.m', - 'example/ios/Runner/AppDelegate.swift', - ]); - _writeFakePodspec(plugin, 'ios'); - - final List output = - await runCapturingPrint(runner, ['podspec-check']); - - expect( - output, - containsAllInOrder( - [ - contains('Ran for 1 package(s)'), - ], - )); - }); - - test('skips when there are no podspecs', () async { - createFakePlugin('plugin1', packagesDir); - - final List output = - await runCapturingPrint(runner, ['podspec-check']); - - expect( - output, - containsAllInOrder( - [contains('SKIPPING: No podspecs.')], - )); - }); - }); -} diff --git a/script/tool/test/publish_check_command_test.dart b/script/tool/test/publish_check_command_test.dart deleted file mode 100644 index 575f8509fd25..000000000000 --- a/script/tool/test/publish_check_command_test.dart +++ /dev/null @@ -1,464 +0,0 @@ -// 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 'dart:convert'; -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/publish_check_command.dart'; -import 'package:http/http.dart' as http; -import 'package:http/testing.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - group('$PublishCheckCommand tests', () { - FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late RecordingProcessRunner processRunner; - late CommandRunner runner; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final PublishCheckCommand publishCheckCommand = PublishCheckCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner( - 'publish_check_command', - 'Test for publish-check command.', - ); - runner.addCommand(publishCheckCommand); - }); - - test('publish check all packages', () async { - final RepositoryPackage plugin1 = createFakePlugin( - 'plugin_tools_test_package_a', - packagesDir, - examples: [], - ); - final RepositoryPackage plugin2 = createFakePlugin( - 'plugin_tools_test_package_b', - packagesDir, - examples: [], - ); - - await runCapturingPrint(runner, ['publish-check']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'flutter', - const ['pub', 'publish', '--', '--dry-run'], - plugin1.path), - ProcessCall( - 'flutter', - const ['pub', 'publish', '--', '--dry-run'], - plugin2.path), - ])); - }); - - test('publish prepares dependencies of examples (when present)', () async { - final RepositoryPackage plugin1 = createFakePlugin( - 'plugin_tools_test_package_a', - packagesDir, - examples: ['example1', 'example2'], - ); - final RepositoryPackage plugin2 = createFakePlugin( - 'plugin_tools_test_package_b', - packagesDir, - examples: [], - ); - - await runCapturingPrint(runner, ['publish-check']); - - // For plugin1, these are the expected pub get calls that will happen - final Iterable pubGetCalls = - plugin1.getExamples().map((RepositoryPackage example) { - return ProcessCall( - 'dart', - const ['pub', 'get'], - example.path, - ); - }); - - expect(pubGetCalls, hasLength(2)); - expect( - processRunner.recordedCalls, - orderedEquals([ - // plugin1 has 2 examples, so there's some 'dart pub get' calls. - ...pubGetCalls, - ProcessCall( - 'flutter', - const ['pub', 'publish', '--', '--dry-run'], - plugin1.path), - // plugin2 has no examples, so there's no extra 'dart pub get' calls. - ProcessCall( - 'flutter', - const ['pub', 'publish', '--', '--dry-run'], - plugin2.path), - ]), - ); - }); - - test('fail on negative test', () async { - createFakePlugin('plugin_tools_test_package_a', packagesDir); - - processRunner.mockProcessesForExecutable['flutter'] = [ - MockProcess(exitCode: 1, stdout: 'Some error from pub') - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['publish-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Some error from pub'), - contains('Unable to publish plugin_tools_test_package_a'), - ]), - ); - }); - - test('fail on bad pubspec', () async { - final RepositoryPackage package = createFakePlugin('c', packagesDir); - await package.pubspecFile.writeAsString('bad-yaml'); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['publish-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No valid pubspec found.'), - ]), - ); - }); - - test('fails if AUTHORS is missing', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - package.authorsFile.delete(); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['publish-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'No AUTHORS file found. Packages must include an AUTHORS file.'), - ]), - ); - }); - - test('does not require AUTHORS for third-party', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', - packagesDir.parent - .childDirectory('third_party') - .childDirectory('packages')); - package.authorsFile.delete(); - - final List output = - await runCapturingPrint(runner, ['publish-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for a_package'), - ]), - ); - }); - - test('pass on prerelease if --allow-pre-release flag is on', () async { - createFakePlugin('d', packagesDir); - - final MockProcess process = MockProcess( - exitCode: 1, - stdout: 'Package has 1 warning.\n' - 'Packages with an SDK constraint on a pre-release of the Dart ' - 'SDK should themselves be published as a pre-release version.'); - processRunner.mockProcessesForExecutable['flutter'] = [ - process, - ]; - - expect( - runCapturingPrint( - runner, ['publish-check', '--allow-pre-release']), - completes); - }); - - test('fail on prerelease if --allow-pre-release flag is off', () async { - createFakePlugin('d', packagesDir); - - final MockProcess process = MockProcess( - exitCode: 1, - stdout: 'Package has 1 warning.\n' - 'Packages with an SDK constraint on a pre-release of the Dart ' - 'SDK should themselves be published as a pre-release version.'); - processRunner.mockProcessesForExecutable['flutter'] = [ - process, - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['publish-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Packages with an SDK constraint on a pre-release of the Dart SDK'), - contains('Unable to publish d'), - ]), - ); - }); - - test('Success message on stderr is not printed as an error', () async { - createFakePlugin('d', packagesDir); - - processRunner.mockProcessesForExecutable['flutter'] = [ - MockProcess(stdout: 'Package has 0 warnings.'), - ]; - - final List output = - await runCapturingPrint(runner, ['publish-check']); - - expect(output, isNot(contains(contains('ERROR:')))); - }); - - test( - '--machine: Log JSON with status:no-publish and correct human message, if there are no packages need to be published. ', - () async { - const Map httpResponseA = { - 'name': 'a', - 'versions': [ - '0.0.1', - '0.1.0', - ], - }; - - const Map httpResponseB = { - 'name': 'b', - 'versions': [ - '0.0.1', - '0.1.0', - '0.2.0', - ], - }; - - final MockClient mockClient = MockClient((http.Request request) async { - if (request.url.pathSegments.last == 'no_publish_a.json') { - return http.Response(json.encode(httpResponseA), 200); - } else if (request.url.pathSegments.last == 'no_publish_b.json') { - return http.Response(json.encode(httpResponseB), 200); - } - return http.Response('', 500); - }); - final PublishCheckCommand command = PublishCheckCommand(packagesDir, - processRunner: processRunner, httpClient: mockClient); - - runner = CommandRunner( - 'publish_check_command', - 'Test for publish-check command.', - ); - runner.addCommand(command); - - createFakePlugin('no_publish_a', packagesDir, version: '0.1.0'); - createFakePlugin('no_publish_b', packagesDir, version: '0.2.0'); - - final List output = await runCapturingPrint( - runner, ['publish-check', '--machine']); - - expect(output.first, r''' -{ - "status": "no-publish", - "humanMessage": [ - "\n============================================================\n|| Running for no_publish_a\n============================================================\n", - "Package no_publish_a version: 0.1.0 has already be published on pub.", - "\n============================================================\n|| Running for no_publish_b\n============================================================\n", - "Package no_publish_b version: 0.2.0 has already be published on pub.", - "\n", - "------------------------------------------------------------", - "Run overview:", - " no_publish_a - ran", - " no_publish_b - ran", - "", - "Ran for 2 package(s)", - "\n", - "No issues found!" - ] -}'''); - }); - - test( - '--machine: Log JSON with status:needs-publish and correct human message, if there is at least 1 plugin needs to be published.', - () async { - const Map httpResponseA = { - 'name': 'a', - 'versions': [ - '0.0.1', - '0.1.0', - ], - }; - - const Map httpResponseB = { - 'name': 'b', - 'versions': [ - '0.0.1', - '0.1.0', - ], - }; - - final MockClient mockClient = MockClient((http.Request request) async { - if (request.url.pathSegments.last == 'no_publish_a.json') { - return http.Response(json.encode(httpResponseA), 200); - } else if (request.url.pathSegments.last == 'no_publish_b.json') { - return http.Response(json.encode(httpResponseB), 200); - } - return http.Response('', 500); - }); - final PublishCheckCommand command = PublishCheckCommand(packagesDir, - processRunner: processRunner, httpClient: mockClient); - - runner = CommandRunner( - 'publish_check_command', - 'Test for publish-check command.', - ); - runner.addCommand(command); - - createFakePlugin('no_publish_a', packagesDir, version: '0.1.0'); - createFakePlugin('no_publish_b', packagesDir, version: '0.2.0'); - - final List output = await runCapturingPrint( - runner, ['publish-check', '--machine']); - - expect(output.first, r''' -{ - "status": "needs-publish", - "humanMessage": [ - "\n============================================================\n|| Running for no_publish_a\n============================================================\n", - "Package no_publish_a version: 0.1.0 has already be published on pub.", - "\n============================================================\n|| Running for no_publish_b\n============================================================\n", - "Running pub publish --dry-run:", - "Package no_publish_b is able to be published.", - "\n", - "------------------------------------------------------------", - "Run overview:", - " no_publish_a - ran", - " no_publish_b - ran", - "", - "Ran for 2 package(s)", - "\n", - "No issues found!" - ] -}'''); - }); - - test( - '--machine: Log correct JSON, if there is at least 1 plugin contains error.', - () async { - const Map httpResponseA = { - 'name': 'a', - 'versions': [ - '0.0.1', - '0.1.0', - ], - }; - - const Map httpResponseB = { - 'name': 'b', - 'versions': [ - '0.0.1', - '0.1.0', - ], - }; - - final MockClient mockClient = MockClient((http.Request request) async { - print('url ${request.url}'); - print(request.url.pathSegments.last); - if (request.url.pathSegments.last == 'no_publish_a.json') { - return http.Response(json.encode(httpResponseA), 200); - } else if (request.url.pathSegments.last == 'no_publish_b.json') { - return http.Response(json.encode(httpResponseB), 200); - } - return http.Response('', 500); - }); - final PublishCheckCommand command = PublishCheckCommand(packagesDir, - processRunner: processRunner, httpClient: mockClient); - - runner = CommandRunner( - 'publish_check_command', - 'Test for publish-check command.', - ); - runner.addCommand(command); - - final RepositoryPackage plugin = - createFakePlugin('no_publish_a', packagesDir, version: '0.1.0'); - createFakePlugin('no_publish_b', packagesDir, version: '0.2.0'); - - await plugin.pubspecFile.writeAsString('bad-yaml'); - - bool hasError = false; - final List output = await runCapturingPrint( - runner, ['publish-check', '--machine'], - errorHandler: (Error error) { - expect(error, isA()); - hasError = true; - }); - expect(hasError, isTrue); - - expect(output.first, contains(r''' -{ - "status": "error", - "humanMessage": [ - "\n============================================================\n|| Running for no_publish_a\n============================================================\n", - "Failed to parse `pubspec.yaml` at /packages/no_publish_a/pubspec.yaml: ParsedYamlException:''')); - // This is split into two checks since the details of the YamlException - // aren't controlled by this package, so asserting its exact format would - // make the test fragile to irrelevant changes in those details. - expect(output.first, contains(r''' - "No valid pubspec found.", - "\n============================================================\n|| Running for no_publish_b\n============================================================\n", - "url https://pub.dev/packages/no_publish_b.json", - "no_publish_b.json", - "Running pub publish --dry-run:", - "Package no_publish_b is able to be published.", - "\n", - "The following packages had errors:", - " no_publish_a", - "See above for full details." - ] -}''')); - }); - }); -} diff --git a/script/tool/test/publish_command_test.dart b/script/tool/test/publish_command_test.dart deleted file mode 100644 index da5f9c871f05..000000000000 --- a/script/tool/test/publish_command_test.dart +++ /dev/null @@ -1,922 +0,0 @@ -// 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 'dart:async'; -import 'dart:convert'; -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/publish_command.dart'; -import 'package:http/http.dart' as http; -import 'package:http/testing.dart'; -import 'package:mockito/mockito.dart'; -import 'package:platform/platform.dart'; -import 'package:test/test.dart'; - -import 'common/package_command_test.mocks.dart'; -import 'mocks.dart'; -import 'util.dart'; - -void main() { - final String flutterCommand = getFlutterCommand(const LocalPlatform()); - - late Directory packagesDir; - late MockGitDir gitDir; - late TestProcessRunner processRunner; - late CommandRunner commandRunner; - late MockStdin mockStdin; - late FileSystem fileSystem; - // Map of package name to mock response. - late Map> mockHttpResponses; - - void createMockCredentialFile() { - final String credentialPath = PublishCommand.getCredentialPath(); - fileSystem.file(credentialPath) - ..createSync(recursive: true) - ..writeAsStringSync('some credential'); - } - - setUp(() async { - fileSystem = MemoryFileSystem(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = TestProcessRunner(); - - mockHttpResponses = >{}; - final MockClient mockClient = MockClient((http.Request request) async { - final String packageName = - request.url.pathSegments.last.replaceAll('.json', ''); - final Map? response = mockHttpResponses[packageName]; - if (response != null) { - return http.Response(json.encode(response), 200); - } - // Default to simulating the plugin never having been published. - return http.Response('', 404); - }); - - gitDir = MockGitDir(); - when(gitDir.path).thenReturn(packagesDir.parent.path); - when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) - .thenAnswer((Invocation invocation) { - final List arguments = - invocation.positionalArguments[0]! as List; - // Route git calls through the process runner, to make mock output - // consistent with outer processes. Attach the first argument to the - // command to make targeting the mock results easier. - final String gitCommand = arguments.removeAt(0); - return processRunner.run('git-$gitCommand', arguments); - }); - - mockStdin = MockStdin(); - commandRunner = CommandRunner('tester', '') - ..addCommand(PublishCommand( - packagesDir, - processRunner: processRunner, - stdinput: mockStdin, - gitDir: gitDir, - httpClient: mockClient, - )); - }); - - group('Initial validation', () { - test('refuses to proceed with dirty files', () async { - final RepositoryPackage plugin = - createFakePlugin('foo', packagesDir, examples: []); - - processRunner.mockProcessesForExecutable['git-status'] = [ - MockProcess(stdout: '?? ${plugin.directory.childFile('tmp').path}\n') - ]; - - Error? commandError; - final List output = - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=foo', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains("There are files in the package directory that haven't " - 'been saved in git. Refusing to publish these files:\n\n' - '?? /packages/foo/tmp\n\n' - 'If the directory should be clean, you can run `git clean -xdf && ' - 'git reset --hard HEAD` to wipe all local changes.'), - contains('foo:\n' - ' uncommitted changes'), - ])); - }); - - test("fails immediately if the remote doesn't exist", () async { - createFakePlugin('foo', packagesDir, examples: []); - - processRunner.mockProcessesForExecutable['git-remote'] = [ - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - commandRunner, ['publish', '--packages=foo'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Unable to find URL for remote upstream; cannot push tags'), - ])); - }); - }); - - group('Publishes package', () { - test('while showing all output from pub publish to the user', () async { - createFakePlugin('plugin1', packagesDir, examples: []); - createFakePlugin('plugin2', packagesDir, examples: []); - - processRunner.mockProcessesForExecutable[flutterCommand] = [ - MockProcess( - stdout: 'Foo', - stderr: 'Bar', - stdoutEncoding: utf8, - stderrEncoding: utf8), // pub publish for plugin1 - MockProcess( - stdout: 'Baz', - stdoutEncoding: utf8, - stderrEncoding: utf8), // pub publish for plugin1 - ]; - - final List output = await runCapturingPrint( - commandRunner, ['publish', '--packages=plugin1,plugin2']); - - expect( - output, - containsAllInOrder([ - contains('Running `pub publish ` in /packages/plugin1...'), - contains('Foo'), - contains('Bar'), - contains('Package published!'), - contains('Running `pub publish ` in /packages/plugin2...'), - contains('Baz'), - contains('Package published!'), - ])); - }); - - test('forwards input from the user to `pub publish`', () async { - createFakePlugin('foo', packagesDir, examples: []); - - mockStdin.mockUserInputs.add(utf8.encode('user input')); - - await runCapturingPrint( - commandRunner, ['publish', '--packages=foo']); - - expect(processRunner.mockPublishProcess.stdinMock.lines, - contains('user input')); - }); - - test('forwards --pub-publish-flags to pub publish', () async { - final RepositoryPackage plugin = - createFakePlugin('foo', packagesDir, examples: []); - - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=foo', - '--pub-publish-flags', - '--dry-run,--server=bar' - ]); - - expect( - processRunner.recordedCalls, - contains(ProcessCall( - flutterCommand, - const ['pub', 'publish', '--dry-run', '--server=bar'], - plugin.path))); - }); - - test( - '--skip-confirmation flag automatically adds --force to --pub-publish-flags', - () async { - createMockCredentialFile(); - final RepositoryPackage plugin = - createFakePlugin('foo', packagesDir, examples: []); - - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=foo', - '--skip-confirmation', - '--pub-publish-flags', - '--server=bar' - ]); - - expect( - processRunner.recordedCalls, - contains(ProcessCall( - flutterCommand, - const ['pub', 'publish', '--server=bar', '--force'], - plugin.path))); - }); - - test('--force is only added once, regardless of plugin count', () async { - createMockCredentialFile(); - final RepositoryPackage plugin1 = - createFakePlugin('plugin_a', packagesDir, examples: []); - final RepositoryPackage plugin2 = - createFakePlugin('plugin_b', packagesDir, examples: []); - - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=plugin_a,plugin_b', - '--skip-confirmation', - '--pub-publish-flags', - '--server=bar' - ]); - - expect( - processRunner.recordedCalls, - containsAllInOrder([ - ProcessCall( - flutterCommand, - const ['pub', 'publish', '--server=bar', '--force'], - plugin1.path), - ProcessCall( - flutterCommand, - const ['pub', 'publish', '--server=bar', '--force'], - plugin2.path), - ])); - }); - - test('throws if pub publish fails', () async { - createFakePlugin('foo', packagesDir, examples: []); - - processRunner.mockProcessesForExecutable[flutterCommand] = [ - MockProcess(exitCode: 128) // pub publish - ]; - - Error? commandError; - final List output = - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=foo', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Publishing foo failed.'), - ])); - }); - - test('publish, dry run', () async { - final RepositoryPackage plugin = - createFakePlugin('foo', packagesDir, examples: []); - - final List output = - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=foo', - '--dry-run', - ]); - - expect( - processRunner.recordedCalls - .map((ProcessCall call) => call.executable), - isNot(contains('git-push'))); - expect( - output, - containsAllInOrder([ - contains('=============== DRY RUN ==============='), - contains('Running for foo'), - contains('Running `pub publish ` in ${plugin.path}...'), - contains('Tagging release foo-v0.0.1...'), - contains('Pushing tag to upstream...'), - contains('Published foo successfully!'), - ])); - }); - - test('can publish non-flutter package', () async { - const String packageName = 'a_package'; - createFakePackage(packageName, packagesDir); - - final List output = - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=$packageName', - ]); - - expect( - output, - containsAllInOrder( - [ - contains('Running `pub publish ` in /packages/a_package...'), - contains('Package published!'), - ], - ), - ); - }); - }); - - group('Tags release', () { - test('with the version and name from the pubspec.yaml', () async { - createFakePlugin('foo', packagesDir, examples: []); - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=foo', - ]); - - expect(processRunner.recordedCalls, - contains(const ProcessCall('git-tag', ['foo-v0.0.1'], null))); - }); - - test('only if publishing succeeded', () async { - createFakePlugin('foo', packagesDir, examples: []); - - processRunner.mockProcessesForExecutable[flutterCommand] = [ - MockProcess(exitCode: 128) // pub publish - ]; - - Error? commandError; - final List output = - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=foo', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Publishing foo failed.'), - ])); - expect( - processRunner.recordedCalls, - isNot(contains( - const ProcessCall('git-tag', ['foo-v0.0.1'], null)))); - }); - }); - - group('Pushes tags', () { - test('to upstream by default', () async { - createFakePlugin('foo', packagesDir, examples: []); - - mockStdin.readLineOutput = 'y'; - - final List output = - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=foo', - ]); - - expect( - processRunner.recordedCalls, - contains(const ProcessCall( - 'git-push', ['upstream', 'foo-v0.0.1'], null))); - expect( - output, - containsAllInOrder([ - contains('Pushing tag to upstream...'), - contains('Published foo successfully!'), - ])); - }); - - test('does not ask for user input if the --skip-confirmation flag is on', - () async { - createMockCredentialFile(); - createFakePlugin('foo', packagesDir, examples: []); - - final List output = - await runCapturingPrint(commandRunner, [ - 'publish', - '--skip-confirmation', - '--packages=foo', - ]); - - expect( - processRunner.recordedCalls, - contains(const ProcessCall( - 'git-push', ['upstream', 'foo-v0.0.1'], null))); - expect( - output, - containsAllInOrder([ - contains('Published foo successfully!'), - ])); - }); - - test('to upstream by default, dry run', () async { - final RepositoryPackage plugin = - createFakePlugin('foo', packagesDir, examples: []); - - mockStdin.readLineOutput = 'y'; - - final List output = await runCapturingPrint( - commandRunner, ['publish', '--packages=foo', '--dry-run']); - - expect( - processRunner.recordedCalls - .map((ProcessCall call) => call.executable), - isNot(contains('git-push'))); - expect( - output, - containsAllInOrder([ - contains('=============== DRY RUN ==============='), - contains('Running `pub publish ` in ${plugin.path}...'), - contains('Tagging release foo-v0.0.1...'), - contains('Pushing tag to upstream...'), - contains('Published foo successfully!'), - ])); - }); - - test('to different remotes based on a flag', () async { - createFakePlugin('foo', packagesDir, examples: []); - - mockStdin.readLineOutput = 'y'; - - final List output = - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=foo', - '--remote', - 'origin', - ]); - - expect( - processRunner.recordedCalls, - contains(const ProcessCall( - 'git-push', ['origin', 'foo-v0.0.1'], null))); - expect( - output, - containsAllInOrder([ - contains('Published foo successfully!'), - ])); - }); - }); - - group('Auto release (all-changed flag)', () { - test('can release newly created plugins', () async { - mockHttpResponses['plugin1'] = { - 'name': 'plugin1', - 'versions': [], - }; - - mockHttpResponses['plugin2'] = { - 'name': 'plugin2', - 'versions': [], - }; - - // Non-federated - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - // federated - final RepositoryPackage plugin2 = createFakePlugin( - 'plugin2', - packagesDir.childDirectory('plugin2'), - ); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess( - stdout: '${plugin1.pubspecFile.path}\n' - '${plugin2.pubspecFile.path}\n') - ]; - mockStdin.readLineOutput = 'y'; - - final List output = await runCapturingPrint(commandRunner, - ['publish', '--all-changed', '--base-sha=HEAD~']); - - expect( - output, - containsAllInOrder([ - contains( - 'Publishing all packages that have changed relative to "HEAD~"'), - contains('Running `pub publish ` in ${plugin1.path}...'), - contains('Running `pub publish ` in ${plugin2.path}...'), - contains('plugin1 - \x1B[32mpublished\x1B[0m'), - contains('plugin2/plugin2 - \x1B[32mpublished\x1B[0m'), - ])); - expect( - processRunner.recordedCalls, - contains(const ProcessCall( - 'git-push', ['upstream', 'plugin1-v0.0.1'], null))); - expect( - processRunner.recordedCalls, - contains(const ProcessCall( - 'git-push', ['upstream', 'plugin2-v0.0.1'], null))); - }); - - test('can release newly created plugins, while there are existing plugins', - () async { - mockHttpResponses['plugin0'] = { - 'name': 'plugin0', - 'versions': ['0.0.1'], - }; - - mockHttpResponses['plugin1'] = { - 'name': 'plugin1', - 'versions': [], - }; - - mockHttpResponses['plugin2'] = { - 'name': 'plugin2', - 'versions': [], - }; - - // The existing plugin. - createFakePlugin('plugin0', packagesDir); - // Non-federated - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - // federated - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir.childDirectory('plugin2')); - - // Git results for plugin0 having been released already, and plugin1 and - // plugin2 being new. - processRunner.mockProcessesForExecutable['git-tag'] = [ - MockProcess(stdout: 'plugin0-v0.0.1\n') - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess( - stdout: '${plugin1.pubspecFile.path}\n' - '${plugin2.pubspecFile.path}\n') - ]; - - mockStdin.readLineOutput = 'y'; - - final List output = await runCapturingPrint(commandRunner, - ['publish', '--all-changed', '--base-sha=HEAD~']); - - expect( - output, - containsAllInOrder([ - 'Running `pub publish ` in ${plugin1.path}...\n', - 'Running `pub publish ` in ${plugin2.path}...\n', - ])); - expect( - processRunner.recordedCalls, - contains(const ProcessCall( - 'git-push', ['upstream', 'plugin1-v0.0.1'], null))); - expect( - processRunner.recordedCalls, - contains(const ProcessCall( - 'git-push', ['upstream', 'plugin2-v0.0.1'], null))); - }); - - test('can release newly created plugins, dry run', () async { - mockHttpResponses['plugin1'] = { - 'name': 'plugin1', - 'versions': [], - }; - - mockHttpResponses['plugin2'] = { - 'name': 'plugin2', - 'versions': [], - }; - - // Non-federated - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - // federated - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir.childDirectory('plugin2')); - - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess( - stdout: '${plugin1.pubspecFile.path}\n' - '${plugin2.pubspecFile.path}\n') - ]; - mockStdin.readLineOutput = 'y'; - - final List output = await runCapturingPrint( - commandRunner, [ - 'publish', - '--all-changed', - '--base-sha=HEAD~', - '--dry-run' - ]); - - expect( - output, - containsAllInOrder([ - contains('=============== DRY RUN ==============='), - contains('Running `pub publish ` in ${plugin1.path}...'), - contains('Tagging release plugin1-v0.0.1...'), - contains('Pushing tag to upstream...'), - contains('Published plugin1 successfully!'), - contains('Running `pub publish ` in ${plugin2.path}...'), - contains('Tagging release plugin2-v0.0.1...'), - contains('Pushing tag to upstream...'), - contains('Published plugin2 successfully!'), - ])); - expect( - processRunner.recordedCalls - .map((ProcessCall call) => call.executable), - isNot(contains('git-push'))); - }); - - test('version change triggers releases.', () async { - mockHttpResponses['plugin1'] = { - 'name': 'plugin1', - 'versions': ['0.0.1'], - }; - - mockHttpResponses['plugin2'] = { - 'name': 'plugin2', - 'versions': ['0.0.1'], - }; - - // Non-federated - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir, version: '0.0.2'); - // federated - final RepositoryPackage plugin2 = createFakePlugin( - 'plugin2', packagesDir.childDirectory('plugin2'), - version: '0.0.2'); - - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess( - stdout: '${plugin1.pubspecFile.path}\n' - '${plugin2.pubspecFile.path}\n') - ]; - - mockStdin.readLineOutput = 'y'; - - final List output2 = await runCapturingPrint(commandRunner, - ['publish', '--all-changed', '--base-sha=HEAD~']); - expect( - output2, - containsAllInOrder([ - contains('Running `pub publish ` in ${plugin1.path}...'), - contains('Published plugin1 successfully!'), - contains('Running `pub publish ` in ${plugin2.path}...'), - contains('Published plugin2 successfully!'), - ])); - expect( - processRunner.recordedCalls, - contains(const ProcessCall( - 'git-push', ['upstream', 'plugin1-v0.0.2'], null))); - expect( - processRunner.recordedCalls, - contains(const ProcessCall( - 'git-push', ['upstream', 'plugin2-v0.0.2'], null))); - }); - - test( - 'delete package will not trigger publish but exit the command successfully!', - () async { - mockHttpResponses['plugin1'] = { - 'name': 'plugin1', - 'versions': ['0.0.1'], - }; - - mockHttpResponses['plugin2'] = { - 'name': 'plugin2', - 'versions': ['0.0.1'], - }; - - // Non-federated - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir, version: '0.0.2'); - // federated - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir.childDirectory('plugin2')); - plugin2.directory.deleteSync(recursive: true); - - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess( - stdout: '${plugin1.pubspecFile.path}\n' - '${plugin2.pubspecFile.path}\n') - ]; - - mockStdin.readLineOutput = 'y'; - - final List output2 = await runCapturingPrint(commandRunner, - ['publish', '--all-changed', '--base-sha=HEAD~']); - expect( - output2, - containsAllInOrder([ - contains('Running `pub publish ` in ${plugin1.path}...'), - contains('Published plugin1 successfully!'), - contains( - 'The pubspec file for plugin2/plugin2 does not exist, so no publishing will happen.\nSafe to ignore if the package is deleted in this commit.\n'), - contains('SKIPPING: package deleted'), - contains('skipped (with warning)'), - ])); - expect( - processRunner.recordedCalls, - contains(const ProcessCall( - 'git-push', ['upstream', 'plugin1-v0.0.2'], null))); - }); - - test('Existing versions do not trigger release, also prints out message.', - () async { - mockHttpResponses['plugin1'] = { - 'name': 'plugin1', - 'versions': ['0.0.2'], - }; - - mockHttpResponses['plugin2'] = { - 'name': 'plugin2', - 'versions': ['0.0.2'], - }; - - // Non-federated - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir, version: '0.0.2'); - // federated - final RepositoryPackage plugin2 = createFakePlugin( - 'plugin2', packagesDir.childDirectory('plugin2'), - version: '0.0.2'); - - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess( - stdout: '${plugin1.pubspecFile.path}\n' - '${plugin2.pubspecFile.path}\n') - ]; - processRunner.mockProcessesForExecutable['git-tag'] = [ - MockProcess( - stdout: 'plugin1-v0.0.2\n' - 'plugin2-v0.0.2\n') - ]; - - final List output = await runCapturingPrint(commandRunner, - ['publish', '--all-changed', '--base-sha=HEAD~']); - - expect( - output, - containsAllInOrder([ - contains('plugin1 0.0.2 has already been published'), - contains('SKIPPING: already published'), - contains('plugin2 0.0.2 has already been published'), - contains('SKIPPING: already published'), - ])); - - expect( - processRunner.recordedCalls - .map((ProcessCall call) => call.executable), - isNot(contains('git-push'))); - }); - - test( - 'Existing versions do not trigger release, but fail if the tags do not exist.', - () async { - mockHttpResponses['plugin1'] = { - 'name': 'plugin1', - 'versions': ['0.0.2'], - }; - - mockHttpResponses['plugin2'] = { - 'name': 'plugin2', - 'versions': ['0.0.2'], - }; - - // Non-federated - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir, version: '0.0.2'); - // federated - final RepositoryPackage plugin2 = createFakePlugin( - 'plugin2', packagesDir.childDirectory('plugin2'), - version: '0.0.2'); - - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess( - stdout: '${plugin1.pubspecFile.path}\n' - '${plugin2.pubspecFile.path}\n') - ]; - - Error? commandError; - final List output = await runCapturingPrint(commandRunner, - ['publish', '--all-changed', '--base-sha=HEAD~'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('plugin1 0.0.2 has already been published, ' - 'however the git release tag (plugin1-v0.0.2) was not found.'), - contains('plugin2 0.0.2 has already been published, ' - 'however the git release tag (plugin2-v0.0.2) was not found.'), - ])); - expect( - processRunner.recordedCalls - .map((ProcessCall call) => call.executable), - isNot(contains('git-push'))); - }); - - test('No version change does not release any plugins', () async { - // Non-federated - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - // federated - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir.childDirectory('plugin2')); - - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess( - stdout: '${plugin1.libDirectory.childFile('plugin1.dart').path}\n' - '${plugin2.libDirectory.childFile('plugin2.dart').path}\n') - ]; - - final List output = await runCapturingPrint(commandRunner, - ['publish', '--all-changed', '--base-sha=HEAD~']); - - expect(output, containsAllInOrder(['Ran for 0 package(s)'])); - expect( - processRunner.recordedCalls - .map((ProcessCall call) => call.executable), - isNot(contains('git-push'))); - }); - - test('Do not release flutter_plugin_tools', () async { - mockHttpResponses['plugin1'] = { - 'name': 'flutter_plugin_tools', - 'versions': [], - }; - - final RepositoryPackage flutterPluginTools = - createFakePlugin('flutter_plugin_tools', packagesDir); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: flutterPluginTools.pubspecFile.path) - ]; - - final List output = await runCapturingPrint(commandRunner, - ['publish', '--all-changed', '--base-sha=HEAD~']); - - expect( - output, - containsAllInOrder([ - contains( - 'SKIPPING: publishing flutter_plugin_tools via the tool is not supported') - ])); - expect( - output.contains( - 'Running `pub publish ` in ${flutterPluginTools.path}...', - ), - isFalse); - expect( - processRunner.recordedCalls - .map((ProcessCall call) => call.executable), - isNot(contains('git-push'))); - }); - }); -} - -/// An extension of [RecordingProcessRunner] that stores 'flutter pub publish' -/// calls so that their input streams can be checked in tests. -class TestProcessRunner extends RecordingProcessRunner { - // Most recent returned publish process. - late MockProcess mockPublishProcess; - - @override - Future start(String executable, List args, - {Directory? workingDirectory}) async { - final io.Process process = - await super.start(executable, args, workingDirectory: workingDirectory); - if (executable == getFlutterCommand(const LocalPlatform()) && - args.isNotEmpty && - args[0] == 'pub' && - args[1] == 'publish') { - mockPublishProcess = process as MockProcess; - } - return process; - } -} - -class MockStdin extends Mock implements io.Stdin { - List> mockUserInputs = >[]; - final StreamController> _controller = StreamController>(); - String? readLineOutput; - - @override - Stream transform(StreamTransformer, S> streamTransformer) { - mockUserInputs.forEach(_addUserInputsToSteam); - return _controller.stream.transform(streamTransformer); - } - - @override - StreamSubscription> listen(void Function(List event)? onData, - {Function? onError, void Function()? onDone, bool? cancelOnError}) { - return _controller.stream.listen(onData, - onError: onError, onDone: onDone, cancelOnError: cancelOnError); - } - - @override - String? readLineSync( - {Encoding encoding = io.systemEncoding, - bool retainNewlines = false}) => - readLineOutput; - - void _addUserInputsToSteam(List input) => _controller.add(input); -} diff --git a/script/tool/test/pubspec_check_command_test.dart b/script/tool/test/pubspec_check_command_test.dart deleted file mode 100644 index 7a9c0cec7cbc..000000000000 --- a/script/tool/test/pubspec_check_command_test.dart +++ /dev/null @@ -1,1143 +0,0 @@ -// 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 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/pubspec_check_command.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -/// Returns the top section of a pubspec.yaml for a package named [name], -/// for either a flutter/packages or flutter/plugins package depending on -/// the values of [isPlugin]. -/// -/// By default it will create a header that includes all of the expected -/// values, elements can be changed via arguments to create incorrect -/// entries. -/// -/// If [includeRepository] is true, by default the path in the link will -/// be "packages/[name]"; a different "packages"-relative path can be -/// provided with [repositoryPackagesDirRelativePath]. -String _headerSection( - String name, { - bool isPlugin = false, - bool includeRepository = true, - String repositoryBranch = 'main', - String? repositoryPackagesDirRelativePath, - bool includeHomepage = false, - bool includeIssueTracker = true, - bool publishable = true, - String? description, -}) { - final String repositoryPath = repositoryPackagesDirRelativePath ?? name; - final List repoLinkPathComponents = [ - 'flutter', - if (isPlugin) 'plugins' else 'packages', - 'tree', - repositoryBranch, - 'packages', - repositoryPath, - ]; - final String repoLink = - 'https://github.com/${repoLinkPathComponents.join('/')}'; - final String issueTrackerLink = 'https://github.com/flutter/flutter/issues?' - 'q=is%3Aissue+is%3Aopen+label%3A%22p%3A+$name%22'; - description ??= 'A test package for validating that the pubspec.yaml ' - 'follows repo best practices.'; - return ''' -name: $name -description: $description -${includeRepository ? 'repository: $repoLink' : ''} -${includeHomepage ? 'homepage: $repoLink' : ''} -${includeIssueTracker ? 'issue_tracker: $issueTrackerLink' : ''} -version: 1.0.0 -${publishable ? '' : "publish_to: 'none'"} -'''; -} - -String _environmentSection({ - String dartConstraint = '>=2.12.0 <3.0.0', - String? flutterConstraint = '>=2.0.0', -}) { - return [ - 'environment:', - ' sdk: "$dartConstraint"', - if (flutterConstraint != null) ' flutter: "$flutterConstraint"', - '', - ].join('\n'); -} - -String _flutterSection({ - bool isPlugin = false, - String? implementedPackage, - Map> pluginPlatformDetails = - const >{}, -}) { - String pluginEntry = ''' - plugin: -${implementedPackage == null ? '' : ' implements: $implementedPackage'} - platforms: -'''; - - for (final MapEntry> platform - in pluginPlatformDetails.entries) { - pluginEntry += ''' - ${platform.key}: -'''; - for (final MapEntry detail in platform.value.entries) { - pluginEntry += ''' - ${detail.key}: ${detail.value} -'''; - } - } - - return ''' -flutter: -${isPlugin ? pluginEntry : ''} -'''; -} - -String _dependenciesSection() { - return ''' -dependencies: - flutter: - sdk: flutter -'''; -} - -String _devDependenciesSection() { - return ''' -dev_dependencies: - flutter_test: - sdk: flutter -'''; -} - -String _falseSecretsSection() { - return ''' -false_secrets: - - /lib/main.dart -'''; -} - -void main() { - group('test pubspec_check_command', () { - late CommandRunner runner; - late RecordingProcessRunner processRunner; - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = fileSystem.currentDirectory.childDirectory('packages'); - createPackagesDirectory(parentDir: packagesDir.parent); - processRunner = RecordingProcessRunner(); - final PubspecCheckCommand command = PubspecCheckCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner( - 'pubspec_check_command', 'Test for pubspec_check_command'); - runner.addCommand(command); - }); - - test('passes for a plugin following conventions', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true)} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -${_falseSecretsSection()} -'''); - - plugin.getExamples().first.pubspecFile.writeAsStringSync(''' -${_headerSection( - 'plugin_example', - publishable: false, - includeRepository: false, - includeIssueTracker: false, - )} -${_environmentSection()} -${_dependenciesSection()} -${_flutterSection()} -'''); - - final List output = await runCapturingPrint(runner, [ - 'pubspec-check', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin...'), - contains('Running for plugin/example...'), - contains('No issues found!'), - ]), - ); - }); - - test('passes for a Flutter package following conventions', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - package.pubspecFile.writeAsStringSync(''' -${_headerSection('a_package')} -${_environmentSection()} -${_dependenciesSection()} -${_devDependenciesSection()} -${_flutterSection()} -${_falseSecretsSection()} -'''); - - package.getExamples().first.pubspecFile.writeAsStringSync(''' -${_headerSection( - 'a_package', - publishable: false, - includeRepository: false, - includeIssueTracker: false, - )} -${_environmentSection()} -${_dependenciesSection()} -${_flutterSection()} -'''); - - final List output = await runCapturingPrint(runner, [ - 'pubspec-check', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for a_package...'), - contains('Running for a_package/example...'), - contains('No issues found!'), - ]), - ); - }); - - test('passes for a minimal package following conventions', () async { - final RepositoryPackage package = - createFakePackage('package', packagesDir, examples: []); - - package.pubspecFile.writeAsStringSync(''' -${_headerSection('package')} -${_environmentSection()} -${_dependenciesSection()} -'''); - - final List output = await runCapturingPrint(runner, [ - 'pubspec-check', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for package...'), - contains('No issues found!'), - ]), - ); - }); - - test('fails when homepage is included', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true, includeHomepage: true)} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Found a "homepage" entry; only "repository" should be used.'), - ]), - ); - }); - - test('fails when repository is missing', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true, includeRepository: false)} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Missing "repository"'), - ]), - ); - }); - - test('fails when homepage is given instead of repository', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true, includeHomepage: true, includeRepository: false)} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Found a "homepage" entry; only "repository" should be used.'), - ]), - ); - }); - - test('fails when repository package name is incorrect', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true, repositoryPackagesDirRelativePath: 'different_plugin')} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The "repository" link should end with the package path.'), - ]), - ); - }); - - test('fails when repository uses master instead of main', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true, repositoryBranch: 'master')} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The "repository" link should use "main", not "master".'), - ]), - ); - }); - - test('fails when issue tracker is missing', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true, includeIssueTracker: false)} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('A package should have an "issue_tracker" link'), - ]), - ); - }); - - test('fails when description is too short', () async { - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', packagesDir.childDirectory('a_plugin'), - examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true, description: 'Too short')} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('"description" is too short. pub.dev recommends package ' - 'descriptions of 60-180 characters.'), - ]), - ); - }); - - test( - 'allows short descriptions for non-app-facing parts of federated plugins', - () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true, description: 'Too short')} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('"description" is too short. pub.dev recommends package ' - 'descriptions of 60-180 characters.'), - ]), - ); - }); - - test('fails when description is too long', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - const String description = 'This description is too long. It just goes ' - 'on and on and on and on and on. pub.dev will down-score it because ' - 'there is just too much here. Someone shoul really cut this down to just ' - 'the core description so that search results are more useful and the ' - 'package does not lose pub points.'; - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true, description: description)} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('"description" is too long. pub.dev recommends package ' - 'descriptions of 60-180 characters.'), - ]), - ); - }); - - test('fails when environment section is out of order', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true)} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -${_environmentSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Major sections should follow standard repository ordering:'), - ]), - ); - }); - - test('fails when flutter section is out of order', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true)} -${_flutterSection(isPlugin: true)} -${_environmentSection()} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Major sections should follow standard repository ordering:'), - ]), - ); - }); - - test('fails when dependencies section is out of order', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true)} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_devDependenciesSection()} -${_dependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Major sections should follow standard repository ordering:'), - ]), - ); - }); - - test('fails when dev_dependencies section is out of order', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true)} -${_environmentSection()} -${_devDependenciesSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Major sections should follow standard repository ordering:'), - ]), - ); - }); - - test('fails when false_secrets section is out of order', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true)} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_falseSecretsSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Major sections should follow standard repository ordering:'), - ]), - ); - }); - - test('fails when an implemenation package is missing "implements"', - () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin_a_foo', packagesDir.childDirectory('plugin_a'), - examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin_a_foo', isPlugin: true)} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Missing "implements: plugin_a" in "plugin" section.'), - ]), - ); - }); - - test('fails when an implemenation package has the wrong "implements"', - () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin_a_foo', packagesDir.childDirectory('plugin_a'), - examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin_a_foo', isPlugin: true)} -${_environmentSection()} -${_flutterSection(isPlugin: true, implementedPackage: 'plugin_a_foo')} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Expecetd "implements: plugin_a"; ' - 'found "implements: plugin_a_foo".'), - ]), - ); - }); - - test('passes for a correct implemenation package', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin_a_foo', packagesDir.childDirectory('plugin_a'), - examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection( - 'plugin_a_foo', - isPlugin: true, - repositoryPackagesDirRelativePath: 'plugin_a/plugin_a_foo', - )} -${_environmentSection()} -${_flutterSection(isPlugin: true, implementedPackage: 'plugin_a')} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - final List output = - await runCapturingPrint(runner, ['pubspec-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin_a_foo...'), - contains('No issues found!'), - ]), - ); - }); - - test('fails when a "default_package" looks incorrect', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin_a', packagesDir.childDirectory('plugin_a'), - examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection( - 'plugin_a', - isPlugin: true, - repositoryPackagesDirRelativePath: 'plugin_a/plugin_a', - )} -${_environmentSection()} -${_flutterSection( - isPlugin: true, - pluginPlatformDetails: >{ - 'android': {'default_package': 'plugin_b_android'} - }, - )} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - '"plugin_b_android" is not an expected implementation name for "plugin_a"'), - ]), - ); - }); - - test( - 'fails when a "default_package" does not have a corresponding dependency', - () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin_a', packagesDir.childDirectory('plugin_a'), - examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection( - 'plugin_a', - isPlugin: true, - repositoryPackagesDirRelativePath: 'plugin_a/plugin_a', - )} -${_environmentSection()} -${_flutterSection( - isPlugin: true, - pluginPlatformDetails: >{ - 'android': {'default_package': 'plugin_a_android'} - }, - )} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following default_packages are missing corresponding ' - 'dependencies:\n plugin_a_android'), - ]), - ); - }); - - test('passes for an app-facing package without "implements"', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin_a', packagesDir.childDirectory('plugin_a'), - examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection( - 'plugin_a', - isPlugin: true, - repositoryPackagesDirRelativePath: 'plugin_a/plugin_a', - )} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - final List output = - await runCapturingPrint(runner, ['pubspec-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin_a/plugin_a...'), - contains('No issues found!'), - ]), - ); - }); - - test('passes for a platform interface package without "implements"', - () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin_a_platform_interface', packagesDir.childDirectory('plugin_a'), - examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection( - 'plugin_a_platform_interface', - isPlugin: true, - repositoryPackagesDirRelativePath: - 'plugin_a/plugin_a_platform_interface', - )} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - final List output = - await runCapturingPrint(runner, ['pubspec-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin_a_platform_interface...'), - contains('No issues found!'), - ]), - ); - }); - - test('validates some properties even for unpublished packages', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin_a_foo', packagesDir.childDirectory('plugin_a'), - examples: []); - - // Environment section is in the wrong location. - // Missing 'implements'. - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin_a_foo', isPlugin: true, publishable: false)} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -${_environmentSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Major sections should follow standard repository ordering:'), - contains('Missing "implements: plugin_a" in "plugin" section.'), - ]), - ); - }); - - test('ignores some checks for unpublished packages', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - // Missing metadata that is only useful for published packages, such as - // repository and issue tracker. - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection( - 'plugin', - isPlugin: true, - publishable: false, - includeRepository: false, - includeIssueTracker: false, - )} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - final List output = - await runCapturingPrint(runner, ['pubspec-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin...'), - contains('No issues found!'), - ]), - ); - }); - - test('fails when a Flutter package has a too-low minimum Flutter version', - () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - isFlutter: true, examples: []); - - package.pubspecFile.writeAsStringSync(''' -${_headerSection('a_package')} -${_environmentSection(flutterConstraint: '>=2.10.0')} -${_dependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'pubspec-check', - '--min-min-flutter-version', - '3.0.0' - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Minimum allowed Flutter version 2.10.0 is less than 3.0.0'), - ]), - ); - }); - - test( - 'passes when a Flutter package requires exactly the minimum Flutter version', - () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - isFlutter: true, examples: []); - - package.pubspecFile.writeAsStringSync(''' -${_headerSection('a_package')} -${_environmentSection(flutterConstraint: '>=3.0.0')} -${_dependenciesSection()} -'''); - - final List output = await runCapturingPrint(runner, - ['pubspec-check', '--min-min-flutter-version', '3.0.0']); - - expect( - output, - containsAllInOrder([ - contains('Running for a_package...'), - contains('No issues found!'), - ]), - ); - }); - - test( - 'passes when a Flutter package requires a higher minimum Flutter version', - () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - isFlutter: true, examples: []); - - package.pubspecFile.writeAsStringSync(''' -${_headerSection('a_package')} -${_environmentSection(flutterConstraint: '>=3.3.0')} -${_dependenciesSection()} -'''); - - final List output = await runCapturingPrint(runner, - ['pubspec-check', '--min-min-flutter-version', '3.0.0']); - - expect( - output, - containsAllInOrder([ - contains('Running for a_package...'), - contains('No issues found!'), - ]), - ); - }); - - test('fails when a non-Flutter package has a too-low minimum Dart version', - () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, examples: []); - - package.pubspecFile.writeAsStringSync(''' -${_headerSection('a_package')} -${_environmentSection(dartConstraint: '>=2.14.0 <3.0.0', flutterConstraint: null)} -${_dependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check', '--min-min-dart-version', '2.17.0'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Minimum allowed Dart version 2.14.0 is less than 2.17.0'), - ]), - ); - }); - - test( - 'passes when a non-Flutter package requires exactly the minimum Dart version', - () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - isFlutter: true, examples: []); - - package.pubspecFile.writeAsStringSync(''' -${_headerSection('a_package')} -${_environmentSection(dartConstraint: '>=2.17.0 <3.0.0', flutterConstraint: null)} -${_dependenciesSection()} -'''); - - final List output = await runCapturingPrint(runner, - ['pubspec-check', '--min-min-dart-version', '2.17.0']); - - expect( - output, - containsAllInOrder([ - contains('Running for a_package...'), - contains('No issues found!'), - ]), - ); - }); - - test( - 'passes when a non-Flutter package requires a higher minimum Dart version', - () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - isFlutter: true, examples: []); - - package.pubspecFile.writeAsStringSync(''' -${_headerSection('a_package')} -${_environmentSection(dartConstraint: '>=2.18.0 <3.0.0', flutterConstraint: null)} -${_dependenciesSection()} -'''); - - final List output = await runCapturingPrint(runner, - ['pubspec-check', '--min-min-dart-version', '2.17.0']); - - expect( - output, - containsAllInOrder([ - contains('Running for a_package...'), - contains('No issues found!'), - ]), - ); - }); - }); - - group('test pubspec_check_command on Windows', () { - late CommandRunner runner; - late RecordingProcessRunner processRunner; - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - - setUp(() { - fileSystem = MemoryFileSystem(style: FileSystemStyle.windows); - mockPlatform = MockPlatform(isWindows: true); - packagesDir = fileSystem.currentDirectory.childDirectory('packages'); - createPackagesDirectory(parentDir: packagesDir.parent); - processRunner = RecordingProcessRunner(); - final PubspecCheckCommand command = PubspecCheckCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner( - 'pubspec_check_command', 'Test for pubspec_check_command'); - runner.addCommand(command); - }); - - test('repository check works', () async { - final RepositoryPackage package = - createFakePackage('package', packagesDir, examples: []); - - package.pubspecFile.writeAsStringSync(''' -${_headerSection('package')} -${_environmentSection()} -${_dependenciesSection()} -'''); - - final List output = - await runCapturingPrint(runner, ['pubspec-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for package...'), - contains('No issues found!'), - ]), - ); - }); - }); -} diff --git a/script/tool/test/readme_check_command_test.dart b/script/tool/test/readme_check_command_test.dart deleted file mode 100644 index eb2b6c8e7512..000000000000 --- a/script/tool/test/readme_check_command_test.dart +++ /dev/null @@ -1,741 +0,0 @@ -// 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 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; -import 'package:flutter_plugin_tools/src/readme_check_command.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - late CommandRunner runner; - late RecordingProcessRunner processRunner; - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = fileSystem.currentDirectory.childDirectory('packages'); - createPackagesDirectory(parentDir: packagesDir.parent); - processRunner = RecordingProcessRunner(); - final ReadmeCheckCommand command = ReadmeCheckCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner( - 'readme_check_command', 'Test for readme_check_command'); - runner.addCommand(command); - }); - - test('prints paths of checked READMEs', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - examples: ['example1', 'example2']); - for (final RepositoryPackage example in package.getExamples()) { - example.readmeFile.writeAsStringSync('A readme'); - } - getExampleDir(package).childFile('README.md').writeAsStringSync('A readme'); - - final List output = - await runCapturingPrint(runner, ['readme-check']); - - expect( - output, - containsAll([ - contains(' Checking README.md...'), - contains(' Checking example/README.md...'), - contains(' Checking example/example1/README.md...'), - contains(' Checking example/example2/README.md...'), - ]), - ); - }); - - test('fails when package README is missing', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - package.readmeFile.deleteSync(); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Missing README.md'), - ]), - ); - }); - - test('passes when example README is missing', () async { - createFakePackage('a_package', packagesDir); - - final List output = - await runCapturingPrint(runner, ['readme-check']); - - expect( - output, - containsAllInOrder([ - contains('No README for example'), - ]), - ); - }); - - test('does not inculde non-example subpackages', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - const String subpackageName = 'special_test'; - final RepositoryPackage miscSubpackage = - createFakePackage(subpackageName, package.directory); - miscSubpackage.readmeFile.delete(); - - final List output = - await runCapturingPrint(runner, ['readme-check']); - - expect(output, isNot(contains(subpackageName))); - }); - - test('fails when README still has plugin template boilerplate', () async { - final RepositoryPackage package = createFakePlugin('a_plugin', packagesDir); - package.readmeFile.writeAsStringSync(''' -## Getting Started - -This project is a starting point for a Flutter -[plug-in package](https://flutter.dev/developing-packages/), -a specialized package that includes platform-specific implementation code for -Android and/or iOS. - -For help getting started with Flutter development, view the -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The boilerplate section about getting started with Flutter ' - 'should not be left in.'), - contains('Contains template boilerplate'), - ]), - ); - }); - - test('fails when example README still has application template boilerplate', - () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - package.getExamples().first.readmeFile.writeAsStringSync(''' -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The boilerplate section about getting started with Flutter ' - 'should not be left in.'), - contains('Contains template boilerplate'), - ]), - ); - }); - - test( - 'fails when a plugin implementation package example README has the ' - 'template boilerplate', () async { - final RepositoryPackage package = createFakePlugin( - 'a_plugin_ios', packagesDir.childDirectory('a_plugin')); - package.getExamples().first.readmeFile.writeAsStringSync(''' -# a_plugin_ios_example - -Demonstrates how to use the a_plugin_ios plugin. -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The boilerplate should not be left in for a federated plugin ' - "implementation package's example."), - contains('Contains template boilerplate'), - ]), - ); - }); - - test( - 'allows the template boilerplate in the example README for packages ' - 'other than plugin implementation packages', () async { - final RepositoryPackage package = createFakePlugin( - 'a_plugin', - packagesDir.childDirectory('a_plugin'), - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - }, - ); - // Write a README with an OS support table so that the main README check - // passes. - package.readmeFile.writeAsStringSync(''' -# a_plugin - -| | Android | -|----------------|---------| -| **Support** | SDK 19+ | - -A great plugin. -'''); - package.getExamples().first.readmeFile.writeAsStringSync(''' -# a_plugin_example - -Demonstrates how to use the a_plugin plugin. -'''); - - final List output = - await runCapturingPrint(runner, ['readme-check']); - - expect( - output, - containsAll([ - contains(' Checking README.md...'), - contains(' Checking example/README.md...'), - ]), - ); - }); - - test( - 'fails when a plugin implementation package example README does not have ' - 'the repo-standard message', () async { - final RepositoryPackage package = createFakePlugin( - 'a_plugin_ios', packagesDir.childDirectory('a_plugin')); - package.getExamples().first.readmeFile.writeAsStringSync(''' -# a_plugin_ios_example - -Some random description. -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The example README for a platform implementation package ' - 'should warn readers about its intended use. Please copy the ' - 'example README from another implementation package in this ' - 'repository.'), - contains('Missing implementation package example warning'), - ]), - ); - }); - - test('passes for a plugin implementation package with the expected content', - () async { - final RepositoryPackage package = createFakePlugin( - 'a_plugin', - packagesDir.childDirectory('a_plugin'), - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - }, - ); - // Write a README with an OS support table so that the main README check - // passes. - package.readmeFile.writeAsStringSync(''' -# a_plugin - -| | Android | -|----------------|---------| -| **Support** | SDK 19+ | - -A great plugin. -'''); - package.getExamples().first.readmeFile.writeAsStringSync(''' -# Platform Implementation Test App - -This is a test app for manual testing and automated integration testing -of this platform implementation. It is not intended to demonstrate actual use of -this package, since the intent is that plugin clients use the app-facing -package. - -Unless you are making changes to this implementation package, this example is -very unlikely to be relevant. -'''); - - final List output = - await runCapturingPrint(runner, ['readme-check']); - - expect( - output, - containsAll([ - contains(' Checking README.md...'), - contains(' Checking example/README.md...'), - ]), - ); - }); - - test( - 'fails when multi-example top-level example directory README still has ' - 'application template boilerplate', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - examples: ['example1', 'example2']); - package.directory - .childDirectory('example') - .childFile('README.md') - .writeAsStringSync(''' -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The boilerplate section about getting started with Flutter ' - 'should not be left in.'), - contains('Contains template boilerplate'), - ]), - ); - }); - - group('plugin OS support', () { - test( - 'does not check support table for anything other than app-facing plugin packages', - () async { - const String federatedPluginName = 'a_federated_plugin'; - final Directory federatedDir = - packagesDir.childDirectory(federatedPluginName); - // A non-plugin package. - createFakePackage('a_package', packagesDir); - // Non-app-facing parts of a federated plugin. - createFakePlugin( - '${federatedPluginName}_platform_interface', federatedDir); - createFakePlugin('${federatedPluginName}_android', federatedDir); - - final List output = await runCapturingPrint(runner, [ - 'readme-check', - ]); - - expect( - output, - containsAll([ - contains('Running for a_package...'), - contains('Running for a_federated_plugin_platform_interface...'), - contains('Running for a_federated_plugin_android...'), - contains('No issues found!'), - ]), - ); - }); - - test('fails when non-federated plugin is missing an OS support table', - () async { - createFakePlugin('a_plugin', packagesDir); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No OS support table found'), - ]), - ); - }); - - test( - 'fails when app-facing part of a federated plugin is missing an OS support table', - () async { - createFakePlugin('a_plugin', packagesDir.childDirectory('a_plugin')); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No OS support table found'), - ]), - ); - }); - - test('fails the OS support table is missing the header', () async { - final RepositoryPackage plugin = - createFakePlugin('a_plugin', packagesDir); - - plugin.readmeFile.writeAsStringSync(''' -A very useful plugin. - -| **Support** | SDK 21+ | iOS 10+* | [See `camera_web `][1] | -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('OS support table does not have the expected header format'), - ]), - ); - }); - - test('fails if the OS support table is missing a supported OS', () async { - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - platformWeb: const PlatformDetails(PlatformSupport.inline), - }, - ); - - plugin.readmeFile.writeAsStringSync(''' -A very useful plugin. - -| | Android | iOS | -|----------------|---------|----------| -| **Support** | SDK 21+ | iOS 10+* | -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains(' OS support table does not match supported platforms:\n' - ' Actual: android, ios, web\n' - ' Documented: android, ios'), - contains('Incorrect OS support table'), - ]), - ); - }); - - test('fails if the OS support table lists an extra OS', () async { - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - plugin.readmeFile.writeAsStringSync(''' -A very useful plugin. - -| | Android | iOS | Web | -|----------------|---------|----------|------------------------| -| **Support** | SDK 21+ | iOS 10+* | [See `camera_web `][1] | -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains(' OS support table does not match supported platforms:\n' - ' Actual: android, ios\n' - ' Documented: android, ios, web'), - contains('Incorrect OS support table'), - ]), - ); - }); - - test('fails if the OS support table has unexpected OS formatting', - () async { - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - platformMacOS: const PlatformDetails(PlatformSupport.inline), - platformWeb: const PlatformDetails(PlatformSupport.inline), - }, - ); - - plugin.readmeFile.writeAsStringSync(''' -A very useful plugin. - -| | android | ios | MacOS | web | -|----------------|---------|----------|-------|------------------------| -| **Support** | SDK 21+ | iOS 10+* | 10.11 | [See `camera_web `][1] | -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains(' Incorrect OS capitalization: android, ios, MacOS, web\n' - ' Please use standard capitalizations: Android, iOS, macOS, Web\n'), - contains('Incorrect OS support formatting'), - ]), - ); - }); - }); - - group('code blocks', () { - test('fails on missing info string', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - package.readmeFile.writeAsStringSync(''' -Example: - -``` -void main() { - // ... -} -``` -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Code block at line 3 is missing a language identifier.'), - contains('Missing language identifier for code block'), - ]), - ); - }); - - test('allows unknown info strings', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - package.readmeFile.writeAsStringSync(''' -Example: - -```someunknowninfotag -A B C -``` -'''); - - final List output = await runCapturingPrint(runner, [ - 'readme-check', - ]); - - expect( - output, - containsAll([ - contains('Running for a_package...'), - contains('No issues found!'), - ]), - ); - }); - - test('allows space around info strings', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - package.readmeFile.writeAsStringSync(''' -Example: - -``` dart -A B C -``` -'''); - - final List output = await runCapturingPrint(runner, [ - 'readme-check', - ]); - - expect( - output, - containsAll([ - contains('Running for a_package...'), - contains('No issues found!'), - ]), - ); - }); - - test('passes when excerpt requirement is met', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', - packagesDir, - extraFiles: [kReadmeExcerptConfigPath], - ); - - package.readmeFile.writeAsStringSync(''' -Example: - - -```dart -A B C -``` -'''); - - final List output = await runCapturingPrint( - runner, ['readme-check', '--require-excerpts']); - - expect( - output, - containsAll([ - contains('Running for a_package...'), - contains('No issues found!'), - ]), - ); - }); - - test('fails when excerpts are used but the package is not configured', - () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - package.readmeFile.writeAsStringSync(''' -Example: - - -```dart -A B C -``` -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check', '--require-excerpts'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('code-excerpt tag found, but the package is not configured ' - 'for excerpting. Follow the instructions at\n' - 'https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages\n' - 'for setting up a build.excerpt.yaml file.'), - contains('Missing code-excerpt configuration'), - ]), - ); - }); - - test('fails on missing excerpt tag when requested', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - package.readmeFile.writeAsStringSync(''' -Example: - -```dart -A B C -``` -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check', '--require-excerpts'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Dart code block at line 3 is not managed by code-excerpt.'), - // Ensure that the failure message links to instructions. - contains( - 'https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages'), - contains('Missing code-excerpt management for code block'), - ]), - ); - }); - }); -} diff --git a/script/tool/test/remove_dev_dependencies_test.dart b/script/tool/test/remove_dev_dependencies_test.dart deleted file mode 100644 index 776cbf197838..000000000000 --- a/script/tool/test/remove_dev_dependencies_test.dart +++ /dev/null @@ -1,102 +0,0 @@ -// 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 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/remove_dev_dependencies.dart'; -import 'package:test/test.dart'; - -import 'util.dart'; - -void main() { - late FileSystem fileSystem; - late Directory packagesDir; - late CommandRunner runner; - - setUp(() { - fileSystem = MemoryFileSystem(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - - final RemoveDevDependenciesCommand command = RemoveDevDependenciesCommand( - packagesDir, - ); - runner = CommandRunner('trim_dev_dependencies_command', - 'Test for trim_dev_dependencies_command'); - runner.addCommand(command); - }); - - void addToPubspec(RepositoryPackage package, String addition) { - final String originalContent = package.pubspecFile.readAsStringSync(); - package.pubspecFile.writeAsStringSync(''' -$originalContent -$addition -'''); - } - - test('skips if nothing is removed', () async { - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - final List output = - await runCapturingPrint(runner, ['remove-dev-dependencies']); - - expect( - output, - containsAllInOrder([ - contains('SKIPPING: Nothing to remove.'), - ]), - ); - }); - - test('removes dev_dependencies', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - addToPubspec(package, ''' -dev_dependencies: - some_dependency: ^2.1.8 - another_dependency: ^1.0.0 -'''); - - final List output = - await runCapturingPrint(runner, ['remove-dev-dependencies']); - - expect( - output, - containsAllInOrder([ - contains('Removed dev_dependencies'), - ]), - ); - expect(package.pubspecFile.readAsStringSync(), - isNot(contains('some_dependency:'))); - expect(package.pubspecFile.readAsStringSync(), - isNot(contains('another_dependency:'))); - }); - - test('removes from examples', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - final RepositoryPackage example = package.getExamples().first; - addToPubspec(example, ''' -dev_dependencies: - some_dependency: ^2.1.8 - another_dependency: ^1.0.0 -'''); - - final List output = - await runCapturingPrint(runner, ['remove-dev-dependencies']); - - expect( - output, - containsAllInOrder([ - contains('Removed dev_dependencies'), - ]), - ); - expect(package.pubspecFile.readAsStringSync(), - isNot(contains('some_dependency:'))); - expect(package.pubspecFile.readAsStringSync(), - isNot(contains('another_dependency:'))); - }); -} diff --git a/script/tool/test/test_command_test.dart b/script/tool/test/test_command_test.dart deleted file mode 100644 index 14a1e4a67c1f..000000000000 --- a/script/tool/test/test_command_test.dart +++ /dev/null @@ -1,268 +0,0 @@ -// 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 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; -import 'package:flutter_plugin_tools/src/test_command.dart'; -import 'package:platform/platform.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - group('$TestCommand', () { - late FileSystem fileSystem; - late Platform mockPlatform; - late Directory packagesDir; - late CommandRunner runner; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final TestCommand command = TestCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner('test_test', 'Test for $TestCommand'); - runner.addCommand(command); - }); - - test('runs flutter test on each plugin', () async { - final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir, - extraFiles: ['test/empty_test.dart']); - final RepositoryPackage plugin2 = createFakePlugin('plugin2', packagesDir, - extraFiles: ['test/empty_test.dart']); - - await runCapturingPrint(runner, ['test']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['test', '--color'], plugin1.path), - ProcessCall(getFlutterCommand(mockPlatform), - const ['test', '--color'], plugin2.path), - ]), - ); - }); - - test('runs flutter test on Flutter package example tests', () async { - final RepositoryPackage plugin = createFakePlugin('a_plugin', packagesDir, - extraFiles: [ - 'test/empty_test.dart', - 'example/test/an_example_test.dart' - ]); - - await runCapturingPrint(runner, ['test']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['test', '--color'], plugin.path), - ProcessCall(getFlutterCommand(mockPlatform), - const ['test', '--color'], getExampleDir(plugin).path), - ]), - ); - }); - - test('fails when Flutter tests fail', () async { - createFakePlugin('plugin1', packagesDir, - extraFiles: ['test/empty_test.dart']); - createFakePlugin('plugin2', packagesDir, - extraFiles: ['test/empty_test.dart']); - - processRunner - .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] = - [ - MockProcess(exitCode: 1), // plugin 1 test - MockProcess(), // plugin 2 test - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['test'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains(' plugin1'), - ])); - }); - - test('skips testing plugins without test directory', () async { - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = createFakePlugin('plugin2', packagesDir, - extraFiles: ['test/empty_test.dart']); - - await runCapturingPrint(runner, ['test']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['test', '--color'], plugin2.path), - ]), - ); - }); - - test('runs dart run test on non-Flutter packages', () async { - final RepositoryPackage plugin = createFakePlugin('a', packagesDir, - extraFiles: ['test/empty_test.dart']); - final RepositoryPackage package = createFakePackage('b', packagesDir, - extraFiles: ['test/empty_test.dart']); - - await runCapturingPrint( - runner, ['test', '--enable-experiment=exp1']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const ['test', '--color', '--enable-experiment=exp1'], - plugin.path), - ProcessCall('dart', const ['pub', 'get'], package.path), - ProcessCall( - 'dart', - const ['run', '--enable-experiment=exp1', 'test'], - package.path), - ]), - ); - }); - - test('runs dart run test on non-Flutter package examples', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, extraFiles: [ - 'test/empty_test.dart', - 'example/test/an_example_test.dart' - ]); - - await runCapturingPrint(runner, ['test']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall('dart', const ['pub', 'get'], package.path), - ProcessCall('dart', const ['run', 'test'], package.path), - ProcessCall('dart', const ['pub', 'get'], - getExampleDir(package).path), - ProcessCall('dart', const ['run', 'test'], - getExampleDir(package).path), - ]), - ); - }); - - test('fails when getting non-Flutter package dependencies fails', () async { - createFakePackage('a_package', packagesDir, - extraFiles: ['test/empty_test.dart']); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(exitCode: 1), // dart pub get - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['test'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to fetch dependencies'), - contains('The following packages had errors:'), - contains(' a_package'), - ])); - }); - - test('fails when non-Flutter tests fail', () async { - createFakePackage('a_package', packagesDir, - extraFiles: ['test/empty_test.dart']); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(), // dart pub get - MockProcess(exitCode: 1), // dart pub run test - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['test'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains(' a_package'), - ])); - }); - - test('runs on Chrome for web plugins', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: ['test/empty_test.dart'], - platformSupport: { - platformWeb: const PlatformDetails(PlatformSupport.inline), - }, - ); - - await runCapturingPrint(runner, ['test']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const ['test', '--color', '--platform=chrome'], - plugin.path), - ]), - ); - }); - - test('enable-experiment flag', () async { - final RepositoryPackage plugin = createFakePlugin('a', packagesDir, - extraFiles: ['test/empty_test.dart']); - final RepositoryPackage package = createFakePackage('b', packagesDir, - extraFiles: ['test/empty_test.dart']); - - await runCapturingPrint( - runner, ['test', '--enable-experiment=exp1']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const ['test', '--color', '--enable-experiment=exp1'], - plugin.path), - ProcessCall('dart', const ['pub', 'get'], package.path), - ProcessCall( - 'dart', - const ['run', '--enable-experiment=exp1', 'test'], - package.path), - ]), - ); - }); - }); -} diff --git a/script/tool/test/update_excerpts_command_test.dart b/script/tool/test/update_excerpts_command_test.dart deleted file mode 100644 index 5a2f0f340414..000000000000 --- a/script/tool/test/update_excerpts_command_test.dart +++ /dev/null @@ -1,301 +0,0 @@ -// 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 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/update_excerpts_command.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -import 'common/package_command_test.mocks.dart'; -import 'mocks.dart'; -import 'util.dart'; - -void main() { - late FileSystem fileSystem; - late Directory packagesDir; - late RecordingProcessRunner processRunner; - late CommandRunner runner; - - setUp(() { - fileSystem = MemoryFileSystem(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - final MockGitDir gitDir = MockGitDir(); - when(gitDir.path).thenReturn(packagesDir.parent.path); - processRunner = RecordingProcessRunner(); - final UpdateExcerptsCommand command = UpdateExcerptsCommand( - packagesDir, - processRunner: processRunner, - platform: MockPlatform(), - gitDir: gitDir, - ); - - runner = CommandRunner( - 'update_excerpts_command', 'Test for update_excerpts_command'); - runner.addCommand(command); - }); - - test('runs pub get before running scripts', () async { - final RepositoryPackage package = createFakePlugin('a_package', packagesDir, - extraFiles: [kReadmeExcerptConfigPath]); - final Directory example = getExampleDir(package); - - await runCapturingPrint(runner, ['update-excerpts']); - - expect( - processRunner.recordedCalls, - containsAll([ - ProcessCall('dart', const ['pub', 'get'], example.path), - ProcessCall( - 'dart', - const [ - 'run', - 'build_runner', - 'build', - '--config', - 'excerpt', - '--output', - 'excerpts', - '--delete-conflicting-outputs', - ], - example.path), - ])); - }); - - test('runs when config is present', () async { - final RepositoryPackage package = createFakePlugin('a_package', packagesDir, - extraFiles: [kReadmeExcerptConfigPath]); - final Directory example = getExampleDir(package); - - final List output = - await runCapturingPrint(runner, ['update-excerpts']); - - expect( - processRunner.recordedCalls, - containsAll([ - ProcessCall( - 'dart', - const [ - 'run', - 'build_runner', - 'build', - '--config', - 'excerpt', - '--output', - 'excerpts', - '--delete-conflicting-outputs', - ], - example.path), - ProcessCall( - 'dart', - const [ - 'run', - 'code_excerpt_updater', - '--write-in-place', - '--yaml', - '--no-escape-ng-interpolation', - '../README.md', - ], - example.path), - ])); - - expect( - output, - containsAllInOrder([ - contains('Ran for 1 package(s)'), - ])); - }); - - test('skips when no config is present', () async { - createFakePlugin('a_package', packagesDir); - - final List output = - await runCapturingPrint(runner, ['update-excerpts']); - - expect(processRunner.recordedCalls, isEmpty); - - expect( - output, - containsAllInOrder([ - contains('Skipped 1 package(s)'), - ])); - }); - - test('restores pubspec even if running the script fails', () async { - final RepositoryPackage package = createFakePlugin('a_package', packagesDir, - extraFiles: [kReadmeExcerptConfigPath]); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(exitCode: 1), // dart pub get - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['update-excerpts'], errorHandler: (Error e) { - commandError = e; - }); - - // Check that it's definitely a failure in a step between making the changes - // and restoring the original. - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains('a_package:\n' - ' Unable to get script dependencies') - ])); - - final String examplePubspecContent = - package.getExamples().first.pubspecFile.readAsStringSync(); - expect(examplePubspecContent, isNot(contains('code_excerpter'))); - expect(examplePubspecContent, isNot(contains('code_excerpt_updater'))); - }); - - test('fails if pub get fails', () async { - createFakePlugin('a_package', packagesDir, - extraFiles: [kReadmeExcerptConfigPath]); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(exitCode: 1), // dart pub get - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['update-excerpts'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains('a_package:\n' - ' Unable to get script dependencies') - ])); - }); - - test('fails if extraction fails', () async { - createFakePlugin('a_package', packagesDir, - extraFiles: [kReadmeExcerptConfigPath]); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(), // dart pub get - MockProcess(exitCode: 1), // dart run build_runner ... - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['update-excerpts'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains('a_package:\n' - ' Unable to extract excerpts') - ])); - }); - - test('fails if injection fails', () async { - createFakePlugin('a_package', packagesDir, - extraFiles: [kReadmeExcerptConfigPath]); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(), // dart pub get - MockProcess(), // dart run build_runner ... - MockProcess(exitCode: 1), // dart run code_excerpt_updater ... - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['update-excerpts'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains('a_package:\n' - ' Unable to inject excerpts') - ])); - }); - - test('fails if READMEs are changed with --fail-on-change', () async { - createFakePlugin('a_plugin', packagesDir, - extraFiles: [kReadmeExcerptConfigPath]); - - const String changedFilePath = 'packages/a_plugin/README.md'; - processRunner.mockProcessesForExecutable['git'] = [ - MockProcess(stdout: changedFilePath), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['update-excerpts', '--fail-on-change'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('README.md is out of sync with its source excerpts'), - contains('Snippets are out of sync in the following files: ' - 'packages/a_plugin/README.md'), - ])); - }); - - test('passes if unrelated files are changed with --fail-on-change', () async { - createFakePlugin('a_plugin', packagesDir, - extraFiles: [kReadmeExcerptConfigPath]); - - const String changedFilePath = 'packages/a_plugin/linux/CMakeLists.txt'; - processRunner.mockProcessesForExecutable['git'] = [ - MockProcess(stdout: changedFilePath), - ]; - - final List output = await runCapturingPrint( - runner, ['update-excerpts', '--fail-on-change']); - - expect( - output, - containsAllInOrder([ - contains('Ran for 1 package(s)'), - ])); - }); - - test('fails if git ls-files fails', () async { - createFakePlugin('a_plugin', packagesDir, - extraFiles: [kReadmeExcerptConfigPath]); - - processRunner.mockProcessesForExecutable['git'] = [ - MockProcess(exitCode: 1) - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['update-excerpts', '--fail-on-change'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to determine local file state'), - ])); - }); -} diff --git a/script/tool/test/update_release_info_command_test.dart b/script/tool/test/update_release_info_command_test.dart deleted file mode 100644 index cfec93823ff0..000000000000 --- a/script/tool/test/update_release_info_command_test.dart +++ /dev/null @@ -1,674 +0,0 @@ -// 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 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/update_release_info_command.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -import 'common/package_command_test.mocks.dart'; -import 'mocks.dart'; -import 'util.dart'; - -void main() { - late FileSystem fileSystem; - late Directory packagesDir; - late MockGitDir gitDir; - late RecordingProcessRunner processRunner; - late CommandRunner runner; - - setUp(() { - fileSystem = MemoryFileSystem(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - - gitDir = MockGitDir(); - when(gitDir.path).thenReturn(packagesDir.parent.path); - when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) - .thenAnswer((Invocation invocation) { - final List arguments = - invocation.positionalArguments[0]! as List; - // Route git calls through a process runner, to make mock output - // consistent with other processes. Attach the first argument to the - // command to make targeting the mock results easier. - final String gitCommand = arguments.removeAt(0); - return processRunner.run('git-$gitCommand', arguments); - }); - - final UpdateReleaseInfoCommand command = UpdateReleaseInfoCommand( - packagesDir, - gitDir: gitDir, - ); - runner = CommandRunner( - 'update_release_info_command', 'Test for update_release_info_command'); - runner.addCommand(command); - }); - - group('flags', () { - test('fails if --changelog is missing', () async { - Exception? commandError; - await runCapturingPrint(runner, [ - 'update-release-info', - '--version=next', - ], exceptionHandler: (Exception e) { - commandError = e; - }); - - expect(commandError, isA()); - }); - - test('fails if --changelog is blank', () async { - Exception? commandError; - await runCapturingPrint(runner, [ - 'update-release-info', - '--version=next', - '--changelog', - '', - ], exceptionHandler: (Exception e) { - commandError = e; - }); - - expect(commandError, isA()); - }); - - test('fails if --version is missing', () async { - Exception? commandError; - await runCapturingPrint( - runner, ['update-release-info', '--changelog', ''], - exceptionHandler: (Exception e) { - commandError = e; - }); - - expect(commandError, isA()); - }); - - test('fails if --version is an unknown value', () async { - Exception? commandError; - await runCapturingPrint(runner, [ - 'update-release-info', - '--version=foo', - '--changelog', - '', - ], exceptionHandler: (Exception e) { - commandError = e; - }); - - expect(commandError, isA()); - }); - }); - - group('changelog', () { - test('adds new NEXT section', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - const String originalChangelog = ''' -## 1.0.0 - -* Previous changes. -'''; - package.changelogFile.writeAsStringSync(originalChangelog); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=next', - '--changelog', - 'A change.' - ]); - - final String newChangelog = package.changelogFile.readAsStringSync(); - const String expectedChangeLog = ''' -## NEXT - -* A change. - -$originalChangelog'''; - - expect( - output, - containsAllInOrder([ - contains(' Added a NEXT section.'), - ]), - ); - expect(newChangelog, expectedChangeLog); - }); - - test('adds to existing NEXT section', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - const String originalChangelog = ''' -## NEXT - -* Already-pending changes. - -## 1.0.0 - -* Old changes. -'''; - package.changelogFile.writeAsStringSync(originalChangelog); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=next', - '--changelog', - 'A change.' - ]); - - final String newChangelog = package.changelogFile.readAsStringSync(); - const String expectedChangeLog = ''' -## NEXT - -* A change. -* Already-pending changes. - -## 1.0.0 - -* Old changes. -'''; - - expect(output, - containsAllInOrder([contains(' Updated NEXT section.')])); - expect(newChangelog, expectedChangeLog); - }); - - test('adds new version section', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - const String originalChangelog = ''' -## 1.0.0 - -* Previous changes. -'''; - package.changelogFile.writeAsStringSync(originalChangelog); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=bugfix', - '--changelog', - 'A change.' - ]); - - final String newChangelog = package.changelogFile.readAsStringSync(); - const String expectedChangeLog = ''' -## 1.0.1 - -* A change. - -$originalChangelog'''; - - expect( - output, - containsAllInOrder([ - contains(' Added a 1.0.1 section.'), - ]), - ); - expect(newChangelog, expectedChangeLog); - }); - - test('converts existing NEXT section to version section', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - const String originalChangelog = ''' -## NEXT - -* Already-pending changes. - -## 1.0.0 - -* Old changes. -'''; - package.changelogFile.writeAsStringSync(originalChangelog); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=bugfix', - '--changelog', - 'A change.' - ]); - - final String newChangelog = package.changelogFile.readAsStringSync(); - const String expectedChangeLog = ''' -## 1.0.1 - -* A change. -* Already-pending changes. - -## 1.0.0 - -* Old changes. -'''; - - expect(output, - containsAllInOrder([contains(' Updated NEXT section.')])); - expect(newChangelog, expectedChangeLog); - }); - - test('treats multiple lines as multiple list items', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - const String originalChangelog = ''' -## 1.0.0 - -* Previous changes. -'''; - package.changelogFile.writeAsStringSync(originalChangelog); - - await runCapturingPrint(runner, [ - 'update-release-info', - '--version=bugfix', - '--changelog', - 'First change.\nSecond change.' - ]); - - final String newChangelog = package.changelogFile.readAsStringSync(); - const String expectedChangeLog = ''' -## 1.0.1 - -* First change. -* Second change. - -$originalChangelog'''; - - expect(newChangelog, expectedChangeLog); - }); - - test('adds a period to any lines missing it, and removes whitespace', - () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - const String originalChangelog = ''' -## 1.0.0 - -* Previous changes. -'''; - package.changelogFile.writeAsStringSync(originalChangelog); - - await runCapturingPrint(runner, [ - 'update-release-info', - '--version=bugfix', - '--changelog', - 'First change \nSecond change' - ]); - - final String newChangelog = package.changelogFile.readAsStringSync(); - const String expectedChangeLog = ''' -## 1.0.1 - -* First change. -* Second change. - -$originalChangelog'''; - - expect(newChangelog, expectedChangeLog); - }); - - test('handles non-standard changelog format', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - const String originalChangelog = ''' -# 1.0.0 - -* A version with the wrong heading format. -'''; - package.changelogFile.writeAsStringSync(originalChangelog); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=next', - '--changelog', - 'A change.' - ]); - - final String newChangelog = package.changelogFile.readAsStringSync(); - const String expectedChangeLog = ''' -## NEXT - -* A change. - -$originalChangelog'''; - - expect(output, - containsAllInOrder([contains(' Added a NEXT section.')])); - expect(newChangelog, expectedChangeLog); - }); - - test('adds to existing NEXT section using - list style', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - const String originalChangelog = ''' -## NEXT - - - Already-pending changes. - -## 1.0.0 - - - Previous changes. -'''; - package.changelogFile.writeAsStringSync(originalChangelog); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=next', - '--changelog', - 'A change.' - ]); - - final String newChangelog = package.changelogFile.readAsStringSync(); - const String expectedChangeLog = ''' -## NEXT - - - A change. - - Already-pending changes. - -## 1.0.0 - - - Previous changes. -'''; - - expect(output, - containsAllInOrder([contains(' Updated NEXT section.')])); - expect(newChangelog, expectedChangeLog); - }); - - test('skips for "minimal" when there are no changes at all', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.1'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/different_package/lib/foo.dart -'''), - ]; - final String originalChangelog = package.changelogFile.readAsStringSync(); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=minimal', - '--changelog', - 'A change.', - ]); - - final String version = package.parsePubspec().version?.toString() ?? ''; - expect(version, '1.0.1'); - expect(package.changelogFile.readAsStringSync(), originalChangelog); - expect( - output, - containsAllInOrder([ - contains('No changes to package'), - contains('Skipped 1 package') - ])); - }); - - test('skips for "minimal" when there are only test changes', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.1'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/a_package/test/a_test.dart -packages/a_package/example/integration_test/another_test.dart -'''), - ]; - final String originalChangelog = package.changelogFile.readAsStringSync(); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=minimal', - '--changelog', - 'A change.', - ]); - - final String version = package.parsePubspec().version?.toString() ?? ''; - expect(version, '1.0.1'); - expect(package.changelogFile.readAsStringSync(), originalChangelog); - expect( - output, - containsAllInOrder([ - contains('No non-exempt changes to package'), - contains('Skipped 1 package') - ])); - }); - - test('fails if CHANGELOG.md is missing', () async { - createFakePackage('a_package', packagesDir, includeCommonFiles: false); - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=minor', - '--changelog', - 'A change.', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect(output, - containsAllInOrder([contains(' Missing CHANGELOG.md.')])); - }); - - test('fails if CHANGELOG.md has unexpected NEXT block format', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - const String originalChangelog = ''' -## NEXT - -Some free-form text that isn't a list. - -## 1.0.0 - -- Previous changes. -'''; - package.changelogFile.writeAsStringSync(originalChangelog); - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=minor', - '--changelog', - 'A change.', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains(' Existing NEXT section has unrecognized format.') - ])); - }); - }); - - group('pubspec', () { - test('does not change for --next', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - await runCapturingPrint(runner, [ - 'update-release-info', - '--version=next', - '--changelog', - 'A change.' - ]); - - final String version = package.parsePubspec().version?.toString() ?? ''; - expect(version, '1.0.0'); - }); - - test('updates bugfix version for pre-1.0 without existing build number', - () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '0.1.0'); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=bugfix', - '--changelog', - 'A change.', - ]); - - final String version = package.parsePubspec().version?.toString() ?? ''; - expect(version, '0.1.0+1'); - expect( - output, - containsAllInOrder( - [contains(' Incremented version to 0.1.0+1')])); - }); - - test('updates bugfix version for pre-1.0 with existing build number', - () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '0.1.0+2'); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=bugfix', - '--changelog', - 'A change.', - ]); - - final String version = package.parsePubspec().version?.toString() ?? ''; - expect(version, '0.1.0+3'); - expect( - output, - containsAllInOrder( - [contains(' Incremented version to 0.1.0+3')])); - }); - - test('updates bugfix version for post-1.0', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.1'); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=bugfix', - '--changelog', - 'A change.', - ]); - - final String version = package.parsePubspec().version?.toString() ?? ''; - expect(version, '1.0.2'); - expect( - output, - containsAllInOrder( - [contains(' Incremented version to 1.0.2')])); - }); - - test('updates minor version for pre-1.0', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '0.1.0+2'); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=minor', - '--changelog', - 'A change.', - ]); - - final String version = package.parsePubspec().version?.toString() ?? ''; - expect(version, '0.1.1'); - expect( - output, - containsAllInOrder( - [contains(' Incremented version to 0.1.1')])); - }); - - test('updates minor version for post-1.0', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.1'); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=minor', - '--changelog', - 'A change.', - ]); - - final String version = package.parsePubspec().version?.toString() ?? ''; - expect(version, '1.1.0'); - expect( - output, - containsAllInOrder( - [contains(' Incremented version to 1.1.0')])); - }); - - test('updates bugfix version for "minimal" with publish-worthy changes', - () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.1'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/a_package/lib/plugin.dart -'''), - ]; - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=minimal', - '--changelog', - 'A change.', - ]); - - final String version = package.parsePubspec().version?.toString() ?? ''; - expect(version, '1.0.2'); - expect( - output, - containsAllInOrder( - [contains(' Incremented version to 1.0.2')])); - }); - - test('no version change for "minimal" with non-publish-worthy changes', - () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.1'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/a_package/test/plugin_test.dart -'''), - ]; - - await runCapturingPrint(runner, [ - 'update-release-info', - '--version=minimal', - '--changelog', - 'A change.', - ]); - - final String version = package.parsePubspec().version?.toString() ?? ''; - expect(version, '1.0.1'); - }); - - test('fails if there is no version in pubspec', () async { - createFakePackage('a_package', packagesDir, version: null); - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=minor', - '--changelog', - 'A change.', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder( - [contains('Could not determine current version.')])); - }); - }); -} diff --git a/script/tool/test/util.dart b/script/tool/test/util.dart deleted file mode 100644 index 913242b6ea69..000000000000 --- a/script/tool/test/util.dart +++ /dev/null @@ -1,471 +0,0 @@ -// 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 'dart:async'; -import 'dart:convert'; -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/file_utils.dart'; -import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; -import 'package:flutter_plugin_tools/src/common/process_runner.dart'; -import 'package:flutter_plugin_tools/src/common/repository_package.dart'; -import 'package:meta/meta.dart'; -import 'package:path/path.dart' as p; -import 'package:platform/platform.dart'; -import 'package:quiver/collection.dart'; - -import 'mocks.dart'; - -export 'package:flutter_plugin_tools/src/common/repository_package.dart'; - -/// The relative path from a package to the file that is used to enable -/// README excerpting for a package. -// This is a shared constant to ensure that both readme-check and -// update-excerpt are looking for the same file, so that readme-check can't -// get out of sync with what actually drives excerpting. -const String kReadmeExcerptConfigPath = 'example/build.excerpt.yaml'; - -const String _defaultDartConstraint = '>=2.14.0 <3.0.0'; -const String _defaultFlutterConstraint = '>=2.5.0'; - -/// Returns the exe name that command will use when running Flutter on -/// [platform]. -String getFlutterCommand(Platform platform) => - platform.isWindows ? 'flutter.bat' : 'flutter'; - -/// Creates a packages directory in the given location. -/// -/// If [parentDir] is set the packages directory will be created there, -/// otherwise [fileSystem] must be provided and it will be created an arbitrary -/// location in that filesystem. -Directory createPackagesDirectory( - {Directory? parentDir, FileSystem? fileSystem}) { - assert(parentDir != null || fileSystem != null, - 'One of parentDir or fileSystem must be provided'); - assert(fileSystem == null || fileSystem is MemoryFileSystem, - 'If using a real filesystem, parentDir must be provided'); - final Directory packagesDir = - (parentDir ?? fileSystem!.currentDirectory).childDirectory('packages'); - packagesDir.createSync(); - return packagesDir; -} - -/// Details for platform support in a plugin. -@immutable -class PlatformDetails { - const PlatformDetails( - this.type, { - this.hasNativeCode = true, - this.hasDartCode = false, - }); - - /// The type of support for the platform. - final PlatformSupport type; - - /// Whether or not the plugin includes native code. - /// - /// Ignored for web, which does not have native code. - final bool hasNativeCode; - - /// Whether or not the plugin includes Dart code. - /// - /// Ignored for web, which always has native code. - final bool hasDartCode; -} - -/// Returns the 'example' directory for [package]. -/// -/// This is deliberately not a method on [RepositoryPackage] since actual tool -/// code should essentially never need this, and instead be using -/// [RepositoryPackage.getExamples] to avoid assuming there's a single example -/// directory. However, needing to construct paths with the example directory -/// is very common in test code. -/// -/// This returns a Directory rather than a RepositoryPackage because there is no -/// guarantee that the returned directory is a package. -Directory getExampleDir(RepositoryPackage package) { - return package.directory.childDirectory('example'); -} - -/// Creates a plugin package with the given [name] in [packagesDirectory]. -/// -/// [platformSupport] is a map of platform string to the support details for -/// that platform. -/// -/// [extraFiles] is an optional list of plugin-relative paths, using Posix -/// separators, of extra files to create in the plugin. -RepositoryPackage createFakePlugin( - String name, - Directory parentDirectory, { - List examples = const ['example'], - List extraFiles = const [], - Map platformSupport = - const {}, - String? version = '0.0.1', - String flutterConstraint = _defaultFlutterConstraint, - String dartConstraint = _defaultDartConstraint, -}) { - final RepositoryPackage package = createFakePackage( - name, - parentDirectory, - isFlutter: true, - examples: examples, - extraFiles: extraFiles, - version: version, - flutterConstraint: flutterConstraint, - dartConstraint: dartConstraint, - ); - - createFakePubspec( - package, - name: name, - isPlugin: true, - platformSupport: platformSupport, - version: version, - flutterConstraint: flutterConstraint, - dartConstraint: dartConstraint, - ); - - return package; -} - -/// Creates a plugin package with the given [name] in [packagesDirectory]. -/// -/// [extraFiles] is an optional list of package-relative paths, using unix-style -/// separators, of extra files to create in the package. -/// -/// If [includeCommonFiles] is true, common but non-critical files like -/// CHANGELOG.md, README.md, and AUTHORS will be included. -/// -/// If non-null, [directoryName] will be used for the directory instead of -/// [name]. -RepositoryPackage createFakePackage( - String name, - Directory parentDirectory, { - List examples = const ['example'], - List extraFiles = const [], - bool isFlutter = false, - String? version = '0.0.1', - String flutterConstraint = _defaultFlutterConstraint, - String dartConstraint = _defaultDartConstraint, - bool includeCommonFiles = true, - String? directoryName, - String? publishTo, -}) { - final RepositoryPackage package = - RepositoryPackage(parentDirectory.childDirectory(directoryName ?? name)); - package.directory.createSync(recursive: true); - - package.libDirectory.createSync(); - createFakePubspec(package, - name: name, - isFlutter: isFlutter, - version: version, - flutterConstraint: flutterConstraint, - dartConstraint: dartConstraint); - if (includeCommonFiles) { - package.changelogFile.writeAsStringSync(''' -## $version - * Some changes. - '''); - package.readmeFile.writeAsStringSync('A very useful package'); - package.authorsFile.writeAsStringSync('Google Inc.'); - } - - if (examples.length == 1) { - createFakePackage('${name}_example', package.directory, - directoryName: examples.first, - examples: [], - includeCommonFiles: false, - isFlutter: isFlutter, - publishTo: 'none', - flutterConstraint: flutterConstraint, - dartConstraint: dartConstraint); - } else if (examples.isNotEmpty) { - final Directory examplesDirectory = getExampleDir(package)..createSync(); - for (final String exampleName in examples) { - createFakePackage(exampleName, examplesDirectory, - examples: [], - includeCommonFiles: false, - isFlutter: isFlutter, - publishTo: 'none', - flutterConstraint: flutterConstraint, - dartConstraint: dartConstraint); - } - } - - final p.Context posixContext = p.posix; - for (final String file in extraFiles) { - childFileWithSubcomponents(package.directory, posixContext.split(file)) - .createSync(recursive: true); - } - - return package; -} - -/// Creates a `pubspec.yaml` file for [package]. -/// -/// [platformSupport] is a map of platform string to the support details for -/// that platform. If empty, no `plugin` entry will be created unless `isPlugin` -/// is set to `true`. -void createFakePubspec( - RepositoryPackage package, { - String name = 'fake_package', - bool isFlutter = true, - bool isPlugin = false, - Map platformSupport = - const {}, - String? publishTo, - String? version, - String dartConstraint = _defaultDartConstraint, - String flutterConstraint = _defaultFlutterConstraint, -}) { - isPlugin |= platformSupport.isNotEmpty; - - String environmentSection = ''' -environment: - sdk: "$dartConstraint" -'''; - String dependenciesSection = ''' -dependencies: -'''; - String pluginSection = ''; - - // Add Flutter-specific entries if requested. - if (isFlutter) { - environmentSection += ''' - flutter: "$flutterConstraint" -'''; - dependenciesSection += ''' - flutter: - sdk: flutter -'''; - - if (isPlugin) { - pluginSection += ''' -flutter: - plugin: - platforms: -'''; - for (final MapEntry platform - in platformSupport.entries) { - pluginSection += - _pluginPlatformSection(platform.key, platform.value, name); - } - } - } - - // Default to a fake server to avoid ever accidentally publishing something - // from a test. Does not use 'none' since that changes the behavior of some - // commands. - final String publishToSection = - 'publish_to: ${publishTo ?? 'http://no_pub_server.com'}'; - - final String yaml = ''' -name: $name -${(version != null) ? 'version: $version' : ''} -$publishToSection - -$environmentSection - -$dependenciesSection - -$pluginSection -'''; - - package.pubspecFile.createSync(); - package.pubspecFile.writeAsStringSync(yaml); -} - -String _pluginPlatformSection( - String platform, PlatformDetails support, String packageName) { - String entry = ''; - // Build the main plugin entry. - if (support.type == PlatformSupport.federated) { - entry = ''' - $platform: - default_package: ${packageName}_$platform -'''; - } else { - final List lines = [ - ' $platform:', - ]; - switch (platform) { - case platformAndroid: - lines.add(' package: io.flutter.plugins.fake'); - continue nativeByDefault; - nativeByDefault: - case platformIOS: - case platformLinux: - case platformMacOS: - case platformWindows: - if (support.hasNativeCode) { - final String className = - platform == platformIOS ? 'FLTFakePlugin' : 'FakePlugin'; - lines.add(' pluginClass: $className'); - } - if (support.hasDartCode) { - lines.add(' dartPluginClass: FakeDartPlugin'); - } - break; - case platformWeb: - lines.addAll([ - ' pluginClass: FakePlugin', - ' fileName: ${packageName}_web.dart', - ]); - break; - default: - assert(false, 'Unrecognized platform: $platform'); - break; - } - entry = '${lines.join('\n')}\n'; - } - - return entry; -} - -/// Run the command [runner] with the given [args] and return -/// what was printed. -/// A custom [errorHandler] can be used to handle the runner error as desired without throwing. -Future> runCapturingPrint( - CommandRunner runner, - List args, { - Function(Error error)? errorHandler, - Function(Exception error)? exceptionHandler, -}) async { - final List prints = []; - final ZoneSpecification spec = ZoneSpecification( - print: (_, __, ___, String message) { - prints.add(message); - }, - ); - try { - await Zone.current - .fork(specification: spec) - .run>(() => runner.run(args)); - } on Error catch (e) { - if (errorHandler == null) { - rethrow; - } - errorHandler(e); - } on Exception catch (e) { - if (exceptionHandler == null) { - rethrow; - } - exceptionHandler(e); - } - - return prints; -} - -/// A mock [ProcessRunner] which records process calls. -class RecordingProcessRunner extends ProcessRunner { - final List recordedCalls = []; - - /// Maps an executable to a list of processes that should be used for each - /// successive call to it via [run], [runAndStream], or [start]. - final Map> mockProcessesForExecutable = - >{}; - - @override - Future runAndStream( - String executable, - List args, { - Directory? workingDir, - bool exitOnError = false, - }) async { - recordedCalls.add(ProcessCall(executable, args, workingDir?.path)); - final io.Process? processToReturn = _getProcessToReturn(executable); - final int exitCode = - processToReturn == null ? 0 : await processToReturn.exitCode; - if (exitOnError && (exitCode != 0)) { - throw io.ProcessException(executable, args); - } - return Future.value(exitCode); - } - - /// Returns [io.ProcessResult] created from [mockProcessesForExecutable]. - @override - Future run( - String executable, - List args, { - Directory? workingDir, - bool exitOnError = false, - bool logOnError = false, - Encoding stdoutEncoding = io.systemEncoding, - Encoding stderrEncoding = io.systemEncoding, - }) async { - recordedCalls.add(ProcessCall(executable, args, workingDir?.path)); - - final io.Process? process = _getProcessToReturn(executable); - final List? processStdout = - await process?.stdout.transform(stdoutEncoding.decoder).toList(); - final String stdout = processStdout?.join() ?? ''; - final List? processStderr = - await process?.stderr.transform(stderrEncoding.decoder).toList(); - final String stderr = processStderr?.join() ?? ''; - - final io.ProcessResult result = process == null - ? io.ProcessResult(1, 0, '', '') - : io.ProcessResult(process.pid, await process.exitCode, stdout, stderr); - - if (exitOnError && (result.exitCode != 0)) { - throw io.ProcessException(executable, args); - } - - return Future.value(result); - } - - @override - Future start(String executable, List args, - {Directory? workingDirectory}) async { - recordedCalls.add(ProcessCall(executable, args, workingDirectory?.path)); - return Future.value( - _getProcessToReturn(executable) ?? MockProcess()); - } - - io.Process? _getProcessToReturn(String executable) { - final List? processes = mockProcessesForExecutable[executable]; - if (processes != null && processes.isNotEmpty) { - return processes.removeAt(0); - } - return null; - } -} - -/// A recorded process call. -@immutable -class ProcessCall { - const ProcessCall(this.executable, this.args, this.workingDir); - - /// The executable that was called. - final String executable; - - /// The arguments passed to [executable] in the call. - final List args; - - /// The working directory this process was called from. - final String? workingDir; - - @override - bool operator ==(Object other) { - return other is ProcessCall && - executable == other.executable && - listsEqual(args, other.args) && - workingDir == other.workingDir; - } - - @override - int get hashCode => Object.hash(executable, args, workingDir); - - @override - String toString() { - final List command = [executable, ...args]; - return '"${command.join(' ')}" in $workingDir'; - } -} diff --git a/script/tool/test/version_check_command_test.dart b/script/tool/test/version_check_command_test.dart deleted file mode 100644 index d485d81ceaf2..000000000000 --- a/script/tool/test/version_check_command_test.dart +++ /dev/null @@ -1,1468 +0,0 @@ -// 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 'dart:convert'; -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/version_check_command.dart'; -import 'package:http/http.dart' as http; -import 'package:http/testing.dart'; -import 'package:mockito/mockito.dart'; -import 'package:pub_semver/pub_semver.dart'; -import 'package:test/test.dart'; - -import 'common/package_command_test.mocks.dart'; -import 'mocks.dart'; -import 'util.dart'; - -void testAllowedVersion( - String mainVersion, - String headVersion, { - bool allowed = true, - NextVersionType? nextVersionType, -}) { - final Version main = Version.parse(mainVersion); - final Version head = Version.parse(headVersion); - final Map allowedVersions = - getAllowedNextVersions(main, newVersion: head); - if (allowed) { - expect(allowedVersions, contains(head)); - if (nextVersionType != null) { - expect(allowedVersions[head], equals(nextVersionType)); - } - } else { - expect(allowedVersions, isNot(contains(head))); - } -} - -class MockProcessResult extends Mock implements io.ProcessResult {} - -void main() { - const String indentation = ' '; - group('VersionCheckCommand', () { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late CommandRunner runner; - late RecordingProcessRunner processRunner; - late MockGitDir gitDir; - // Ignored if mockHttpResponse is set. - int mockHttpStatus; - Map? mockHttpResponse; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - - gitDir = MockGitDir(); - when(gitDir.path).thenReturn(packagesDir.parent.path); - when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) - .thenAnswer((Invocation invocation) { - final List arguments = - invocation.positionalArguments[0]! as List; - // Route git calls through the process runner, to make mock output - // consistent with other processes. Attach the first argument to the - // command to make targeting the mock results easier. - final String gitCommand = arguments.removeAt(0); - return processRunner.run('git-$gitCommand', arguments); - }); - - // Default to simulating the plugin never having been published. - mockHttpStatus = 404; - mockHttpResponse = null; - final MockClient mockClient = MockClient((http.Request request) async { - return http.Response(json.encode(mockHttpResponse), - mockHttpResponse == null ? mockHttpStatus : 200); - }); - - processRunner = RecordingProcessRunner(); - final VersionCheckCommand command = VersionCheckCommand(packagesDir, - processRunner: processRunner, - platform: mockPlatform, - gitDir: gitDir, - httpClient: mockClient); - - runner = CommandRunner( - 'version_check_command', 'Test for $VersionCheckCommand'); - runner.addCommand(command); - }); - - test('allows valid version', () async { - createFakePlugin('plugin', packagesDir, version: '2.0.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('1.0.0 -> 2.0.0'), - ]), - ); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall( - 'git-show', ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - - test('denies invalid version', () async { - createFakePlugin('plugin', packagesDir, version: '0.2.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 0.0.1'), - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Incorrectly updated version.'), - ])); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall( - 'git-show', ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - - test('uses merge-base without explicit base-sha', () async { - createFakePlugin('plugin', packagesDir, version: '2.0.0'); - processRunner.mockProcessesForExecutable['git-merge-base'] = [ - MockProcess(stdout: 'abc123'), - MockProcess(stdout: 'abc123'), - ]; - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - final List output = - await runCapturingPrint(runner, ['version-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('1.0.0 -> 2.0.0'), - ]), - ); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall('git-merge-base', - ['--fork-point', 'FETCH_HEAD', 'HEAD'], null), - ProcessCall('git-show', - ['abc123:packages/plugin/pubspec.yaml'], null), - ])); - }); - - test('allows valid version for new package.', () async { - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - final List output = - await runCapturingPrint(runner, ['version-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Unable to find previous version at git base.'), - ]), - ); - }); - - test('allows likely reverts.', () async { - createFakePlugin('plugin', packagesDir, version: '0.6.1'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 0.6.2'), - ]; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - - expect( - output, - containsAllInOrder([ - contains('New version is lower than previous version. ' - 'This is assumed to be a revert.'), - ]), - ); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall( - 'git-show', ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - - test('denies lower version that could not be a simple revert', () async { - createFakePlugin('plugin', packagesDir, version: '0.5.1'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 0.6.2'), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Incorrectly updated version.'), - ])); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall( - 'git-show', ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - - test('allows minor changes to platform interfaces', () async { - createFakePlugin('plugin_platform_interface', packagesDir, - version: '1.1.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('1.0.0 -> 1.1.0'), - ]), - ); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall( - 'git-show', - [ - 'main:packages/plugin_platform_interface/pubspec.yaml' - ], - null) - ])); - }); - - test('disallows breaking changes to platform interfaces by default', - () async { - createFakePlugin('plugin_platform_interface', packagesDir, - version: '2.0.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - ' Breaking changes to platform interfaces are not allowed ' - 'without explicit justification.\n' - ' See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages ' - 'for more information.'), - ])); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall( - 'git-show', - [ - 'main:packages/plugin_platform_interface/pubspec.yaml' - ], - null) - ])); - }); - - test('allows breaking changes to platform interfaces with override label', - () async { - createFakePlugin('plugin_platform_interface', packagesDir, - version: '2.0.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - - final List output = await runCapturingPrint(runner, [ - 'version-check', - '--base-sha=main', - '--pr-labels=some label,override: allow breaking change,another-label' - ]); - - expect( - output, - containsAllInOrder([ - contains('Allowing breaking change to plugin_platform_interface ' - 'due to the "override: allow breaking change" label.'), - contains('Ran for 1 package(s) (1 with warnings)'), - ]), - ); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall( - 'git-show', - [ - 'main:packages/plugin_platform_interface/pubspec.yaml' - ], - null) - ])); - }); - - test('allows breaking changes to platform interfaces with bypass flag', - () async { - createFakePlugin('plugin_platform_interface', packagesDir, - version: '2.0.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - final List output = await runCapturingPrint(runner, [ - 'version-check', - '--base-sha=main', - '--ignore-platform-interface-breaks' - ]); - - expect( - output, - containsAllInOrder([ - contains('Allowing breaking change to plugin_platform_interface due ' - 'to --ignore-platform-interface-breaks'), - contains('Ran for 1 package(s) (1 with warnings)'), - ]), - ); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall( - 'git-show', - [ - 'main:packages/plugin_platform_interface/pubspec.yaml' - ], - null) - ])); - }); - - test('Allow empty lines in front of the first version in CHANGELOG', - () async { - const String version = '1.0.1'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: version); - const String changelog = ''' - -## $version -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - ]), - ); - }); - - test('Throws if versions in changelog and pubspec do not match', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.1'); - const String changelog = ''' -## 1.0.2 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - Error? commandError; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main', '--against-pub'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Versions in CHANGELOG.md and pubspec.yaml do not match.'), - ]), - ); - }); - - test('Success if CHANGELOG and pubspec versions match', () async { - const String version = '1.0.1'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: version); - - const String changelog = ''' -## $version -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - ]), - ); - }); - - test( - 'Fail if pubspec version only matches an older version listed in CHANGELOG', - () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.1 -* Some changes. -## 1.0.0 -* Some other changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - bool hasError = false; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main', '--against-pub'], - errorHandler: (Error e) { - expect(e, isA()); - hasError = true; - }); - expect(hasError, isTrue); - - expect( - output, - containsAllInOrder([ - contains('Versions in CHANGELOG.md and pubspec.yaml do not match.'), - ]), - ); - }); - - test('Allow NEXT as a placeholder for gathering CHANGELOG entries', - () async { - const String version = '1.0.0'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: version); - - const String changelog = ''' -## NEXT -* Some changes that won't be published until the next time there's a release. -## $version -* Some other changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Found NEXT; validating next version in the CHANGELOG.'), - ]), - ); - }); - - test('Fail if NEXT appears after a version', () async { - const String version = '1.0.1'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: version); - - const String changelog = ''' -## $version -* Some changes. -## NEXT -* Some changes that should have been folded in 1.0.1. -## 1.0.0 -* Some other changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - bool hasError = false; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main', '--against-pub'], - errorHandler: (Error e) { - expect(e, isA()); - hasError = true; - }); - expect(hasError, isTrue); - - expect( - output, - containsAllInOrder([ - contains('When bumping the version for release, the NEXT section ' - "should be incorporated into the new version's release notes.") - ]), - ); - }); - - test('Fail if NEXT is left in the CHANGELOG when adding a version bump', - () async { - const String version = '1.0.1'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: version); - - const String changelog = ''' -## NEXT -* Some changes that should have been folded in 1.0.1. -## $version -* Some changes. -## 1.0.0 -* Some other changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - - bool hasError = false; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main', '--against-pub'], - errorHandler: (Error e) { - expect(e, isA()); - hasError = true; - }); - expect(hasError, isTrue); - - expect( - output, - containsAllInOrder([ - contains('When bumping the version for release, the NEXT section ' - "should be incorporated into the new version's release notes."), - contains('plugin:\n' - ' CHANGELOG.md failed validation.'), - ]), - ); - }); - - test('fails if the version increases without replacing NEXT', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.1'); - - const String changelog = ''' -## NEXT -* Some changes that should be listed as part of 1.0.1. -## 1.0.0 -* Some other changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - - bool hasError = false; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main', '--against-pub'], - errorHandler: (Error e) { - expect(e, isA()); - hasError = true; - }); - expect(hasError, isTrue); - - expect( - output, - containsAllInOrder([ - contains('When bumping the version for release, the NEXT section ' - "should be incorporated into the new version's release notes.") - ]), - ); - }); - - test('allows NEXT for a revert', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## NEXT -* Some changes that should be listed as part of 1.0.1. -## 1.0.0 -* Some other changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.1'), - ]; - - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - expect( - output, - containsAllInOrder([ - contains('New version is lower than previous version. ' - 'This is assumed to be a revert.'), - ]), - ); - }); - - test( - 'fails gracefully if the version headers are not found due to using the wrong style', - () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## NEXT -* Some changes for a later release. -# 1.0.0 -* Some other changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'version-check', - '--base-sha=main', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to find a version in CHANGELOG.md'), - contains('The current version should be on a line starting with ' - '"## ", either on the first non-empty line or after a "## NEXT" ' - 'section.'), - ]), - ); - }); - - test('fails gracefully if the version is unparseable', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## Alpha -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'version-check', - '--base-sha=main', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('"Alpha" could not be parsed as a version.'), - ]), - ); - }); - - group('missing change detection', () { - Future> runWithMissingChangeDetection(List extraArgs, - {void Function(Error error)? errorHandler}) async { - return runCapturingPrint( - runner, - [ - 'version-check', - '--base-sha=main', - '--check-for-missing-changes', - ...extraArgs, - ], - errorHandler: errorHandler); - } - - test('passes for unchanged packages', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''), - ]; - - final List output = - await runWithMissingChangeDetection([]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - ]), - ); - }); - - test( - 'fails if a version change is missing from a change that does not ' - 'pass the exemption check', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin/lib/plugin.dart -'''), - ]; - - Error? commandError; - final List output = await runWithMissingChangeDetection( - [], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No version change found'), - contains('plugin:\n' - ' Missing version change'), - ]), - ); - }); - - test('passes version change requirement when version changes', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.1'); - - const String changelog = ''' -## 1.0.1 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin/lib/plugin.dart -packages/plugin/CHANGELOG.md -packages/plugin/pubspec.yaml -'''), - ]; - - final List output = - await runWithMissingChangeDetection([]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - ]), - ); - }); - - test('version change check ignores files outside the package', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin_a/lib/plugin.dart -tool/plugin/lib/plugin.dart -'''), - ]; - - final List output = - await runWithMissingChangeDetection([]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - ]), - ); - }); - - test('allows missing version change for exempt changes', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin/example/android/lint-baseline.xml -packages/plugin/example/android/src/androidTest/foo/bar/FooTest.java -packages/plugin/example/ios/RunnerTests/Foo.m -packages/plugin/example/ios/RunnerUITests/info.plist -packages/plugin/CHANGELOG.md -'''), - ]; - - final List output = - await runWithMissingChangeDetection([]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - ]), - ); - }); - - test('allows missing version change with override label', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin/lib/plugin.dart -packages/plugin/CHANGELOG.md -packages/plugin/pubspec.yaml -'''), - ]; - - final List output = - await runWithMissingChangeDetection([ - '--pr-labels=some label,override: no versioning needed,another-label' - ]); - - expect( - output, - containsAllInOrder([ - contains('Ignoring lack of version change due to the ' - '"override: no versioning needed" label.'), - ]), - ); - }); - - test('fails if a CHANGELOG change is missing', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin/example/lib/foo.dart -'''), - ]; - - Error? commandError; - final List output = await runWithMissingChangeDetection( - [], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No CHANGELOG change found'), - contains('plugin:\n' - ' Missing CHANGELOG change'), - ]), - ); - }); - - test('passes CHANGELOG check when the CHANGELOG is changed', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin/example/lib/foo.dart -packages/plugin/CHANGELOG.md -'''), - ]; - - final List output = - await runWithMissingChangeDetection([]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - ]), - ); - }); - - test('fails CHANGELOG check if only another package CHANGELOG chages', - () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin/example/lib/foo.dart -packages/another_plugin/CHANGELOG.md -'''), - ]; - - Error? commandError; - final List output = await runWithMissingChangeDetection( - [], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No CHANGELOG change found'), - ]), - ); - }); - - test('allows missing CHANGELOG change with justification', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin/example/lib/foo.dart -'''), - ]; - - final List output = - await runWithMissingChangeDetection([ - '--pr-labels=some label,override: no changelog needed,another-label' - ]); - - expect( - output, - containsAllInOrder([ - contains('Ignoring lack of CHANGELOG update due to the ' - '"override: no changelog needed" label.'), - ]), - ); - }); - - // This test ensures that Dependabot Gradle changes to test-only files - // aren't flagged by the version check. - test( - 'allows missing CHANGELOG and version change for test-only Gradle changes', - () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - // File list. - MockProcess(stdout: ''' -packages/plugin/android/build.gradle -'''), - // build.gradle diff - MockProcess(stdout: ''' -- androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' -- testImplementation 'junit:junit:4.10.0' -+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' -+ testImplementation 'junit:junit:4.13.2' -'''), - ]; - - final List output = - await runWithMissingChangeDetection([]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - ]), - ); - }); - - test('allows missing CHANGELOG and version change for dev-only changes', - () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - // File list. - MockProcess(stdout: ''' -packages/plugin/tool/run_tests.dart -packages/plugin/run_tests.sh -'''), - ]; - - final List output = - await runWithMissingChangeDetection([]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - ]), - ); - }); - }); - - test('allows valid against pub', () async { - mockHttpResponse = { - 'name': 'some_package', - 'versions': [ - '0.0.1', - '0.0.2', - '1.0.0', - ], - }; - - createFakePlugin('plugin', packagesDir, version: '2.0.0'); - final List output = await runCapturingPrint(runner, - ['version-check', '--base-sha=main', '--against-pub']); - - expect( - output, - containsAllInOrder([ - contains('plugin: Current largest version on pub: 1.0.0'), - ]), - ); - }); - - test('denies invalid against pub', () async { - mockHttpResponse = { - 'name': 'some_package', - 'versions': [ - '0.0.1', - '0.0.2', - ], - }; - - createFakePlugin('plugin', packagesDir, version: '2.0.0'); - - bool hasError = false; - final List result = await runCapturingPrint( - runner, ['version-check', '--base-sha=main', '--against-pub'], - errorHandler: (Error e) { - expect(e, isA()); - hasError = true; - }); - expect(hasError, isTrue); - - expect( - result, - containsAllInOrder([ - contains(''' -${indentation}Incorrectly updated version. -${indentation}HEAD: 2.0.0, pub: 0.0.2. -${indentation}Allowed versions: {1.0.0: NextVersionType.BREAKING_MAJOR, 0.1.0: NextVersionType.MINOR, 0.0.3: NextVersionType.PATCH}''') - ]), - ); - }); - - test( - 'throw and print error message if http request failed when checking against pub', - () async { - mockHttpStatus = 400; - - createFakePlugin('plugin', packagesDir, version: '2.0.0'); - bool hasError = false; - final List result = await runCapturingPrint( - runner, ['version-check', '--base-sha=main', '--against-pub'], - errorHandler: (Error e) { - expect(e, isA()); - hasError = true; - }); - expect(hasError, isTrue); - - expect( - result, - containsAllInOrder([ - contains(''' -${indentation}Error fetching version on pub for plugin. -${indentation}HTTP Status 400 -${indentation}HTTP response: null -''') - ]), - ); - }); - - test('when checking against pub, allow any version if http status is 404.', - () async { - mockHttpStatus = 404; - - createFakePlugin('plugin', packagesDir, version: '2.0.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - final List result = await runCapturingPrint(runner, - ['version-check', '--base-sha=main', '--against-pub']); - - expect( - result, - containsAllInOrder([ - contains('Unable to find previous version on pub server.'), - ]), - ); - }); - - group('prelease versions', () { - test( - 'allow an otherwise-valid transition that also adds a pre-release component', - () async { - createFakePlugin('plugin', packagesDir, version: '2.0.0-dev'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('1.0.0 -> 2.0.0-dev'), - ]), - ); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall('git-show', - ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - - test('allow releasing a pre-release', () async { - createFakePlugin('plugin', packagesDir, version: '1.2.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.2.0-dev'), - ]; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('1.2.0-dev -> 1.2.0'), - ]), - ); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall('git-show', - ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - - // Allow abandoning a pre-release version in favor of a different version - // change type. - test( - 'allow an otherwise-valid transition that also removes a pre-release component', - () async { - createFakePlugin('plugin', packagesDir, version: '2.0.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.2.0-dev'), - ]; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('1.2.0-dev -> 2.0.0'), - ]), - ); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall('git-show', - ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - - test('allow changing only the pre-release version', () async { - createFakePlugin('plugin', packagesDir, version: '1.2.0-dev.2'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.2.0-dev.1'), - ]; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('1.2.0-dev.1 -> 1.2.0-dev.2'), - ]), - ); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall('git-show', - ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - - test('denies invalid version change that also adds a pre-release', - () async { - createFakePlugin('plugin', packagesDir, version: '0.2.0-dev'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 0.0.1'), - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Incorrectly updated version.'), - ])); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall('git-show', - ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - - test('denies invalid version change that also removes a pre-release', - () async { - createFakePlugin('plugin', packagesDir, version: '0.2.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 0.0.1-dev'), - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Incorrectly updated version.'), - ])); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall('git-show', - ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - - test('denies invalid version change between pre-releases', () async { - createFakePlugin('plugin', packagesDir, version: '0.2.0-dev'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 0.0.1-dev'), - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Incorrectly updated version.'), - ])); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall('git-show', - ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - }); - }); - - group('Pre 1.0', () { - test('nextVersion allows patch version', () { - testAllowedVersion('0.12.0', '0.12.0+1', - nextVersionType: NextVersionType.PATCH); - testAllowedVersion('0.12.0+4', '0.12.0+5', - nextVersionType: NextVersionType.PATCH); - }); - - test('nextVersion does not allow jumping patch', () { - testAllowedVersion('0.12.0', '0.12.0+2', allowed: false); - testAllowedVersion('0.12.0+2', '0.12.0+4', allowed: false); - }); - - test('nextVersion does not allow going back', () { - testAllowedVersion('0.12.0', '0.11.0', allowed: false); - testAllowedVersion('0.12.0+2', '0.12.0+1', allowed: false); - testAllowedVersion('0.12.0+1', '0.12.0', allowed: false); - }); - - test('nextVersion allows minor version', () { - testAllowedVersion('0.12.0', '0.12.1', - nextVersionType: NextVersionType.MINOR); - testAllowedVersion('0.12.0+4', '0.12.1', - nextVersionType: NextVersionType.MINOR); - }); - - test('nextVersion does not allow jumping minor', () { - testAllowedVersion('0.12.0', '0.12.2', allowed: false); - testAllowedVersion('0.12.0+2', '0.12.3', allowed: false); - }); - }); - - group('Releasing 1.0', () { - test('nextVersion allows releasing 1.0', () { - testAllowedVersion('0.12.0', '1.0.0', - nextVersionType: NextVersionType.BREAKING_MAJOR); - testAllowedVersion('0.12.0+4', '1.0.0', - nextVersionType: NextVersionType.BREAKING_MAJOR); - }); - - test('nextVersion does not allow jumping major', () { - testAllowedVersion('0.12.0', '2.0.0', allowed: false); - testAllowedVersion('0.12.0+4', '2.0.0', allowed: false); - }); - - test('nextVersion does not allow un-releasing', () { - testAllowedVersion('1.0.0', '0.12.0+4', allowed: false); - testAllowedVersion('1.0.0', '0.12.0', allowed: false); - }); - }); - - group('Post 1.0', () { - test('nextVersion allows patch jumps', () { - testAllowedVersion('1.0.1', '1.0.2', - nextVersionType: NextVersionType.PATCH); - testAllowedVersion('1.0.0', '1.0.1', - nextVersionType: NextVersionType.PATCH); - }); - - test('nextVersion does not allow build jumps', () { - testAllowedVersion('1.0.1', '1.0.1+1', allowed: false); - testAllowedVersion('1.0.0+5', '1.0.0+6', allowed: false); - }); - - test('nextVersion does not allow skipping patches', () { - testAllowedVersion('1.0.1', '1.0.3', allowed: false); - testAllowedVersion('1.0.0', '1.0.6', allowed: false); - }); - - test('nextVersion allows minor version jumps', () { - testAllowedVersion('1.0.1', '1.1.0', - nextVersionType: NextVersionType.MINOR); - testAllowedVersion('1.0.0', '1.1.0', - nextVersionType: NextVersionType.MINOR); - }); - - test('nextVersion does not allow skipping minor versions', () { - testAllowedVersion('1.0.1', '1.2.0', allowed: false); - testAllowedVersion('1.1.0', '1.3.0', allowed: false); - }); - - test('nextVersion allows breaking changes', () { - testAllowedVersion('1.0.1', '2.0.0', - nextVersionType: NextVersionType.BREAKING_MAJOR); - testAllowedVersion('1.0.0', '2.0.0', - nextVersionType: NextVersionType.BREAKING_MAJOR); - }); - - test('nextVersion does not allow skipping major versions', () { - testAllowedVersion('1.0.1', '3.0.0', allowed: false); - testAllowedVersion('1.1.0', '2.3.0', allowed: false); - }); - }); -} diff --git a/script/tool/test/xcode_analyze_command_test.dart b/script/tool/test/xcode_analyze_command_test.dart deleted file mode 100644 index 418c695f295c..000000000000 --- a/script/tool/test/xcode_analyze_command_test.dart +++ /dev/null @@ -1,484 +0,0 @@ -// 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 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; -import 'package:flutter_plugin_tools/src/xcode_analyze_command.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -// TODO(stuartmorgan): Rework these tests to use a mock Xcode instead of -// doing all the process mocking and validation. -void main() { - group('test xcode_analyze_command', () { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late CommandRunner runner; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(isMacOS: true); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final XcodeAnalyzeCommand command = XcodeAnalyzeCommand(packagesDir, - processRunner: processRunner, platform: mockPlatform); - - runner = CommandRunner( - 'xcode_analyze_command', 'Test for xcode_analyze_command'); - runner.addCommand(command); - }); - - test('Fails if no platforms are provided', () async { - Error? commandError; - final List output = await runCapturingPrint( - runner, ['xcode-analyze'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('At least one platform flag must be provided'), - ]), - ); - }); - - group('iOS', () { - test('skip if iOS is not supported', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final List output = - await runCapturingPrint(runner, ['xcode-analyze', '--ios']); - expect(output, - contains(contains('Not implemented for target platform(s).'))); - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('skip if iOS is implemented in a federated package', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.federated) - }); - - final List output = - await runCapturingPrint(runner, ['xcode-analyze', '--ios']); - expect(output, - contains(contains('Not implemented for target platform(s).'))); - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('runs for iOS plugin', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline) - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, [ - 'xcode-analyze', - '--ios', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('plugin/example (iOS) passed analysis.') - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'analyze', - '-workspace', - 'ios/Runner.xcworkspace', - '-scheme', - 'Runner', - '-configuration', - 'Debug', - '-destination', - 'generic/platform=iOS Simulator', - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - pluginExampleDirectory.path), - ])); - }); - - test('passes min iOS deployment version when requested', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline) - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, - ['xcode-analyze', '--ios', '--ios-min-version=14.0']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('plugin/example (iOS) passed analysis.') - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'analyze', - '-workspace', - 'ios/Runner.xcworkspace', - '-scheme', - 'Runner', - '-configuration', - 'Debug', - '-destination', - 'generic/platform=iOS Simulator', - 'IPHONEOS_DEPLOYMENT_TARGET=14.0', - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - pluginExampleDirectory.path), - ])); - }); - - test('fails if xcrun fails', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline) - }); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(exitCode: 1) - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, - [ - 'xcode-analyze', - '--ios', - ], - errorHandler: (Error e) { - commandError = e; - }, - ); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains(' plugin'), - ])); - }); - }); - - group('macOS', () { - test('skip if macOS is not supported', () async { - createFakePlugin( - 'plugin', - packagesDir, - ); - - final List output = await runCapturingPrint( - runner, ['xcode-analyze', '--macos']); - expect(output, - contains(contains('Not implemented for target platform(s).'))); - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('skip if macOS is implemented in a federated package', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.federated), - }); - - final List output = await runCapturingPrint( - runner, ['xcode-analyze', '--macos']); - expect(output, - contains(contains('Not implemented for target platform(s).'))); - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('runs for macOS plugin', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, [ - 'xcode-analyze', - '--macos', - ]); - - expect(output, - contains(contains('plugin/example (macOS) passed analysis.'))); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'analyze', - '-workspace', - 'macos/Runner.xcworkspace', - '-scheme', - 'Runner', - '-configuration', - 'Debug', - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - pluginExampleDirectory.path), - ])); - }); - - test('passes min macOS deployment version when requested', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, - ['xcode-analyze', '--macos', '--macos-min-version=12.0']); - - expect(output, - contains(contains('plugin/example (macOS) passed analysis.'))); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'analyze', - '-workspace', - 'macos/Runner.xcworkspace', - '-scheme', - 'Runner', - '-configuration', - 'Debug', - 'MACOSX_DEPLOYMENT_TARGET=12.0', - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - pluginExampleDirectory.path), - ])); - }); - - test('fails if xcrun fails', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(exitCode: 1) - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['xcode-analyze', '--macos'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains(' plugin'), - ]), - ); - }); - }); - - group('combined', () { - test('runs both iOS and macOS when supported', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline), - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, [ - 'xcode-analyze', - '--ios', - '--macos', - ]); - - expect( - output, - containsAll([ - contains('plugin/example (iOS) passed analysis.'), - contains('plugin/example (macOS) passed analysis.'), - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'analyze', - '-workspace', - 'ios/Runner.xcworkspace', - '-scheme', - 'Runner', - '-configuration', - 'Debug', - '-destination', - 'generic/platform=iOS Simulator', - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - pluginExampleDirectory.path), - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'analyze', - '-workspace', - 'macos/Runner.xcworkspace', - '-scheme', - 'Runner', - '-configuration', - 'Debug', - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - pluginExampleDirectory.path), - ])); - }); - - test('runs only macOS for a macOS plugin', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, [ - 'xcode-analyze', - '--ios', - '--macos', - ]); - - expect( - output, - containsAllInOrder([ - contains('plugin/example (macOS) passed analysis.'), - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'analyze', - '-workspace', - 'macos/Runner.xcworkspace', - '-scheme', - 'Runner', - '-configuration', - 'Debug', - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - pluginExampleDirectory.path), - ])); - }); - - test('runs only iOS for a iOS plugin', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline) - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, [ - 'xcode-analyze', - '--ios', - '--macos', - ]); - - expect( - output, - containsAllInOrder( - [contains('plugin/example (iOS) passed analysis.')])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'analyze', - '-workspace', - 'ios/Runner.xcworkspace', - '-scheme', - 'Runner', - '-configuration', - 'Debug', - '-destination', - 'generic/platform=iOS Simulator', - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - pluginExampleDirectory.path), - ])); - }); - - test('skips when neither are supported', () async { - createFakePlugin('plugin', packagesDir); - - final List output = await runCapturingPrint(runner, [ - 'xcode-analyze', - '--ios', - '--macos', - ]); - - expect( - output, - containsAllInOrder([ - contains('SKIPPING: Not implemented for target platform(s).'), - ])); - - expect(processRunner.recordedCalls, orderedEquals([])); - }); - }); - }); -} diff --git a/script/tool_runner.sh b/script/tool_runner.sh index 221071550cc1..ba7bec6579d1 100755 --- a/script/tool_runner.sh +++ b/script/tool_runner.sh @@ -6,18 +6,20 @@ set -e # WARNING! Do not remove this script, or change its behavior, unless you have -# verified that it will not break the flutter/flutter analysis run of this -# repository: https://github.com/flutter/flutter/blob/master/dev/bots/test.dart +# verified that it will not break the dart-lang analysis run of this +# repository: https://github.com/dart-lang/sdk/blob/main/tools/bots/flutter/analyze_flutter_plugins.sh readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" readonly REPO_DIR="$(dirname "$SCRIPT_DIR")" -readonly TOOL_PATH="$REPO_DIR/script/tool/bin/flutter_plugin_tools.dart" -# Ensure that the tool dependencies have been fetched. -(pushd "$REPO_DIR/script/tool" && dart pub get && popd) >/dev/null # The tool expects to be run from the repo root. -cd "$REPO_DIR" -# Run from the in-tree source. # PACKAGE_SHARDING is (optionally) set from Cirrus. See .cirrus.yml -dart run "$TOOL_PATH" "$@" --packages-for-branch --log-timing $PACKAGE_SHARDING +cd "$REPO_DIR" +# Ensure that the tooling has been activated. +.ci/scripts/prepare_tool.sh + +dart pub global run flutter_plugin_tools "$@" \ + --packages-for-branch \ + --log-timing \ + $PACKAGE_SHARDING