mirror of
https://github.com/flutter/packages.git
synced 2025-06-03 17:48:10 +08:00
Run static analyzer during xctest (#3667)
This commit is contained in:
@ -14,8 +14,7 @@ import 'common.dart';
|
||||
|
||||
typedef void Print(Object object);
|
||||
|
||||
/// Lint the CocoaPod podspecs, run the static analyzer on iOS/macOS plugin
|
||||
/// platform code, and run unit tests.
|
||||
/// Lint the CocoaPod podspecs and run unit tests.
|
||||
///
|
||||
/// See https://guides.cocoapods.org/terminal/commands.html#pod_lib_lint.
|
||||
class LintPodspecsCommand extends PluginCommand {
|
||||
@ -35,10 +34,6 @@ class LintPodspecsCommand extends PluginCommand {
|
||||
help:
|
||||
'Do not pass --allow-warnings flag to "pod lib lint" for podspecs with this basename (example: plugins with known warnings)',
|
||||
valueHelp: 'podspec_file_name');
|
||||
argParser.addMultiOption('no-analyze',
|
||||
help:
|
||||
'Do not pass --analyze flag to "pod lib lint" for podspecs with this basename (example: plugins with known analyzer warnings)',
|
||||
valueHelp: 'podspec_file_name');
|
||||
}
|
||||
|
||||
@override
|
||||
@ -102,25 +97,17 @@ class LintPodspecsCommand extends PluginCommand {
|
||||
Future<bool> _lintPodspec(File podspec) async {
|
||||
// Do not run the static analyzer on plugins with known analyzer issues.
|
||||
final String podspecPath = podspec.path;
|
||||
final bool runAnalyzer = !argResults['no-analyze']
|
||||
.contains(p.basenameWithoutExtension(podspecPath));
|
||||
|
||||
final String podspecBasename = p.basename(podspecPath);
|
||||
if (runAnalyzer) {
|
||||
_print('Linting and analyzing $podspecBasename');
|
||||
} else {
|
||||
_print('Linting $podspecBasename');
|
||||
}
|
||||
_print('Linting $podspecBasename');
|
||||
|
||||
// Lint plugin as framework (use_frameworks!).
|
||||
final ProcessResult frameworkResult = await _runPodLint(podspecPath,
|
||||
runAnalyzer: runAnalyzer, libraryLint: true);
|
||||
final ProcessResult frameworkResult = await _runPodLint(podspecPath, libraryLint: true);
|
||||
_print(frameworkResult.stdout);
|
||||
_print(frameworkResult.stderr);
|
||||
|
||||
// Lint plugin as library.
|
||||
final ProcessResult libraryResult = await _runPodLint(podspecPath,
|
||||
runAnalyzer: runAnalyzer, libraryLint: false);
|
||||
final ProcessResult libraryResult = await _runPodLint(podspecPath, libraryLint: false);
|
||||
_print(libraryResult.stdout);
|
||||
_print(libraryResult.stderr);
|
||||
|
||||
@ -128,7 +115,7 @@ class LintPodspecsCommand extends PluginCommand {
|
||||
}
|
||||
|
||||
Future<ProcessResult> _runPodLint(String podspecPath,
|
||||
{bool runAnalyzer, bool libraryLint}) async {
|
||||
{bool libraryLint}) async {
|
||||
final bool allowWarnings = argResults['ignore-warnings']
|
||||
.contains(p.basenameWithoutExtension(podspecPath));
|
||||
final List<String> arguments = <String>[
|
||||
@ -136,10 +123,10 @@ class LintPodspecsCommand extends PluginCommand {
|
||||
'lint',
|
||||
podspecPath,
|
||||
if (allowWarnings) '--allow-warnings',
|
||||
if (runAnalyzer) '--analyze',
|
||||
if (libraryLint) '--use-libraries'
|
||||
];
|
||||
|
||||
_print('Running "pod ${arguments.join(' ')}"');
|
||||
return processRunner.run('pod', arguments,
|
||||
workingDir: packagesDir, stdoutEncoding: utf8, stderrEncoding: utf8);
|
||||
}
|
||||
|
@ -12,17 +12,15 @@ import 'package:path/path.dart' as p;
|
||||
import 'common.dart';
|
||||
|
||||
const String _kiOSDestination = 'ios-destination';
|
||||
const String _kTarget = 'target';
|
||||
const String _kSkip = 'skip';
|
||||
const String _kXcodeBuildCommand = 'xcodebuild';
|
||||
const String _kXCRunCommand = 'xcrun';
|
||||
const String _kFoundNoSimulatorsMessage =
|
||||
'Cannot find any available simulators, tests failed';
|
||||
|
||||
/// The command to run iOS' XCTests in plugins, this should work for both XCUnitTest and XCUITest targets.
|
||||
/// The tests target have to be added to the xcode project of the example app. Usually at "example/ios/Runner.xcodeproj".
|
||||
/// The command takes a "-target" argument which has to match the target of the test target.
|
||||
/// For information on how to add test target in an xcode project, see https://developer.apple.com/library/archive/documentation/ToolsLanguages/Conceptual/Xcode_Overview/UnitTesting.html
|
||||
/// The command to run iOS XCTests in plugins, this should work for both XCUnitTest and XCUITest targets.
|
||||
/// The tests target have to be added to the xcode project of the example app. Usually at "example/ios/Runner.xcworkspace".
|
||||
/// The static analyzer is also run.
|
||||
class XCTestCommand extends PluginCommand {
|
||||
XCTestCommand(
|
||||
Directory packagesDir,
|
||||
@ -36,10 +34,6 @@ class XCTestCommand extends PluginCommand {
|
||||
'this is passed to the `-destination` argument in 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.addOption(_kTarget,
|
||||
help: 'The test target.\n'
|
||||
'This is the xcode project test target. This is passed to the `-scheme` 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 scheme');
|
||||
argParser.addMultiOption(_kSkip,
|
||||
help: 'Plugins to skip while running this command. \n');
|
||||
}
|
||||
@ -49,17 +43,10 @@ class XCTestCommand extends PluginCommand {
|
||||
|
||||
@override
|
||||
final String description = 'Runs the xctests in the iOS example apps.\n\n'
|
||||
'This command requires "flutter" to be in your path.';
|
||||
'This command requires "flutter" and "xcrun" to be in your path.';
|
||||
|
||||
@override
|
||||
Future<Null> run() async {
|
||||
if (argResults[_kTarget] == null) {
|
||||
// TODO(cyanglaz): Automatically find all the available testing schemes if this argument is not specified.
|
||||
// https://github.com/flutter/flutter/issues/68419
|
||||
print('--$_kTarget must be specified');
|
||||
throw ToolExit(1);
|
||||
}
|
||||
|
||||
String destination = argResults[_kiOSDestination];
|
||||
if (destination == null) {
|
||||
String simulatorId = await _findAvailableIphoneSimulator();
|
||||
@ -72,7 +59,6 @@ class XCTestCommand extends PluginCommand {
|
||||
|
||||
checkSharding();
|
||||
|
||||
final String target = argResults[_kTarget];
|
||||
final List<String> skipped = argResults[_kSkip];
|
||||
|
||||
List<String> failingPackages = <String>[];
|
||||
@ -92,57 +78,14 @@ class XCTestCommand extends PluginCommand {
|
||||
continue;
|
||||
}
|
||||
for (Directory example in getExamplesForPlugin(plugin)) {
|
||||
// Look for the test scheme in the example app.
|
||||
print('Look for target named: $_kTarget ...');
|
||||
final List<String> findSchemeArgs = <String>[
|
||||
'-project',
|
||||
'ios/Runner.xcodeproj',
|
||||
'-list',
|
||||
'-json'
|
||||
];
|
||||
final String completeFindSchemeCommand =
|
||||
'$_kXcodeBuildCommand ${findSchemeArgs.join(' ')}';
|
||||
print(completeFindSchemeCommand);
|
||||
final io.ProcessResult xcodeprojListResult = await processRunner
|
||||
.run(_kXcodeBuildCommand, findSchemeArgs, workingDir: example);
|
||||
if (xcodeprojListResult.exitCode != 0) {
|
||||
print('Error occurred while running "$completeFindSchemeCommand":\n'
|
||||
'${xcodeprojListResult.stderr}');
|
||||
failingPackages.add(packageName);
|
||||
print('\n\n');
|
||||
continue;
|
||||
// Running tests and static analyzer.
|
||||
print('Running tests and analyzer for $packageName ...');
|
||||
int exitCode = await _runTests(true, destination, example);
|
||||
// 66 = there is no test target (this fails fast). Try again with just the analyzer.
|
||||
if (exitCode == 66) {
|
||||
print('Tests not found for $packageName, running analyzer only...');
|
||||
exitCode = await _runTests(false, destination, example);
|
||||
}
|
||||
|
||||
final String xcodeprojListOutput = xcodeprojListResult.stdout;
|
||||
Map<String, dynamic> xcodeprojListOutputJson =
|
||||
jsonDecode(xcodeprojListOutput);
|
||||
if (!xcodeprojListOutputJson['project']['targets'].contains(target)) {
|
||||
failingPackages.add(packageName);
|
||||
print('$target not configured for $packageName, test failed.');
|
||||
print(
|
||||
'Please check the scheme for the test target if it matches the name $target.\n'
|
||||
'If this plugin does not have an XCTest target, use the $_kSkip flag in the $name command to skip the plugin.');
|
||||
print('\n\n');
|
||||
continue;
|
||||
}
|
||||
// Found the scheme, running tests
|
||||
print('Running XCTests:$target for $packageName ...');
|
||||
final List<String> xctestArgs = <String>[
|
||||
'test',
|
||||
'-workspace',
|
||||
'ios/Runner.xcworkspace',
|
||||
'-scheme',
|
||||
target,
|
||||
'-destination',
|
||||
destination,
|
||||
'CODE_SIGN_IDENTITY=""',
|
||||
'CODE_SIGNING_REQUIRED=NO'
|
||||
];
|
||||
final String completeTestCommand =
|
||||
'$_kXcodeBuildCommand ${xctestArgs.join(' ')}';
|
||||
print(completeTestCommand);
|
||||
final int exitCode = await processRunner
|
||||
.runAndStream(_kXcodeBuildCommand, xctestArgs, workingDir: example);
|
||||
if (exitCode == 0) {
|
||||
print('Successfully ran xctest for $packageName');
|
||||
} else {
|
||||
@ -164,6 +107,31 @@ class XCTestCommand extends PluginCommand {
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> _runTests(bool runTests, String destination, Directory example) {
|
||||
final List<String> xctestArgs = <String>[
|
||||
_kXcodeBuildCommand,
|
||||
if (runTests)
|
||||
'test',
|
||||
'analyze',
|
||||
'-workspace',
|
||||
'ios/Runner.xcworkspace',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'-scheme',
|
||||
'Runner',
|
||||
'-destination',
|
||||
destination,
|
||||
'CODE_SIGN_IDENTITY=""',
|
||||
'CODE_SIGNING_REQUIRED=NO',
|
||||
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
|
||||
];
|
||||
final String completeTestCommand =
|
||||
'$_kXCRunCommand ${xctestArgs.join(' ')}';
|
||||
print(completeTestCommand);
|
||||
return processRunner
|
||||
.runAndStream(_kXCRunCommand, xctestArgs, workingDir: example, exitOnError: false);
|
||||
}
|
||||
|
||||
Future<String> _findAvailableIphoneSimulator() async {
|
||||
// Find the first available destination if not specified.
|
||||
final List<String> findSimulatorsArguments = <String>[
|
||||
|
@ -81,7 +81,6 @@ void main() {
|
||||
'lib',
|
||||
'lint',
|
||||
p.join(plugin1Dir.path, 'ios', 'plugin1.podspec'),
|
||||
'--analyze',
|
||||
'--use-libraries'
|
||||
],
|
||||
mockPackagesDir.path),
|
||||
@ -91,14 +90,13 @@ void main() {
|
||||
'lib',
|
||||
'lint',
|
||||
p.join(plugin1Dir.path, 'ios', 'plugin1.podspec'),
|
||||
'--analyze',
|
||||
],
|
||||
mockPackagesDir.path),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(
|
||||
printedMessages, contains('Linting and analyzing plugin1.podspec'));
|
||||
printedMessages, contains('Linting plugin1.podspec'));
|
||||
expect(printedMessages, contains('Foo'));
|
||||
expect(printedMessages, contains('Bar'));
|
||||
});
|
||||
@ -122,41 +120,6 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
test('skips analyzer for podspecs with known warnings', () async {
|
||||
Directory plugin1Dir =
|
||||
createFakePlugin('plugin1', withExtraFiles: <List<String>>[
|
||||
<String>['plugin1.podspec'],
|
||||
]);
|
||||
|
||||
await runner.run(<String>['podspecs', '--no-analyze=plugin1']);
|
||||
|
||||
expect(
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
ProcessCall('which', <String>['pod'], mockPackagesDir.path),
|
||||
ProcessCall(
|
||||
'pod',
|
||||
<String>[
|
||||
'lib',
|
||||
'lint',
|
||||
p.join(plugin1Dir.path, 'plugin1.podspec'),
|
||||
'--use-libraries'
|
||||
],
|
||||
mockPackagesDir.path),
|
||||
ProcessCall(
|
||||
'pod',
|
||||
<String>[
|
||||
'lib',
|
||||
'lint',
|
||||
p.join(plugin1Dir.path, 'plugin1.podspec'),
|
||||
],
|
||||
mockPackagesDir.path),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(printedMessages, contains('Linting plugin1.podspec'));
|
||||
});
|
||||
|
||||
test('allow warnings for podspecs with known warnings', () async {
|
||||
Directory plugin1Dir =
|
||||
createFakePlugin('plugin1', withExtraFiles: <List<String>>[
|
||||
@ -176,7 +139,6 @@ void main() {
|
||||
'lint',
|
||||
p.join(plugin1Dir.path, 'plugin1.podspec'),
|
||||
'--allow-warnings',
|
||||
'--analyze',
|
||||
'--use-libraries'
|
||||
],
|
||||
mockPackagesDir.path),
|
||||
@ -187,14 +149,13 @@ void main() {
|
||||
'lint',
|
||||
p.join(plugin1Dir.path, 'plugin1.podspec'),
|
||||
'--allow-warnings',
|
||||
'--analyze',
|
||||
],
|
||||
mockPackagesDir.path),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(
|
||||
printedMessages, contains('Linting and analyzing plugin1.podspec'));
|
||||
printedMessages, contains('Linting plugin1.podspec'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import 'package:args/command_runner.dart';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:flutter_plugin_tools/src/xctest_command.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:flutter_plugin_tools/src/common.dart';
|
||||
|
||||
import 'mocks.dart';
|
||||
import 'util.dart';
|
||||
@ -81,7 +80,6 @@ final _kDeviceListMap = {
|
||||
|
||||
void main() {
|
||||
const String _kDestination = '--ios-destination';
|
||||
const String _kTarget = '--target';
|
||||
const String _kSkip = '--skip';
|
||||
|
||||
group('test xctest_command', () {
|
||||
@ -100,12 +98,6 @@ void main() {
|
||||
cleanupPackages();
|
||||
});
|
||||
|
||||
test('Not specifying --target throws', () async {
|
||||
await expectLater(
|
||||
() => runner.run(<String>['xctest', _kDestination, 'a_destination']),
|
||||
throwsA(const TypeMatcher<ToolExit>()));
|
||||
});
|
||||
|
||||
test('skip if ios is not supported', () async {
|
||||
createFakePlugin('plugin',
|
||||
withExtraFiles: <List<String>>[
|
||||
@ -123,8 +115,6 @@ void main() {
|
||||
processRunner.processToReturn = mockProcess;
|
||||
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||
'xctest',
|
||||
_kTarget,
|
||||
'foo_scheme',
|
||||
_kDestination,
|
||||
'foo_destination'
|
||||
]);
|
||||
@ -134,106 +124,7 @@ void main() {
|
||||
cleanupPackages();
|
||||
});
|
||||
|
||||
test('running with correct scheme and destination, did not find scheme',
|
||||
() async {
|
||||
createFakePlugin('plugin',
|
||||
withExtraFiles: <List<String>>[
|
||||
<String>['example', 'test'],
|
||||
],
|
||||
isIosPlugin: true);
|
||||
|
||||
final Directory pluginExampleDirectory =
|
||||
mockPackagesDir.childDirectory('plugin').childDirectory('example');
|
||||
|
||||
createFakePubspec(pluginExampleDirectory, isFlutter: true);
|
||||
|
||||
final MockProcess mockProcess = MockProcess();
|
||||
mockProcess.exitCodeCompleter.complete(0);
|
||||
processRunner.processToReturn = mockProcess;
|
||||
processRunner.resultStdout = '{"project":{"targets":["bar_scheme"]}}';
|
||||
|
||||
await expectLater(() async {
|
||||
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||
'xctest',
|
||||
_kTarget,
|
||||
'foo_scheme',
|
||||
_kDestination,
|
||||
'foo_destination'
|
||||
]);
|
||||
expect(output,
|
||||
contains('foo_scheme not configured for plugin, test failed.'));
|
||||
expect(
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
ProcessCall('xcrun', <String>['simctl', 'list', '--json'], null),
|
||||
ProcessCall(
|
||||
'xcodebuild',
|
||||
<String>[
|
||||
'-project',
|
||||
'ios/Runner.xcodeproj',
|
||||
'-list',
|
||||
'-json'
|
||||
],
|
||||
pluginExampleDirectory.path),
|
||||
]));
|
||||
}, throwsA(const TypeMatcher<ToolExit>()));
|
||||
cleanupPackages();
|
||||
});
|
||||
|
||||
test('running with correct scheme and destination, found scheme', () async {
|
||||
createFakePlugin('plugin',
|
||||
withExtraFiles: <List<String>>[
|
||||
<String>['example', 'test'],
|
||||
],
|
||||
isIosPlugin: true);
|
||||
|
||||
final Directory pluginExampleDirectory =
|
||||
mockPackagesDir.childDirectory('plugin').childDirectory('example');
|
||||
|
||||
createFakePubspec(pluginExampleDirectory, isFlutter: true);
|
||||
|
||||
final MockProcess mockProcess = MockProcess();
|
||||
mockProcess.exitCodeCompleter.complete(0);
|
||||
processRunner.processToReturn = mockProcess;
|
||||
processRunner.resultStdout =
|
||||
'{"project":{"targets":["bar_scheme", "foo_scheme"]}}';
|
||||
List<String> output = await runCapturingPrint(runner, <String>[
|
||||
'xctest',
|
||||
_kTarget,
|
||||
'foo_scheme',
|
||||
_kDestination,
|
||||
'foo_destination'
|
||||
]);
|
||||
|
||||
expect(output, contains('Successfully ran xctest for plugin'));
|
||||
|
||||
expect(
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
ProcessCall(
|
||||
'xcodebuild',
|
||||
<String>['-project', 'ios/Runner.xcodeproj', '-list', '-json'],
|
||||
pluginExampleDirectory.path),
|
||||
ProcessCall(
|
||||
'xcodebuild',
|
||||
<String>[
|
||||
'test',
|
||||
'-workspace',
|
||||
'ios/Runner.xcworkspace',
|
||||
'-scheme',
|
||||
'foo_scheme',
|
||||
'-destination',
|
||||
'foo_destination',
|
||||
'CODE_SIGN_IDENTITY=""',
|
||||
'CODE_SIGNING_REQUIRED=NO'
|
||||
],
|
||||
pluginExampleDirectory.path),
|
||||
]));
|
||||
|
||||
cleanupPackages();
|
||||
});
|
||||
|
||||
test('running with correct scheme and destination, skip 1 plugin',
|
||||
test('running with correct destination, skip 1 plugin',
|
||||
() async {
|
||||
createFakePlugin('plugin1',
|
||||
withExtraFiles: <List<String>>[
|
||||
@ -260,8 +151,6 @@ void main() {
|
||||
'{"project":{"targets":["bar_scheme", "foo_scheme"]}}';
|
||||
List<String> output = await runCapturingPrint(runner, <String>[
|
||||
'xctest',
|
||||
_kTarget,
|
||||
'foo_scheme',
|
||||
_kDestination,
|
||||
'foo_destination',
|
||||
_kSkip,
|
||||
@ -275,21 +164,22 @@ void main() {
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
ProcessCall(
|
||||
'xcodebuild',
|
||||
<String>['-project', 'ios/Runner.xcodeproj', '-list', '-json'],
|
||||
pluginExampleDirectory2.path),
|
||||
ProcessCall(
|
||||
'xcodebuild',
|
||||
'xcrun',
|
||||
<String>[
|
||||
'xcodebuild',
|
||||
'test',
|
||||
'analyze',
|
||||
'-workspace',
|
||||
'ios/Runner.xcworkspace',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'-scheme',
|
||||
'foo_scheme',
|
||||
'Runner',
|
||||
'-destination',
|
||||
'foo_destination',
|
||||
'CODE_SIGN_IDENTITY=""',
|
||||
'CODE_SIGNING_REQUIRED=NO'
|
||||
'CODE_SIGNING_REQUIRED=NO',
|
||||
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
|
||||
],
|
||||
pluginExampleDirectory2.path),
|
||||
]));
|
||||
@ -324,8 +214,6 @@ void main() {
|
||||
jsonEncode(schemeCommandResult..addAll(_kDeviceListMap));
|
||||
await runner.run(<String>[
|
||||
'xctest',
|
||||
_kTarget,
|
||||
'foo_scheme',
|
||||
]);
|
||||
|
||||
expect(
|
||||
@ -333,21 +221,22 @@ void main() {
|
||||
orderedEquals(<ProcessCall>[
|
||||
ProcessCall('xcrun', <String>['simctl', 'list', '--json'], null),
|
||||
ProcessCall(
|
||||
'xcodebuild',
|
||||
<String>['-project', 'ios/Runner.xcodeproj', '-list', '-json'],
|
||||
pluginExampleDirectory.path),
|
||||
ProcessCall(
|
||||
'xcodebuild',
|
||||
'xcrun',
|
||||
<String>[
|
||||
'xcodebuild',
|
||||
'test',
|
||||
'analyze',
|
||||
'-workspace',
|
||||
'ios/Runner.xcworkspace',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'-scheme',
|
||||
'foo_scheme',
|
||||
'Runner',
|
||||
'-destination',
|
||||
'id=1E76A0FD-38AC-4537-A989-EA639D7D012A',
|
||||
'CODE_SIGN_IDENTITY=""',
|
||||
'CODE_SIGNING_REQUIRED=NO'
|
||||
'CODE_SIGNING_REQUIRED=NO',
|
||||
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
|
||||
],
|
||||
pluginExampleDirectory.path),
|
||||
]));
|
||||
|
Reference in New Issue
Block a user