Unverified Commit 186765b3 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] cleanups to plugin test cases (#67242)

Removes usage of global variables, expands documentation, and fixes some formatting inconsistencies. Increased test coverage for android plugin to prove it can determine the embedding version.
parent 304e2c57
......@@ -7,7 +7,6 @@ import 'package:yaml/yaml.dart';
import 'base/common.dart';
import 'base/file_system.dart';
import 'globals.dart' as globals;
/// Constant for 'pluginClass' key in plugin maps.
const String kPluginClass = 'pluginClass';
......@@ -38,18 +37,22 @@ class AndroidPlugin extends PluginPlatform {
@required this.package,
@required this.pluginClass,
@required this.pluginPath,
});
@required FileSystem fileSystem,
}) : _fileSystem = fileSystem;
factory AndroidPlugin.fromYaml(String name, YamlMap yaml, String pluginPath) {
factory AndroidPlugin.fromYaml(String name, YamlMap yaml, String pluginPath, FileSystem fileSystem) {
assert(validate(yaml));
return AndroidPlugin(
name: name,
package: yaml['package'] as String,
pluginClass: yaml['pluginClass'] as String,
pluginPath: pluginPath,
fileSystem: fileSystem,
);
}
final FileSystem _fileSystem;
static bool validate(YamlMap yaml) {
if (yaml == null) {
return false;
......@@ -91,7 +94,7 @@ class AndroidPlugin extends PluginPlatform {
Set<String> _getSupportedEmbeddings() {
assert(pluginPath != null);
final Set<String> supportedEmbeddings = <String>{};
final String baseMainPath = globals.fs.path.join(
final String baseMainPath = _fileSystem.path.join(
pluginPath,
'android',
'src',
......@@ -99,16 +102,16 @@ class AndroidPlugin extends PluginPlatform {
);
final List<String> mainClassCandidates = <String>[
globals.fs.path.join(
_fileSystem.path.join(
baseMainPath,
'java',
package.replaceAll('.', globals.fs.path.separator),
package.replaceAll('.', _fileSystem.path.separator),
'$pluginClass.java',
),
globals.fs.path.join(
_fileSystem.path.join(
baseMainPath,
'kotlin',
package.replaceAll('.', globals.fs.path.separator),
package.replaceAll('.', _fileSystem.path.separator),
'$pluginClass.kt',
)
];
......@@ -116,7 +119,7 @@ class AndroidPlugin extends PluginPlatform {
File mainPluginClass;
bool mainClassFound = false;
for (final String mainClassCandidate in mainClassCandidates) {
mainPluginClass = globals.fs.file(mainClassCandidate);
mainPluginClass = _fileSystem.file(mainClassCandidate);
if (mainPluginClass.existsSync()) {
mainClassFound = true;
break;
......@@ -132,15 +135,7 @@ class AndroidPlugin extends PluginPlatform {
);
}
String mainClassContent;
try {
mainClassContent = mainPluginClass.readAsStringSync();
} on FileSystemException {
throwToolExit(
"Couldn't read file ${mainPluginClass.path} even though it exists. "
'Please verify that this file has read permission and try again.'
);
}
final String mainClassContent = mainPluginClass.readAsStringSync();
if (mainClassContent
.contains('io.flutter.embedding.engine.plugins.FlutterPlugin')) {
supportedEmbeddings.add('2');
......@@ -167,7 +162,7 @@ class IOSPlugin extends PluginPlatform {
});
factory IOSPlugin.fromYaml(String name, YamlMap yaml) {
assert(validate(yaml));
assert(validate(yaml)); // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/67241
return IOSPlugin(
name: name,
classPrefix: '',
......
......@@ -68,16 +68,17 @@ class Plugin {
String name,
String path,
YamlMap pluginYaml,
List<String> dependencies,
) {
List<String> dependencies, {
@required FileSystem fileSystem,
}) {
final List<String> errors = validatePluginYaml(pluginYaml);
if (errors.isNotEmpty) {
throwToolExit('Invalid plugin specification $name.\n${errors.join('\n')}');
}
if (pluginYaml != null && pluginYaml['platforms'] != null) {
return Plugin._fromMultiPlatformYaml(name, path, pluginYaml, dependencies);
return Plugin._fromMultiPlatformYaml(name, path, pluginYaml, dependencies, fileSystem);
}
return Plugin._fromLegacyYaml(name, path, pluginYaml, dependencies);
return Plugin._fromLegacyYaml(name, path, pluginYaml, dependencies, fileSystem);
}
factory Plugin._fromMultiPlatformYaml(
......@@ -85,6 +86,7 @@ class Plugin {
String path,
dynamic pluginYaml,
List<String> dependencies,
FileSystem fileSystem,
) {
assert (pluginYaml != null && pluginYaml['platforms'] != null,
'Invalid multi-platform plugin specification $name.');
......@@ -100,6 +102,7 @@ class Plugin {
name,
platformsYaml[AndroidPlugin.kConfigKey] as YamlMap,
path,
fileSystem,
);
}
......@@ -141,6 +144,7 @@ class Plugin {
String path,
dynamic pluginYaml,
List<String> dependencies,
FileSystem fileSystem,
) {
final Map<String, PluginPlatform> platforms = <String, PluginPlatform>{};
final String pluginClass = pluginYaml['pluginClass'] as String;
......@@ -152,6 +156,7 @@ class Plugin {
package: pluginYaml['androidPackage'] as String,
pluginClass: pluginClass,
pluginPath: path,
fileSystem: fileSystem,
);
}
......@@ -332,6 +337,7 @@ Plugin _pluginFromPackage(String name, Uri packageRoot) {
packageRootPath,
flutterConfig['plugin'] as YamlMap,
dependencies == null ? <String>[] : <String>[...dependencies.keys.cast<String>()],
fileSystem: globals.fs,
);
}
......
// Copyright 2014 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:file/memory.dart';
import 'package:flutter_tools/src/platform_plugins.dart';
import '../src/common.dart';
void main() {
testWithoutContext('AndroidPlugin throws tool exit if the plugin main class can not be found', () {
final FileSystem fileSystem = MemoryFileSystem.test();
final AndroidPlugin androidPlugin = AndroidPlugin(
name: 'pluginA',
package: 'com.company',
pluginClass: 'PluginA',
pluginPath: '.pub_cache/plugin_a',
fileSystem: fileSystem,
);
expect(() => androidPlugin.toMap(), throwsToolExit(
message: "The plugin `pluginA` doesn't have a main class defined in "
'.pub_cache/plugin_a/android/src/main/java/com/company/PluginA.java '
'or .pub_cache/plugin_a/android/src/main/kotlin/com/company/PluginA.kt'
));
});
testWithoutContext('AndroidPlugin parses embedding version 2 from the Java search path', () {
final FileSystem fileSystem = MemoryFileSystem.test();
final AndroidPlugin androidPlugin = AndroidPlugin(
name: 'pluginA',
package: 'com.company',
pluginClass: 'PluginA',
pluginPath: '.pub_cache/plugin_a',
fileSystem: fileSystem,
);
fileSystem.file('.pub_cache/plugin_a/android/src/main/java/com/company/PluginA.java')
..createSync(recursive: true)
..writeAsStringSync('io.flutter.embedding.engine.plugins.FlutterPlugin');
expect(androidPlugin.toMap(), <String, Object>{
'name': 'pluginA',
'package': 'com.company',
'class': 'PluginA',
'supportsEmbeddingV1': false,
'supportsEmbeddingV2': true,
});
});
testWithoutContext('AndroidPlugin parses embedding version 1 from the Java search path', () {
final FileSystem fileSystem = MemoryFileSystem.test();
final AndroidPlugin androidPlugin = AndroidPlugin(
name: 'pluginA',
package: 'com.company',
pluginClass: 'PluginA',
pluginPath: '.pub_cache/plugin_a',
fileSystem: fileSystem,
);
fileSystem.file('.pub_cache/plugin_a/android/src/main/java/com/company/PluginA.java')
..createSync(recursive: true)
..writeAsStringSync('some.other.string');
expect(androidPlugin.toMap(), <String, Object>{
'name': 'pluginA',
'package': 'com.company',
'class': 'PluginA',
'supportsEmbeddingV1': true,
'supportsEmbeddingV2': false,
});
});
testWithoutContext('AndroidPlugin parses embedding version 2 from the Kotlin search path', () {
final FileSystem fileSystem = MemoryFileSystem.test();
final AndroidPlugin androidPlugin = AndroidPlugin(
name: 'pluginA',
package: 'com.company',
pluginClass: 'PluginA',
pluginPath: '.pub_cache/plugin_a',
fileSystem: fileSystem,
);
fileSystem.file('.pub_cache/plugin_a/android/src/main/kotlin/com/company/PluginA.kt')
..createSync(recursive: true)
..writeAsStringSync('io.flutter.embedding.engine.plugins.FlutterPlugin');
expect(androidPlugin.toMap(), <String, Object>{
'name': 'pluginA',
'package': 'com.company',
'class': 'PluginA',
'supportsEmbeddingV1': false,
'supportsEmbeddingV2': true,
});
});
testWithoutContext('AndroidPlugin parses embedding version 1 from the Kotlin search path', () {
final FileSystem fileSystem = MemoryFileSystem.test();
final AndroidPlugin androidPlugin = AndroidPlugin(
name: 'pluginA',
package: 'com.company',
pluginClass: 'PluginA',
pluginPath: '.pub_cache/plugin_a',
fileSystem: fileSystem,
);
fileSystem.file('.pub_cache/plugin_a/android/src/main/kotlin/com/company/PluginA.kt')
..createSync(recursive: true)
..writeAsStringSync('some.other.string');
expect(androidPlugin.toMap(), <String, Object>{
'name': 'pluginA',
'package': 'com.company',
'class': 'PluginA',
'supportsEmbeddingV1': true,
'supportsEmbeddingV2': false,
});
});
}
// Copyright 2014 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_tools/src/platform_plugins.dart';
import 'package:mockito/mockito.dart';
import 'package:path/path.dart' as p;
import '../src/common.dart';
import '../src/context.dart';
void main() {
group('AndroidPlugin', () {
MockFileSystem mockFileSystem;
MockPathContext pathContext;
setUp(() {
pathContext = MockPathContext();
when(pathContext.separator).thenReturn('/');
mockFileSystem = MockFileSystem();
when(mockFileSystem.path).thenReturn(pathContext);
});
testUsingContext("throws tool exit if the plugin main class can't be read", () {
when(pathContext.join('.pub_cache/plugin_a', 'android', 'src', 'main'))
.thenReturn('.pub_cache/plugin_a/android/src/main');
when(pathContext.join('.pub_cache/plugin_a/android/src/main', 'java', 'com/company', 'PluginA.java'))
.thenReturn('.pub_cache/plugin_a/android/src/main/java/com/company/PluginA.java');
when(pathContext.join('.pub_cache/plugin_a/android/src/main', 'kotlin', 'com/company', 'PluginA.kt'))
.thenReturn('.pub_cache/plugin_a/android/src/main/kotlin/com/company/PluginA.kt');
final MockFile pluginJavaMainClass = MockFile();
when(pluginJavaMainClass.existsSync()).thenReturn(true);
when(pluginJavaMainClass.readAsStringSync(encoding: anyNamed('encoding'))).thenThrow(const FileSystemException());
when(mockFileSystem.file('.pub_cache/plugin_a/android/src/main/java/com/company/PluginA.java'))
.thenReturn(pluginJavaMainClass);
final MockFile pluginKotlinMainClass = MockFile();
when(pluginKotlinMainClass.existsSync()).thenReturn(false);
when(mockFileSystem.file('.pub_cache/plugin_a/android/src/main/kotlin/com/company/PluginA.kt'))
.thenReturn(pluginKotlinMainClass);
expect(() {
AndroidPlugin(
name: 'pluginA',
package: 'com.company',
pluginClass: 'PluginA',
pluginPath: '.pub_cache/plugin_a',
).toMap();
}, throwsToolExit(
message: "Couldn't read file null even though it exists. "
'Please verify that this file has read permission and try again.'
));
}, overrides: <Type, Generator>{
FileSystem: () => mockFileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
});
}
class MockFile extends Mock implements File {}
class MockFileSystem extends Mock implements FileSystem {}
class MockPathContext extends Mock implements p.Context {}
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/platform_plugins.dart';
import 'package:flutter_tools/src/plugins.dart';
import 'package:yaml/yaml.dart';
......@@ -12,18 +14,22 @@ const String _kTestPluginName = 'test_plugin_name';
const String _kTestPluginPath = 'test_plugin_path';
void main() {
group('PluginParsing', () {
test('Legacy Format', () {
testWithoutContext('Plugin creation from the legacy format', () {
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
const String pluginYamlRaw = 'androidPackage: com.flutter.dev\n'
'iosPrefix: FLT\n'
'pluginClass: SamplePlugin\n';
final YamlMap pluginYaml = loadYaml(pluginYamlRaw) as YamlMap;
final Plugin plugin =
Plugin.fromYaml(_kTestPluginName, _kTestPluginPath, pluginYaml, const <String>[]);
final Plugin plugin = Plugin.fromYaml(
_kTestPluginName,
_kTestPluginPath,
pluginYaml,
const <String>[],
fileSystem: fileSystem,
);
final AndroidPlugin androidPlugin =
plugin.platforms[AndroidPlugin.kConfigKey] as AndroidPlugin;
final AndroidPlugin androidPlugin = plugin.platforms[AndroidPlugin.kConfigKey] as AndroidPlugin;
final IOSPlugin iosPlugin = plugin.platforms[IOSPlugin.kConfigKey] as IOSPlugin;
final String androidPluginClass = androidPlugin.pluginClass;
final String iosPluginClass = iosPlugin.pluginClass;
......@@ -34,7 +40,8 @@ void main() {
expect(androidPlugin.package, 'com.flutter.dev');
});
test('Multi-platform Format', () {
testWithoutContext('Plugin creation from the multi-platform format', () {
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
const String pluginYamlRaw = 'platforms:\n'
' android:\n'
' package: com.flutter.dev\n'
......@@ -52,19 +59,20 @@ void main() {
' pluginClass: WinSamplePlugin\n';
final YamlMap pluginYaml = loadYaml(pluginYamlRaw) as YamlMap;
final Plugin plugin =
Plugin.fromYaml(_kTestPluginName, _kTestPluginPath, pluginYaml, const <String>[]);
final Plugin plugin = Plugin.fromYaml(
_kTestPluginName,
_kTestPluginPath,
pluginYaml,
const <String>[],
fileSystem: fileSystem,
);
final AndroidPlugin androidPlugin =
plugin.platforms[AndroidPlugin.kConfigKey] as AndroidPlugin;
final AndroidPlugin androidPlugin = plugin.platforms[AndroidPlugin.kConfigKey] as AndroidPlugin;
final IOSPlugin iosPlugin = plugin.platforms[IOSPlugin.kConfigKey] as IOSPlugin;
final LinuxPlugin linuxPlugin =
plugin.platforms[LinuxPlugin.kConfigKey] as LinuxPlugin;
final MacOSPlugin macOSPlugin =
plugin.platforms[MacOSPlugin.kConfigKey] as MacOSPlugin;
final LinuxPlugin linuxPlugin = plugin.platforms[LinuxPlugin.kConfigKey] as LinuxPlugin;
final MacOSPlugin macOSPlugin = plugin.platforms[MacOSPlugin.kConfigKey] as MacOSPlugin;
final WebPlugin webPlugin = plugin.platforms[WebPlugin.kConfigKey] as WebPlugin;
final WindowsPlugin windowsPlugin =
plugin.platforms[WindowsPlugin.kConfigKey] as WindowsPlugin;
final WindowsPlugin windowsPlugin = plugin.platforms[WindowsPlugin.kConfigKey] as WindowsPlugin;
final String androidPluginClass = androidPlugin.pluginClass;
final String iosPluginClass = iosPlugin.pluginClass;
......@@ -79,7 +87,8 @@ void main() {
expect(windowsPlugin.pluginClass, 'WinSamplePlugin');
});
test('Unknown fields are allowed (allows some future compatibility)', () {
testWithoutContext('Plugin parsing of unknown fields are allowed (allows some future compatibility)', () {
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
const String pluginYamlRaw = 'implements: same_plugin\n' // this should be ignored by the tool
'platforms:\n'
' android:\n'
......@@ -98,9 +107,14 @@ void main() {
' windows:\n'
' pluginClass: WinSamplePlugin\n';
final dynamic pluginYaml = loadYaml(pluginYamlRaw);
final Plugin plugin =
Plugin.fromYaml(_kTestPluginName, _kTestPluginPath, pluginYaml as YamlMap, const <String>[]);
final YamlMap pluginYaml = loadYaml(pluginYamlRaw) as YamlMap;
final Plugin plugin = Plugin.fromYaml(
_kTestPluginName,
_kTestPluginPath,
pluginYaml,
const <String>[],
fileSystem: fileSystem,
);
final AndroidPlugin androidPlugin = plugin.platforms[AndroidPlugin.kConfigKey] as AndroidPlugin;
final IOSPlugin iosPlugin = plugin.platforms[IOSPlugin.kConfigKey] as IOSPlugin;
......@@ -122,7 +136,8 @@ void main() {
expect(windowsPlugin.pluginClass, 'WinSamplePlugin');
});
test('Allow for Dart-only plugins without a pluginClass', () {
testWithoutContext('Plugin parsing allows for Dart-only plugins without a pluginClass', () {
final FileSystem fileSystem = MemoryFileSystem.test();
/// This is currently supported only on macOS, linux, Windows.
const String pluginYamlRaw = 'implements: same_plugin\n' // this should be ignored by the tool
'platforms:\n'
......@@ -133,9 +148,14 @@ void main() {
' windows:\n'
' dartPluginClass: WinSamplePlugin\n';
final dynamic pluginYaml = loadYaml(pluginYamlRaw);
final Plugin plugin =
Plugin.fromYaml(_kTestPluginName, _kTestPluginPath, pluginYaml as YamlMap, const <String>[]);
final YamlMap pluginYaml = loadYaml(pluginYamlRaw) as YamlMap;
final Plugin plugin = Plugin.fromYaml(
_kTestPluginName,
_kTestPluginPath,
pluginYaml,
const <String>[],
fileSystem: fileSystem,
);
final LinuxPlugin linuxPlugin = plugin.platforms[LinuxPlugin.kConfigKey] as LinuxPlugin;
final MacOSPlugin macOSPlugin = plugin.platforms[MacOSPlugin.kConfigKey] as MacOSPlugin;
......@@ -149,20 +169,30 @@ void main() {
expect(windowsPlugin.dartPluginClass, 'WinSamplePlugin');
});
test('Legacy Format and Multi-Platform Format together is not allowed and error message contains plugin name', () {
testWithoutContext('Plugin parsing of legacy format and multi-platform format together is not allowed '
'and fatal error message contains plugin name', () {
final FileSystem fileSystem = MemoryFileSystem.test();
const String pluginYamlRaw = 'androidPackage: com.flutter.dev\n'
'platforms:\n'
' android:\n'
' package: com.flutter.dev\n';
final YamlMap pluginYaml = loadYaml(pluginYamlRaw) as YamlMap;
expect(
() => Plugin.fromYaml(_kTestPluginName, _kTestPluginPath, pluginYaml, const <String>[]),
() => Plugin.fromYaml(
_kTestPluginName,
_kTestPluginPath,
pluginYaml,
const <String>[],
fileSystem: fileSystem,
),
throwsToolExit(message: _kTestPluginName),
);
});
test('A default_package field is allowed', () {
testWithoutContext('Plugin parsing allows a default_package field', () {
final FileSystem fileSystem = MemoryFileSystem.test();
const String pluginYamlRaw =
'platforms:\n'
' android:\n'
......@@ -178,43 +208,67 @@ void main() {
' windows:\n'
' default_package: sample_package_windows\n';
final dynamic pluginYaml = loadYaml(pluginYamlRaw);
final Plugin plugin =
Plugin.fromYaml(_kTestPluginName, _kTestPluginPath, pluginYaml as YamlMap, const <String>[]);
final YamlMap pluginYaml = loadYaml(pluginYamlRaw) as YamlMap;
final Plugin plugin = Plugin.fromYaml(
_kTestPluginName,
_kTestPluginPath,
pluginYaml,
const <String>[],
fileSystem: fileSystem,
);
expect(plugin.platforms, <String, PluginPlatform> {});
expect(plugin.platforms, <String, PluginPlatform>{});
});
test('error on empty plugin', () {
const String pluginYamlRaw = '';
testWithoutContext('Plugin parsing throws a fatal error on an empty plugin', () {
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
final YamlMap pluginYaml = loadYaml('') as YamlMap;
final YamlMap pluginYaml = loadYaml(pluginYamlRaw) as YamlMap;
expect(
() => Plugin.fromYaml(_kTestPluginName, _kTestPluginPath, pluginYaml, const <String>[]),
() => Plugin.fromYaml(
_kTestPluginName,
_kTestPluginPath,
pluginYaml,
const <String>[],
fileSystem: fileSystem,
),
throwsToolExit(message: 'Invalid "plugin" specification.'),
);
});
test('error on empty platforms', () {
testWithoutContext('Plugin parsing throws a fatal error on empty platforms', () {
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
const String pluginYamlRaw = 'platforms:\n';
final YamlMap pluginYaml = loadYaml(pluginYamlRaw) as YamlMap;
expect(
() => Plugin.fromYaml(_kTestPluginName, _kTestPluginPath, pluginYaml, const <String>[]),
() => Plugin.fromYaml(
_kTestPluginName,
_kTestPluginPath,
pluginYaml,
const <String>[],
fileSystem: fileSystem,
),
throwsToolExit(message: 'Invalid "platforms" specification.'),
);
});
test('error on empty platform', () {
test('Plugin parsing throws a fatal error on an empty platform key', () {
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
const String pluginYamlRaw =
'platforms:\n'
' android:\n';
final YamlMap pluginYaml = loadYaml(pluginYamlRaw) as YamlMap;
expect(
() => Plugin.fromYaml(_kTestPluginName, _kTestPluginPath, pluginYaml, const <String>[]),
() => Plugin.fromYaml(
_kTestPluginName,
_kTestPluginPath,
pluginYaml,
const <String>[],
fileSystem: fileSystem,
),
throwsToolExit(message: 'Invalid "android" plugin specification.'),
);
});
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment