Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions script/tool/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Pubspec validation now checks for `implements` in implementation packages.
- Pubspec valitation now checks the full relative path of `repository` entries.
- `build-examples` now supports UWP plugins via a `--winuwp` flag.
- `native-test` now supports `--windows` for unit tests.

## 0.5.0

Expand Down
86 changes: 62 additions & 24 deletions script/tool/lib/src/common/plugin_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ enum PlatformSupport {
/// implementation in order to return true.
bool pluginSupportsPlatform(
String platform,
RepositoryPackage package, {
RepositoryPackage plugin, {
PlatformSupport? requiredMode,
String? variant,
}) {
Expand All @@ -41,32 +41,12 @@ bool pluginSupportsPlatform(
platform == kPlatformWindows ||
platform == kPlatformLinux);
try {
final YamlMap pubspecYaml =
loadYaml(package.pubspecFile.readAsStringSync()) as YamlMap;
final YamlMap? flutterSection = pubspecYaml['flutter'] as YamlMap?;
if (flutterSection == null) {
return false;
}
final YamlMap? pluginSection = flutterSection['plugin'] as YamlMap?;
if (pluginSection == null) {
return false;
}
final YamlMap? platforms = pluginSection['platforms'] as YamlMap?;
if (platforms == null) {
// Legacy plugin specs are assumed to support iOS and Android. They are
// never federated.
if (requiredMode == PlatformSupport.federated) {
return false;
}
if (!pluginSection.containsKey('platforms')) {
return platform == kPlatformIos || platform == kPlatformAndroid;
}
return false;
}
final YamlMap? platformEntry = platforms[platform] as YamlMap?;
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) {
Expand Down Expand Up @@ -97,9 +77,67 @@ bool pluginSupportsPlatform(
}

return true;
} on YamlException {
return false;
}
}

/// Returns whether the given [plugin] includes native code for [platform], as
/// opposed to being implemented entirely in Dart.
bool pluginHasNativeCodeForPlatform(String platform, RepositoryPackage plugin) {
if (platform == kPlatformWeb) {
// Web plugins are always Dart-only.
return false;
}
try {
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';
} on FileSystemException {
return false;
} on YamlException {
return false;
}
}

/// 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) {
try {
final File pubspecFile = plugin.pubspecFile;
final YamlMap pubspecYaml =
loadYaml(pubspecFile.readAsStringSync()) as YamlMap;
final YamlMap? flutterSection = pubspecYaml['flutter'] as YamlMap?;
if (flutterSection == null) {
return null;
}
final YamlMap? pluginSection = flutterSection['plugin'] as YamlMap?;
if (pluginSection == null) {
return null;
}
final YamlMap? platforms = pluginSection['platforms'] as YamlMap?;
if (platforms == null) {
return null;
}
return platforms[platform] as YamlMap?;
} on FileSystemException {
return null;
} on YamlException {
return null;
}
}
89 changes: 85 additions & 4 deletions script/tool/lib/src/native_test_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class NativeTestCommand extends PackageLoopingCommand {
argParser.addFlag(kPlatformAndroid, help: 'Runs Android tests');
argParser.addFlag(kPlatformIos, help: 'Runs iOS tests');
argParser.addFlag(kPlatformMacos, help: 'Runs macOS tests');
argParser.addFlag(kPlatformWindows, help: 'Runs Windows tests');

// By default, both unit tests and integration tests are run, but provide
// flags to disable one or the other.
Expand Down Expand Up @@ -80,6 +81,7 @@ this command.
kPlatformAndroid: _PlatformDetails('Android', _testAndroid),
kPlatformIos: _PlatformDetails('iOS', _testIos),
kPlatformMacos: _PlatformDetails('macOS', _testMacOS),
kPlatformWindows: _PlatformDetails('Windows', _testWindows),
};
_requestedPlatforms = _platforms.keys
.where((String platform) => getBoolArg(platform))
Expand All @@ -96,6 +98,11 @@ this command.
throw ToolExit(exitInvalidArguments);
}

if (getBoolArg(kPlatformWindows) && getBoolArg(_integrationTestFlag)) {
logWarning('This command currently only supports unit tests for Windows. '
'See https://github.com/flutter/flutter/issues/70233.');
}

