Run static analyzer during xctest (#3667)
diff --git a/.cirrus.yml b/.cirrus.yml
index d7aebdd..ff8cc0c 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -179,7 +179,7 @@
- name: build-ipas+drive-examples
env:
PATH: $PATH:/usr/local/bin
- PLUGINS_TO_SKIP_XCTESTS: "battery/battery,camera/camera,connectivity/connectivity,device_info/device_info,espresso,google_maps_flutter/google_maps_flutter,google_sign_in/google_sign_in,in_app_purchase,integration_test,ios_platform_images,local_auth,package_info,path_provider/path_provider,sensors,shared_preferences/shared_preferences,url_launcher/url_launcher,video_player/video_player,wifi_info_flutter/wifi_info_flutter"
+ PLUGINS_TO_SKIP_XCTESTS: "integration_test"
matrix:
PLUGIN_SHARDING: "--shardIndex 0 --shardCount 4"
PLUGIN_SHARDING: "--shardIndex 1 --shardCount 4"
@@ -197,7 +197,7 @@
- flutter channel $CHANNEL
- flutter upgrade
- ./script/incremental_build.sh build-examples --ipa
- - ./script/incremental_build.sh xctest --target RunnerUITests --skip $PLUGINS_TO_SKIP_XCTESTS --ios-destination "platform=iOS Simulator,name=iPhone 11,OS=14.3"
+ - ./script/incremental_build.sh xctest --skip $PLUGINS_TO_SKIP_XCTESTS --ios-destination "platform=iOS Simulator,name=iPhone 11,OS=latest"
# `drive-examples` contains integration tests, which changes the UI of the application.
# This UI change sometimes affects `xctest`.
# So we run `drive-examples` after `xctest`, changing the order will result ci failure.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6c4abd0..5a8a9ec 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -69,7 +69,7 @@
1. Open <path_to_plugin>/example/ios/Runner.xcworkspace using XCode.
1. Create a new "UI Testing Bundle".
1. In the target options window, populate details as following, then click on "Finish".
- * In the "product name" field, type in "RunnerUITests" (this is the test target name our CI looks for.).
+ * In the "product name" field, type in "RunnerUITests".
* In the "Team" field, select "None".
* In the Organization Name field, type in "Flutter". This should usually be pre-populated.
* In the organization identifer field, type in "com.google". This should usually be pre-populated.
diff --git a/script/tool/lib/src/lint_podspecs_command.dart b/script/tool/lib/src/lint_podspecs_command.dart
index 68fd4b6..b100311 100644
--- a/script/tool/lib/src/lint_podspecs_command.dart
+++ b/script/tool/lib/src/lint_podspecs_command.dart
@@ -14,8 +14,7 @@
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 @@
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 @@
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 @@
}
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 @@
'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);
}
diff --git a/script/tool/lib/src/xctest_command.dart b/script/tool/lib/src/xctest_command.dart
index d90b7a8..a4d0336 100644
--- a/script/tool/lib/src/xctest_command.dart
+++ b/script/tool/lib/src/xctest_command.dart
@@ -12,17 +12,15 @@
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 @@
'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 @@
@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 @@
checkSharding();
- final String target = argResults[_kTarget];
final List<String> skipped = argResults[_kSkip];
List<String> failingPackages = <String>[];
@@ -92,57 +78,14 @@
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 @@
}
}
+ 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>[
diff --git a/script/tool/test/lint_podspecs_command_test.dart b/script/tool/test/lint_podspecs_command_test.dart
index 49d6ad4..1014dcd 100644
--- a/script/tool/test/lint_podspecs_command_test.dart
+++ b/script/tool/test/lint_podspecs_command_test.dart
@@ -81,7 +81,6 @@
'lib',
'lint',
p.join(plugin1Dir.path, 'ios', 'plugin1.podspec'),
- '--analyze',
'--use-libraries'
],
mockPackagesDir.path),
@@ -91,14 +90,13 @@
'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 @@
);
});
- 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 @@
'lint',
p.join(plugin1Dir.path, 'plugin1.podspec'),
'--allow-warnings',
- '--analyze',
'--use-libraries'
],
mockPackagesDir.path),
@@ -187,14 +149,13 @@
'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'));
});
});
}
diff --git a/script/tool/test/xctest_command_test.dart b/script/tool/test/xctest_command_test.dart
index 007c2e1..2b75ccd 100644
--- a/script/tool/test/xctest_command_test.dart
+++ b/script/tool/test/xctest_command_test.dart
@@ -8,7 +8,6 @@
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 @@
void main() {
const String _kDestination = '--ios-destination';
- const String _kTarget = '--target';
const String _kSkip = '--skip';
group('test xctest_command', () {
@@ -100,12 +98,6 @@
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 @@
processRunner.processToReturn = mockProcess;
final List<String> output = await runCapturingPrint(runner, <String>[
'xctest',
- _kTarget,
- 'foo_scheme',
_kDestination,
'foo_destination'
]);
@@ -134,106 +124,7 @@
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 @@
'{"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 @@
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 @@
jsonEncode(schemeCommandResult..addAll(_kDeviceListMap));
await runner.run(<String>[
'xctest',
- _kTarget,
- 'foo_scheme',
]);
expect(
@@ -333,21 +221,22 @@
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),
]));