mirror of
https://github.com/flutter/packages.git
synced 2025-05-30 05:02:41 +08:00
[flutter_plugin_tools] Add a command to lint Android code (#4206)
Adds a new `lint-android` command to run `gradlew lint` on Android plugins. Also standardizes the names of the Cirrus tasks that run all the build and platform-specific (i.e., not Dart unit test) tests for each platform, as they were getting unnecessarily long and complex in some cases. Fixes https://github.com/flutter/flutter/issues/87071
This commit is contained in:
@ -1,6 +1,7 @@
|
|||||||
## NEXT
|
## NEXT
|
||||||
|
|
||||||
- Added Android native integration test support to `native-test`.
|
- Added Android native integration test support to `native-test`.
|
||||||
|
- Added a new `android-lint` command to lint Android plugin native code.
|
||||||
|
|
||||||
## 0.5.0
|
## 0.5.0
|
||||||
|
|
||||||
|
57
script/tool/lib/src/common/gradle.dart
Normal file
57
script/tool/lib/src/common/gradle.dart
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// 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';
|
||||||
|
|
||||||
|
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].
|
||||||
|
///
|
||||||
|
/// If [log] is true, commands run by this instance will long various status
|
||||||
|
/// messages.
|
||||||
|
GradleProject(
|
||||||
|
this.flutterProject, {
|
||||||
|
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 project's 'android' directory.
|
||||||
|
Directory get androidDirectory => flutterProject.childDirectory('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<int> runCommand(
|
||||||
|
String target, {
|
||||||
|
List<String> arguments = const <String>[],
|
||||||
|
}) {
|
||||||
|
return processRunner.runAndStream(
|
||||||
|
gradleWrapper.path,
|
||||||
|
<String>[target, ...arguments],
|
||||||
|
workingDir: androidDirectory,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,7 @@ const String _xcRunCommand = 'xcrun';
|
|||||||
|
|
||||||
/// A utility class for interacting with the installed version of Xcode.
|
/// A utility class for interacting with the installed version of Xcode.
|
||||||
class Xcode {
|
class Xcode {
|
||||||
/// Creates an instance that runs commends with the given [processRunner].
|
/// Creates an instance that runs commands with the given [processRunner].
|
||||||
///
|
///
|
||||||
/// If [log] is true, commands run by this instance will long various status
|
/// If [log] is true, commands run by this instance will long various status
|
||||||
/// messages.
|
/// messages.
|
||||||
|
@ -10,6 +10,7 @@ import 'package:platform/platform.dart';
|
|||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
import 'common/core.dart';
|
import 'common/core.dart';
|
||||||
|
import 'common/gradle.dart';
|
||||||
import 'common/package_looping_command.dart';
|
import 'common/package_looping_command.dart';
|
||||||
import 'common/process_runner.dart';
|
import 'common/process_runner.dart';
|
||||||
|
|
||||||
@ -74,8 +75,6 @@ class FirebaseTestLabCommand extends PackageLoopingCommand {
|
|||||||
'Runs tests in test_instrumentation folder using the '
|
'Runs tests in test_instrumentation folder using the '
|
||||||
'instrumentation_test package.';
|
'instrumentation_test package.';
|
||||||
|
|
||||||
static const String _gradleWrapper = 'gradlew';
|
|
||||||
|
|
||||||
bool _firebaseProjectConfigured = false;
|
bool _firebaseProjectConfigured = false;
|
||||||
|
|
||||||
Future<void> _configureFirebaseProject() async {
|
Future<void> _configureFirebaseProject() async {
|
||||||
@ -138,13 +137,15 @@ class FirebaseTestLabCommand extends PackageLoopingCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensures that gradle wrapper exists
|
// Ensures that gradle wrapper exists
|
||||||
if (!await _ensureGradleWrapperExists(androidDirectory)) {
|
final GradleProject project = GradleProject(exampleDirectory,
|
||||||
|
processRunner: processRunner, platform: platform);
|
||||||
|
if (!await _ensureGradleWrapperExists(project)) {
|
||||||
return PackageResult.fail(<String>['Unable to build example apk']);
|
return PackageResult.fail(<String>['Unable to build example apk']);
|
||||||
}
|
}
|
||||||
|
|
||||||
await _configureFirebaseProject();
|
await _configureFirebaseProject();
|
||||||
|
|
||||||
if (!await _runGradle(androidDirectory, 'app:assembleAndroidTest')) {
|
if (!await _runGradle(project, 'app:assembleAndroidTest')) {
|
||||||
return PackageResult.fail(<String>['Unable to assemble androidTest']);
|
return PackageResult.fail(<String>['Unable to assemble androidTest']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,8 +157,7 @@ class FirebaseTestLabCommand extends PackageLoopingCommand {
|
|||||||
for (final File test in _findIntegrationTestFiles(package)) {
|
for (final File test in _findIntegrationTestFiles(package)) {
|
||||||
final String testName = getRelativePosixPath(test, from: package);
|
final String testName = getRelativePosixPath(test, from: package);
|
||||||
print('Testing $testName...');
|
print('Testing $testName...');
|
||||||
if (!await _runGradle(androidDirectory, 'app:assembleDebug',
|
if (!await _runGradle(project, 'app:assembleDebug', testFile: test)) {
|
||||||
testFile: test)) {
|
|
||||||
printError('Could not build $testName');
|
printError('Could not build $testName');
|
||||||
errors.add('$testName failed to build');
|
errors.add('$testName failed to build');
|
||||||
continue;
|
continue;
|
||||||
@ -204,12 +204,12 @@ class FirebaseTestLabCommand extends PackageLoopingCommand {
|
|||||||
: PackageResult.fail(errors);
|
: PackageResult.fail(errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks that 'gradlew' exists in [androidDirectory], and if not runs a
|
/// Checks that Gradle has been configured for [project], and if not runs a
|
||||||
/// Flutter build to generate it.
|
/// Flutter build to generate it.
|
||||||
///
|
///
|
||||||
/// Returns true if either gradlew was already present, or the build succeeds.
|
/// Returns true if either gradlew was already present, or the build succeeds.
|
||||||
Future<bool> _ensureGradleWrapperExists(Directory androidDirectory) async {
|
Future<bool> _ensureGradleWrapperExists(GradleProject project) async {
|
||||||
if (!androidDirectory.childFile(_gradleWrapper).existsSync()) {
|
if (!project.isConfigured()) {
|
||||||
print('Running flutter build apk...');
|
print('Running flutter build apk...');
|
||||||
final String experiment = getStringArg(kEnableExperiment);
|
final String experiment = getStringArg(kEnableExperiment);
|
||||||
final int exitCode = await processRunner.runAndStream(
|
final int exitCode = await processRunner.runAndStream(
|
||||||
@ -219,7 +219,7 @@ class FirebaseTestLabCommand extends PackageLoopingCommand {
|
|||||||
'apk',
|
'apk',
|
||||||
if (experiment.isNotEmpty) '--enable-experiment=$experiment',
|
if (experiment.isNotEmpty) '--enable-experiment=$experiment',
|
||||||
],
|
],
|
||||||
workingDir: androidDirectory);
|
workingDir: project.androidDirectory);
|
||||||
|
|
||||||
if (exitCode != 0) {
|
if (exitCode != 0) {
|
||||||
return false;
|
return false;
|
||||||
@ -228,15 +228,15 @@ class FirebaseTestLabCommand extends PackageLoopingCommand {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds [target] using 'gradlew' in the given [directory]. Assumes
|
/// Builds [target] using Gradle in the given [project]. Assumes Gradle is
|
||||||
/// 'gradlew' already exists.
|
/// already configured.
|
||||||
///
|
///
|
||||||
/// [testFile] optionally does the Flutter build with the given test file as
|
/// [testFile] optionally does the Flutter build with the given test file as
|
||||||
/// the build target.
|
/// the build target.
|
||||||
///
|
///
|
||||||
/// Returns true if the command succeeds.
|
/// Returns true if the command succeeds.
|
||||||
Future<bool> _runGradle(
|
Future<bool> _runGradle(
|
||||||
Directory directory,
|
GradleProject project,
|
||||||
String target, {
|
String target, {
|
||||||
File? testFile,
|
File? testFile,
|
||||||
}) async {
|
}) async {
|
||||||
@ -245,17 +245,15 @@ class FirebaseTestLabCommand extends PackageLoopingCommand {
|
|||||||
? Uri.encodeComponent('--enable-experiment=$experiment')
|
? Uri.encodeComponent('--enable-experiment=$experiment')
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
final int exitCode = await processRunner.runAndStream(
|
final int exitCode = await project.runCommand(
|
||||||
directory.childFile(_gradleWrapper).path,
|
|
||||||
<String>[
|
|
||||||
target,
|
target,
|
||||||
|
arguments: <String>[
|
||||||
'-Pverbose=true',
|
'-Pverbose=true',
|
||||||
if (testFile != null) '-Ptarget=${testFile.path}',
|
if (testFile != null) '-Ptarget=${testFile.path}',
|
||||||
if (extraOptions != null) '-Pextra-front-end-options=$extraOptions',
|
if (extraOptions != null) '-Pextra-front-end-options=$extraOptions',
|
||||||
if (extraOptions != null)
|
if (extraOptions != null) '-Pextra-gen-snapshot-options=$extraOptions',
|
||||||
'-Pextra-gen-snapshot-options=$extraOptions',
|
|
||||||
],
|
],
|
||||||
workingDir: directory);
|
);
|
||||||
|
|
||||||
if (exitCode != 0) {
|
if (exitCode != 0) {
|
||||||
return false;
|
return false;
|
||||||
|
61
script/tool/lib/src/lint_android_command.dart
Normal file
61
script/tool/lib/src/lint_android_command.dart
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// 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:flutter_plugin_tools/src/common/plugin_utils.dart';
|
||||||
|
import 'package:platform/platform.dart';
|
||||||
|
|
||||||
|
import 'common/core.dart';
|
||||||
|
import 'common/gradle.dart';
|
||||||
|
import 'common/package_looping_command.dart';
|
||||||
|
import 'common/process_runner.dart';
|
||||||
|
|
||||||
|
/// Lint the CocoaPod podspecs and run unit tests.
|
||||||
|
///
|
||||||
|
/// See https://guides.cocoapods.org/terminal/commands.html#pod_lib_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 example to have been build at least once before running.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PackageResult> runForPackage(Directory package) async {
|
||||||
|
if (!pluginSupportsPlatform(kPlatformAndroid, package,
|
||||||
|
requiredMode: PlatformSupport.inline)) {
|
||||||
|
return PackageResult.skip(
|
||||||
|
'Plugin does not have an Android implemenatation.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final Directory exampleDirectory = package.childDirectory('example');
|
||||||
|
final GradleProject project = GradleProject(exampleDirectory,
|
||||||
|
processRunner: processRunner, platform: platform);
|
||||||
|
|
||||||
|
if (!project.isConfigured()) {
|
||||||
|
return PackageResult.fail(<String>['Build example before linting']);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String packageName = package.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');
|
||||||
|
|
||||||
|
return exitCode == 0 ? PackageResult.success() : PackageResult.fail();
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,7 @@ import 'drive_examples_command.dart';
|
|||||||
import 'firebase_test_lab_command.dart';
|
import 'firebase_test_lab_command.dart';
|
||||||
import 'format_command.dart';
|
import 'format_command.dart';
|
||||||
import 'license_check_command.dart';
|
import 'license_check_command.dart';
|
||||||
|
import 'lint_android_command.dart';
|
||||||
import 'lint_podspecs_command.dart';
|
import 'lint_podspecs_command.dart';
|
||||||
import 'list_command.dart';
|
import 'list_command.dart';
|
||||||
import 'native_test_command.dart';
|
import 'native_test_command.dart';
|
||||||
@ -51,6 +52,7 @@ void main(List<String> args) {
|
|||||||
..addCommand(FirebaseTestLabCommand(packagesDir))
|
..addCommand(FirebaseTestLabCommand(packagesDir))
|
||||||
..addCommand(FormatCommand(packagesDir))
|
..addCommand(FormatCommand(packagesDir))
|
||||||
..addCommand(LicenseCheckCommand(packagesDir))
|
..addCommand(LicenseCheckCommand(packagesDir))
|
||||||
|
..addCommand(LintAndroidCommand(packagesDir))
|
||||||
..addCommand(LintPodspecsCommand(packagesDir))
|
..addCommand(LintPodspecsCommand(packagesDir))
|
||||||
..addCommand(ListCommand(packagesDir))
|
..addCommand(ListCommand(packagesDir))
|
||||||
..addCommand(NativeTestCommand(packagesDir))
|
..addCommand(NativeTestCommand(packagesDir))
|
||||||
|
@ -6,6 +6,7 @@ import 'package:file/file.dart';
|
|||||||
import 'package:platform/platform.dart';
|
import 'package:platform/platform.dart';
|
||||||
|
|
||||||
import 'common/core.dart';
|
import 'common/core.dart';
|
||||||
|
import 'common/gradle.dart';
|
||||||
import 'common/package_looping_command.dart';
|
import 'common/package_looping_command.dart';
|
||||||
import 'common/plugin_utils.dart';
|
import 'common/plugin_utils.dart';
|
||||||
import 'common/process_runner.dart';
|
import 'common/process_runner.dart';
|
||||||
@ -47,8 +48,6 @@ class NativeTestCommand extends PackageLoopingCommand {
|
|||||||
help: 'Runs native integration (UI) tests', defaultsTo: true);
|
help: 'Runs native integration (UI) tests', defaultsTo: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const String _gradleWrapper = 'gradlew';
|
|
||||||
|
|
||||||
// The device destination flags for iOS tests.
|
// The device destination flags for iOS tests.
|
||||||
List<String> _iosDestinationFlags = <String>[];
|
List<String> _iosDestinationFlags = <String>[];
|
||||||
|
|
||||||
@ -243,9 +242,12 @@ this command.
|
|||||||
final String exampleName = getPackageDescription(example);
|
final String exampleName = getPackageDescription(example);
|
||||||
_printRunningExampleTestsMessage(example, 'Android');
|
_printRunningExampleTestsMessage(example, 'Android');
|
||||||
|
|
||||||
final Directory androidDirectory = example.childDirectory('android');
|
final GradleProject project = GradleProject(
|
||||||
final File gradleFile = androidDirectory.childFile(_gradleWrapper);
|
example,
|
||||||
if (!gradleFile.existsSync()) {
|
processRunner: processRunner,
|
||||||
|
platform: platform,
|
||||||
|
);
|
||||||
|
if (!project.isConfigured()) {
|
||||||
printError('ERROR: Run "flutter build apk" on $exampleName, or run '
|
printError('ERROR: Run "flutter build apk" on $exampleName, or run '
|
||||||
'this tool\'s "build-examples --apk" command, '
|
'this tool\'s "build-examples --apk" command, '
|
||||||
'before executing tests.');
|
'before executing tests.');
|
||||||
@ -256,9 +258,7 @@ this command.
|
|||||||
|
|
||||||
if (runUnitTests) {
|
if (runUnitTests) {
|
||||||
print('Running unit tests...');
|
print('Running unit tests...');
|
||||||
final int exitCode = await processRunner.runAndStream(
|
final int exitCode = await project.runCommand('testDebugUnitTest');
|
||||||
gradleFile.path, <String>['testDebugUnitTest'],
|
|
||||||
workingDir: androidDirectory);
|
|
||||||
if (exitCode != 0) {
|
if (exitCode != 0) {
|
||||||
printError('$exampleName unit tests failed.');
|
printError('$exampleName unit tests failed.');
|
||||||
failed = true;
|
failed = true;
|
||||||
@ -275,13 +275,12 @@ this command.
|
|||||||
'notAnnotation=io.flutter.plugins.DartIntegrationTest';
|
'notAnnotation=io.flutter.plugins.DartIntegrationTest';
|
||||||
|
|
||||||
print('Running integration tests...');
|
print('Running integration tests...');
|
||||||
final int exitCode = await processRunner.runAndStream(
|
final int exitCode = await project.runCommand(
|
||||||
gradleFile.path,
|
|
||||||
<String>[
|
|
||||||
'app:connectedAndroidTest',
|
'app:connectedAndroidTest',
|
||||||
|
arguments: <String>[
|
||||||
'-Pandroid.testInstrumentationRunnerArguments.$filter',
|
'-Pandroid.testInstrumentationRunnerArguments.$filter',
|
||||||
],
|
],
|
||||||
workingDir: androidDirectory);
|
);
|
||||||
if (exitCode != 0) {
|
if (exitCode != 0) {
|
||||||
printError('$exampleName integration tests failed.');
|
printError('$exampleName integration tests failed.');
|
||||||
failed = true;
|
failed = true;
|
||||||
|
179
script/tool/test/common/gradle_test.dart
Normal file
179
script/tool/test/common/gradle_test.dart
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
// 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 Directory plugin = createFakePlugin(
|
||||||
|
'plugin', fileSystem.directory('/'),
|
||||||
|
extraFiles: <String>['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 Directory plugin = createFakePlugin(
|
||||||
|
'plugin', fileSystem.directory('/'),
|
||||||
|
extraFiles: <String>['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 Directory plugin = createFakePlugin(
|
||||||
|
'plugin', fileSystem.directory('/'),
|
||||||
|
extraFiles: <String>['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 Directory plugin = createFakePlugin(
|
||||||
|
'plugin', fileSystem.directory('/'),
|
||||||
|
extraFiles: <String>['android/foo']);
|
||||||
|
final GradleProject project = GradleProject(
|
||||||
|
plugin,
|
||||||
|
processRunner: processRunner,
|
||||||
|
platform: MockPlatform(isMacOS: true),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(project.isConfigured(), false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('runXcodeBuild', () {
|
||||||
|
test('runs without arguments', () async {
|
||||||
|
final Directory plugin = createFakePlugin(
|
||||||
|
'plugin', fileSystem.directory('/'),
|
||||||
|
extraFiles: <String>['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>[
|
||||||
|
ProcessCall(
|
||||||
|
plugin.childDirectory('android').childFile('gradlew').path,
|
||||||
|
const <String>[
|
||||||
|
'foo',
|
||||||
|
],
|
||||||
|
plugin.childDirectory('android').path),
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('runs with arguments', () async {
|
||||||
|
final Directory plugin = createFakePlugin(
|
||||||
|
'plugin', fileSystem.directory('/'),
|
||||||
|
extraFiles: <String>['android/gradlew']);
|
||||||
|
final GradleProject project = GradleProject(
|
||||||
|
plugin,
|
||||||
|
processRunner: processRunner,
|
||||||
|
platform: MockPlatform(isMacOS: true),
|
||||||
|
);
|
||||||
|
|
||||||
|
final int exitCode = await project.runCommand(
|
||||||
|
'foo',
|
||||||
|
arguments: <String>['--bar', '--baz'],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(exitCode, 0);
|
||||||
|
expect(
|
||||||
|
processRunner.recordedCalls,
|
||||||
|
orderedEquals(<ProcessCall>[
|
||||||
|
ProcessCall(
|
||||||
|
plugin.childDirectory('android').childFile('gradlew').path,
|
||||||
|
const <String>[
|
||||||
|
'foo',
|
||||||
|
'--bar',
|
||||||
|
'--baz',
|
||||||
|
],
|
||||||
|
plugin.childDirectory('android').path),
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('runs with the correct wrapper on Windows', () async {
|
||||||
|
final Directory plugin = createFakePlugin(
|
||||||
|
'plugin', fileSystem.directory('/'),
|
||||||
|
extraFiles: <String>['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>[
|
||||||
|
ProcessCall(
|
||||||
|
plugin.childDirectory('android').childFile('gradlew.bat').path,
|
||||||
|
const <String>[
|
||||||
|
'foo',
|
||||||
|
],
|
||||||
|
plugin.childDirectory('android').path),
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns error codes', () async {
|
||||||
|
final Directory plugin = createFakePlugin(
|
||||||
|
'plugin', fileSystem.directory('/'),
|
||||||
|
extraFiles: <String>['android/gradlew.bat']);
|
||||||
|
final GradleProject project = GradleProject(
|
||||||
|
plugin,
|
||||||
|
processRunner: processRunner,
|
||||||
|
platform: MockPlatform(isWindows: true),
|
||||||
|
);
|
||||||
|
|
||||||
|
processRunner.mockProcessesForExecutable[project.gradleWrapper.path] =
|
||||||
|
<io.Process>[
|
||||||
|
MockProcess.failing(),
|
||||||
|
];
|
||||||
|
|
||||||
|
final int exitCode = await project.runCommand('foo');
|
||||||
|
|
||||||
|
expect(exitCode, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
158
script/tool/test/lint_android_command_test.dart
Normal file
158
script/tool/test/lint_android_command_test.dart
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
// 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<void> runner;
|
||||||
|
late MockPlatform mockPlatform;
|
||||||
|
late RecordingProcessRunner processRunner;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
fileSystem = MemoryFileSystem(style: FileSystemStyle.posix);
|
||||||
|
packagesDir = createPackagesDirectory(fileSystem: fileSystem);
|
||||||
|
mockPlatform = MockPlatform();
|
||||||
|
processRunner = RecordingProcessRunner();
|
||||||
|
final LintAndroidCommand command = LintAndroidCommand(
|
||||||
|
packagesDir,
|
||||||
|
processRunner: processRunner,
|
||||||
|
platform: mockPlatform,
|
||||||
|
);
|
||||||
|
|
||||||
|
runner = CommandRunner<void>(
|
||||||
|
'lint_android_test', 'Test for $LintAndroidCommand');
|
||||||
|
runner.addCommand(command);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('runs gradle lint', () async {
|
||||||
|
final Directory pluginDir =
|
||||||
|
createFakePlugin('plugin1', packagesDir, extraFiles: <String>[
|
||||||
|
'example/android/gradlew',
|
||||||
|
], platformSupport: <String, PlatformSupport>{
|
||||||
|
kPlatformAndroid: PlatformSupport.inline
|
||||||
|
});
|
||||||
|
|
||||||
|
final Directory androidDir =
|
||||||
|
pluginDir.childDirectory('example').childDirectory('android');
|
||||||
|
|
||||||
|
final List<String> output =
|
||||||
|
await runCapturingPrint(runner, <String>['lint-android']);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
processRunner.recordedCalls,
|
||||||
|
orderedEquals(<ProcessCall>[
|
||||||
|
ProcessCall(
|
||||||
|
androidDir.childFile('gradlew').path,
|
||||||
|
const <String>['plugin1:lintDebug'],
|
||||||
|
androidDir.path,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(<Matcher>[
|
||||||
|
contains('Running for plugin1'),
|
||||||
|
contains('No issues found!'),
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails if gradlew is missing', () async {
|
||||||
|
createFakePlugin('plugin1', packagesDir,
|
||||||
|
platformSupport: <String, PlatformSupport>{
|
||||||
|
kPlatformAndroid: PlatformSupport.inline
|
||||||
|
});
|
||||||
|
|
||||||
|
Error? commandError;
|
||||||
|
final List<String> output = await runCapturingPrint(
|
||||||
|
runner, <String>['lint-android'], errorHandler: (Error e) {
|
||||||
|
commandError = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(commandError, isA<ToolExit>());
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[
|
||||||
|
contains('Build example before linting'),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails if linting finds issues', () async {
|
||||||
|
createFakePlugin('plugin1', packagesDir,
|
||||||
|
platformSupport: <String, PlatformSupport>{
|
||||||
|
kPlatformAndroid: PlatformSupport.inline
|
||||||
|
});
|
||||||
|
|
||||||
|
processRunner.mockProcessesForExecutable['gradlew'] = <io.Process>[
|
||||||
|
MockProcess.failing(),
|
||||||
|
];
|
||||||
|
|
||||||
|
Error? commandError;
|
||||||
|
final List<String> output = await runCapturingPrint(
|
||||||
|
runner, <String>['lint-android'], errorHandler: (Error e) {
|
||||||
|
commandError = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(commandError, isA<ToolExit>());
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[
|
||||||
|
contains('Build example before linting'),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('skips non-Android plugins', () async {
|
||||||
|
createFakePlugin('plugin1', packagesDir);
|
||||||
|
|
||||||
|
final List<String> output =
|
||||||
|
await runCapturingPrint(runner, <String>['lint-android']);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[
|
||||||
|
contains(
|
||||||
|
'SKIPPING: Plugin does not have an Android implemenatation.')
|
||||||
|
],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('skips non-inline plugins', () async {
|
||||||
|
createFakePlugin('plugin1', packagesDir,
|
||||||
|
platformSupport: <String, PlatformSupport>{
|
||||||
|
kPlatformAndroid: PlatformSupport.federated
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<String> output =
|
||||||
|
await runCapturingPrint(runner, <String>['lint-android']);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[
|
||||||
|
contains(
|
||||||
|
'SKIPPING: Plugin does not have an Android implemenatation.')
|
||||||
|
],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Reference in New Issue
Block a user