// iOS-specific run-level state.
if (_requestedPlatforms.contains('ios')) {
String destination = getStringArg(_iosDestinationFlag);
Expand All @@ -119,16 +126,20 @@ this command.
Future<PackageResult> runForPackage(RepositoryPackage package) async {
final List<String> testPlatforms = <String>[];
for (final String platform in _requestedPlatforms) {
if (pluginSupportsPlatform(platform, package,
if (!pluginSupportsPlatform(platform, package,
requiredMode: PlatformSupport.inline)) {
testPlatforms.add(platform);
} else {
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('Not implemented for target platform(s).');
return PackageResult.skip('Nothing to test for target platform(s).');
}

final _TestMode mode = _TestMode(
Expand Down Expand Up @@ -228,6 +239,8 @@ this command.
final bool hasIntegrationTests =
exampleHasNativeIntegrationTests(example);

// TODO(stuartmorgan): Make !hasUnitTests fatal. See
// https://github.com/flutter/flutter/issues/85469
if (mode.unit && !hasUnitTests) {
_printNoExampleTestsMessage(example, 'Android unit');
}
Expand Down Expand Up @@ -335,6 +348,9 @@ this command.
for (final RepositoryPackage example in plugin.getExamples()) {
final String exampleName = example.displayName;

// TODO(stuartmorgan): Always check for RunnerTests, and make it fatal if
// no examples have it. See
// https://github.com/flutter/flutter/issues/85469
if (testTarget != null) {
final Directory project = example.directory
.childDirectory(platform.toLowerCase())
Expand Down Expand Up @@ -387,6 +403,71 @@ this command.
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,
buildDirectoryName: 'windows', 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, {
required String buildDirectoryName,
required bool Function(File) isTestBinary,
}) async {
final List<File> testBinaries = <File>[];
for (final RepositoryPackage example in plugin.getExamples()) {
final Directory buildDir = example.directory
.childDirectory('build')
.childDirectory(buildDirectoryName);
if (!buildDir.existsSync()) {
continue;
}
testBinaries.addAll(buildDir
.listSync(recursive: true)
.whereType<File>()
.where(isTestBinary)
.where((File file) {
// Only run the debug build of the unit tests, to avoid running the
// same tests multiple times.
final List<String> components = path.split(file.path);
return components.contains('debug') || components.contains('Debug');
}));
}

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 $buildDirectoryName unit tests found');
}

bool passing = true;
for (final File test in testBinaries) {
print('Running ${test.basename}...');
final int exitCode =
await processRunner.runAndStream(test.path, <String>[]);
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(
Expand Down
68 changes: 68 additions & 0 deletions script/tool/test/common/plugin_utils_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -273,4 +273,72 @@ void main() {
isTrue);
});
});

group('pluginHasNativeCodeForPlatform', () {
test('returns false for web', () async {
final RepositoryPackage plugin = RepositoryPackage(createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
kPlatformWeb: const PlatformDetails(PlatformSupport.inline),
},
));

expect(pluginHasNativeCodeForPlatform(kPlatformWeb, plugin), isFalse);
});

test('returns false for a native-only plugin', () async {
final RepositoryPackage plugin = RepositoryPackage(createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
kPlatformLinux: const PlatformDetails(PlatformSupport.inline),
kPlatformMacos: const PlatformDetails(PlatformSupport.inline),
kPlatformWindows: const PlatformDetails(PlatformSupport.inline),
},
));

expect(pluginHasNativeCodeForPlatform(kPlatformLinux, plugin), isTrue);
expect(pluginHasNativeCodeForPlatform(kPlatformMacos, plugin), isTrue);
expect(pluginHasNativeCodeForPlatform(kPlatformWindows, plugin), isTrue);
});

test('returns true for a native+Dart plugin', () async {
final RepositoryPackage plugin = RepositoryPackage(createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
kPlatformLinux: const PlatformDetails(PlatformSupport.inline,
hasNativeCode: true, hasDartCode: true),
kPlatformMacos: const PlatformDetails(PlatformSupport.inline,
hasNativeCode: true, hasDartCode: true),
kPlatformWindows: const PlatformDetails(PlatformSupport.inline,
hasNativeCode: true, hasDartCode: true),
},
));

expect(pluginHasNativeCodeForPlatform(kPlatformLinux, plugin), isTrue);
expect(pluginHasNativeCodeForPlatform(kPlatformMacos, plugin), isTrue);
expect(pluginHasNativeCodeForPlatform(kPlatformWindows, plugin), isTrue);
});

test('returns false for a Dart-only plugin', () async {
final RepositoryPackage plugin = RepositoryPackage(createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
kPlatformLinux: const PlatformDetails(PlatformSupport.inline,
hasNativeCode: false, hasDartCode: true),
kPlatformMacos: const PlatformDetails(PlatformSupport.inline,
hasNativeCode: false, hasDartCode: true),
kPlatformWindows: const PlatformDetails(PlatformSupport.inline,
hasNativeCode: false, hasDartCode: true),
},
));

expect(pluginHasNativeCodeForPlatform(kPlatformLinux, plugin), isFalse);
expect(pluginHasNativeCodeForPlatform(kPlatformMacos, plugin), isFalse);
expect(pluginHasNativeCodeForPlatform(kPlatformWindows, plugin), isFalse);
});
});
}
Loading