This repository has been archived by the owner on Feb 22, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
[flutter_plugin_tests] Split analyze out of xctest #4161
Merged
Merged
Changes from 6 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
3611f35
Add more options to control what runs
stuartmorgan 47e9d90
Report skip, not fail, when analyze is off and there are no tests
stuartmorgan 33940f5
Extract a helper for Xcode commands, and add tests for it
stuartmorgan aba3199
Migrate xctest to the Xcode class
stuartmorgan fd1958a
Split out xcode-analyze
stuartmorgan b27265a
Comment fix
stuartmorgan 1a357e5
Remove unnecessary process mocking
stuartmorgan 02945c9
Skip, rather than failing, when a package doesn't have a requested te…
stuartmorgan b4be22c
Merge branch 'master' into xctest-refactor
stuartmorgan be1596a
Filter out unavailable via simctl
stuartmorgan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
// 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 commends 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<int> runXcodeBuild( | ||
Directory directory, { | ||
List<String> actions = const <String>['build'], | ||
required String workspace, | ||
required String scheme, | ||
String? configuration, | ||
List<String> extraFlags = const <String>[], | ||
}) { | ||
final List<String> xctestArgs = <String>[ | ||
_xcodeBuildCommand, | ||
...actions, | ||
if (workspace != null) ...<String>['-workspace', workspace], | ||
if (scheme != null) ...<String>['-scheme', scheme], | ||
if (configuration != null) ...<String>['-configuration', configuration], | ||
...extraFlags, | ||
]; | ||
final String completeTestCommand = '$_xcRunCommand ${xctestArgs.join(' ')}'; | ||
if (log) { | ||
print(completeTestCommand); | ||
} | ||
return processRunner.runAndStream(_xcRunCommand, xctestArgs, | ||
workingDir: directory); | ||
} | ||
|
||
/// Returns the newest available simulator (highest OS version, with ties | ||
/// broken in favor of newest device), if any. | ||
Future<String?> findBestAvailableIphoneSimulator() async { | ||
final List<String> findSimulatorsArguments = <String>[ | ||
'simctl', | ||
'list', | ||
'--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<String, dynamic> simulatorListJson = | ||
jsonDecode(findSimulatorsResult.stdout as String) | ||
as Map<String, dynamic>; | ||
final List<Map<String, dynamic>> runtimes = | ||
(simulatorListJson['runtimes'] as List<dynamic>) | ||
.cast<Map<String, dynamic>>(); | ||
final Map<String, Object> devices = | ||
(simulatorListJson['devices'] as Map<String, dynamic>) | ||
.cast<String, Object>(); | ||
if (runtimes.isEmpty || devices.isEmpty) { | ||
return null; | ||
} | ||
String? id; | ||
// Looking for runtimes, trying to find one with highest OS version. | ||
for (final Map<String, dynamic> rawRuntimeMap in runtimes.reversed) { | ||
final Map<String, Object> runtimeMap = | ||
rawRuntimeMap.cast<String, Object>(); | ||
if ((runtimeMap['name'] as String?)?.contains('iOS') != true) { | ||
continue; | ||
} | ||
final String? runtimeID = runtimeMap['identifier'] as String?; | ||
if (runtimeID == null) { | ||
continue; | ||
} | ||
final List<Map<String, dynamic>>? devicesForRuntime = | ||
(devices[runtimeID] as List<dynamic>?)?.cast<Map<String, dynamic>>(); | ||
if (devicesForRuntime == null || devicesForRuntime.isEmpty) { | ||
continue; | ||
} | ||
// Looking for runtimes, trying to find latest version of device. | ||
for (final Map<String, dynamic> rawDevice in devicesForRuntime.reversed) { | ||
final Map<String, Object> device = rawDevice.cast<String, Object>(); | ||
if (device['availabilityError'] != null || | ||
(device['isAvailable'] as bool?) == false) { | ||
continue; | ||
} | ||
id = device['udid'] as String?; | ||
if (id == null) { | ||
continue; | ||
} | ||
if (log) { | ||
print('device selected: $device'); | ||
} | ||
return id; | ||
} | ||
} | ||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// 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/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(kPlatformIos, help: 'Analyze iOS'); | ||
argParser.addFlag(kPlatformMacos, help: 'Analyze macOS'); | ||
} | ||
|
||
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<void> initializeRun() async { | ||
if (!(getBoolArg(kPlatformIos) || getBoolArg(kPlatformMacos))) { | ||
printError('At least one platform flag must be provided.'); | ||
throw ToolExit(exitInvalidArguments); | ||
} | ||
} | ||
|
||
@override | ||
Future<PackageResult> runForPackage(Directory package) async { | ||
final bool testIos = getBoolArg(kPlatformIos) && | ||
pluginSupportsPlatform(kPlatformIos, package, | ||
requiredMode: PlatformSupport.inline); | ||
final bool testMacos = getBoolArg(kPlatformMacos) && | ||
pluginSupportsPlatform(kPlatformMacos, package, | ||
requiredMode: PlatformSupport.inline); | ||
|
||
final bool multiplePlatformsRequested = | ||
getBoolArg(kPlatformIos) && getBoolArg(kPlatformMacos); | ||
if (!(testIos || testMacos)) { | ||
return PackageResult.skip('Not implemented for target platform(s).'); | ||
} | ||
|
||
final List<String> failures = <String>[]; | ||
if (testIos && | ||
!await _analyzePlugin(package, 'iOS', extraFlags: <String>[ | ||
'-destination', | ||
'generic/platform=iOS Simulator' | ||
])) { | ||
failures.add('iOS'); | ||
} | ||
if (testMacos && !await _analyzePlugin(package, 'macOS')) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't need to worry about this now, but when CI starts using Xcode 13 next year we may need a macOS |
||
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 : <String>[]); | ||
} | ||
|
||
/// Analyzes [plugin] for [platform], returning true if it passed analysis. | ||
Future<bool> _analyzePlugin( | ||
Directory plugin, | ||
String platform, { | ||
List<String> extraFlags = const <String>[], | ||
}) async { | ||
bool passing = true; | ||
for (final Directory example in getExamplesForPlugin(plugin)) { | ||
// Running tests and static analyzer. | ||
final String examplePath = | ||
getRelativePosixPath(example, from: plugin.parent); | ||
print('Running $platform tests and analyzer for $examplePath...'); | ||
final int exitCode = await _xcode.runXcodeBuild( | ||
example, | ||
actions: <String>['analyze'], | ||
workspace: '${platform.toLowerCase()}/Runner.xcworkspace', | ||
scheme: 'Runner', | ||
configuration: 'Debug', | ||
extraFlags: <String>[ | ||
...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; | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know you just copied this, but if you add
devices available
The
availabilityError
/isAvailable
check below can be removed.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, done! (I had to add 'runtimes' as well since it's parsing those too.)