2019-11-27 23:04:02 +00:00
|
|
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
2019-09-19 04:42:57 +00:00
|
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
|
|
// found in the LICENSE file.
|
|
|
|
|
2020-01-17 22:43:34 +00:00
|
|
|
import 'dart:convert';
|
|
|
|
|
2019-09-19 04:42:57 +00:00
|
|
|
import 'package:file/file.dart';
|
|
|
|
import 'package:file/memory.dart';
|
2020-04-03 18:51:01 +00:00
|
|
|
import 'package:file_testing/file_testing.dart';
|
2020-10-06 15:32:54 +00:00
|
|
|
import 'package:flutter_tools/src/base/os.dart';
|
|
|
|
import 'package:flutter_tools/src/base/platform.dart';
|
2020-01-17 22:43:34 +00:00
|
|
|
import 'package:flutter_tools/src/base/time.dart';
|
2020-09-15 00:57:11 +00:00
|
|
|
import 'package:flutter_tools/src/base/utils.dart';
|
2019-10-04 13:23:03 +00:00
|
|
|
import 'package:flutter_tools/src/features.dart';
|
2020-06-24 00:38:03 +00:00
|
|
|
import 'package:flutter_tools/src/globals.dart' as globals;
|
2019-10-04 13:23:03 +00:00
|
|
|
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
2019-09-19 04:42:57 +00:00
|
|
|
import 'package:flutter_tools/src/plugins.dart';
|
|
|
|
import 'package:flutter_tools/src/project.dart';
|
2020-01-17 22:43:34 +00:00
|
|
|
import 'package:flutter_tools/src/version.dart';
|
2019-11-22 23:02:20 +00:00
|
|
|
import 'package:meta/meta.dart';
|
2019-09-19 04:42:57 +00:00
|
|
|
import 'package:mockito/mockito.dart';
|
2020-06-24 00:38:03 +00:00
|
|
|
import 'package:yaml/yaml.dart';
|
2019-09-19 04:42:57 +00:00
|
|
|
|
|
|
|
import '../src/common.dart';
|
|
|
|
import '../src/context.dart';
|
2020-06-24 00:38:03 +00:00
|
|
|
import '../src/pubspec_schema.dart';
|
2019-09-19 04:42:57 +00:00
|
|
|
|
|
|
|
void main() {
|
2019-10-29 23:05:13 +00:00
|
|
|
group('plugins', () {
|
|
|
|
FileSystem fs;
|
|
|
|
MockFlutterProject flutterProject;
|
|
|
|
MockIosProject iosProject;
|
|
|
|
MockMacOSProject macosProject;
|
|
|
|
MockAndroidProject androidProject;
|
|
|
|
MockWebProject webProject;
|
2020-01-17 22:43:34 +00:00
|
|
|
MockWindowsProject windowsProject;
|
|
|
|
MockLinuxProject linuxProject;
|
|
|
|
SystemClock mockClock;
|
|
|
|
FlutterVersion mockVersion;
|
2020-09-09 23:08:19 +00:00
|
|
|
// A Windows-style filesystem. This is not populated by default, so tests
|
|
|
|
// using it instead of fs must re-run any necessary setup (e.g.,
|
|
|
|
// setUpProject).
|
|
|
|
FileSystem fsWindows;
|
2019-10-29 23:05:13 +00:00
|
|
|
|
2020-09-09 23:08:19 +00:00
|
|
|
// Adds basic properties to the flutterProject and its subprojects.
|
|
|
|
void setUpProject(FileSystem fileSystem) {
|
2019-10-29 23:05:13 +00:00
|
|
|
flutterProject = MockFlutterProject();
|
2020-09-09 23:08:19 +00:00
|
|
|
when(flutterProject.directory).thenReturn(fileSystem.systemTempDirectory.childDirectory('app'));
|
|
|
|
// TODO(franciscojma): Remove logic for .flutter-plugins once it's deprecated.
|
2019-11-22 23:02:20 +00:00
|
|
|
when(flutterProject.flutterPluginsFile).thenReturn(flutterProject.directory.childFile('.flutter-plugins'));
|
|
|
|
when(flutterProject.flutterPluginsDependenciesFile).thenReturn(flutterProject.directory.childFile('.flutter-plugins-dependencies'));
|
2020-09-09 23:08:19 +00:00
|
|
|
|
2019-10-29 23:05:13 +00:00
|
|
|
iosProject = MockIosProject();
|
|
|
|
when(flutterProject.ios).thenReturn(iosProject);
|
2020-09-09 23:08:19 +00:00
|
|
|
final Directory iosDirectory = flutterProject.directory.childDirectory('ios');
|
2019-10-29 23:05:13 +00:00
|
|
|
when(iosProject.pluginRegistrantHost).thenReturn(flutterProject.directory.childDirectory('Runner'));
|
2020-09-09 23:08:19 +00:00
|
|
|
when(iosProject.podfile).thenReturn(iosDirectory.childFile('Podfile'));
|
|
|
|
when(iosProject.podManifestLock).thenReturn(iosDirectory.childFile('Podfile.lock'));
|
2020-01-17 22:43:34 +00:00
|
|
|
when(iosProject.pluginConfigKey).thenReturn('ios');
|
|
|
|
when(iosProject.existsSync()).thenReturn(false);
|
2020-09-09 23:08:19 +00:00
|
|
|
|
2019-10-29 23:05:13 +00:00
|
|
|
macosProject = MockMacOSProject();
|
|
|
|
when(flutterProject.macos).thenReturn(macosProject);
|
2020-09-09 23:08:19 +00:00
|
|
|
final Directory macosDirectory = flutterProject.directory.childDirectory('macos');
|
|
|
|
when(macosProject.podfile).thenReturn(macosDirectory.childFile('Podfile'));
|
|
|
|
when(macosProject.podManifestLock).thenReturn(macosDirectory.childFile('Podfile.lock'));
|
|
|
|
final Directory macosManagedDirectory = macosDirectory.childDirectory('Flutter');
|
2020-04-03 18:51:01 +00:00
|
|
|
when(macosProject.managedDirectory).thenReturn(macosManagedDirectory);
|
2020-01-17 22:43:34 +00:00
|
|
|
when(macosProject.pluginConfigKey).thenReturn('macos');
|
|
|
|
when(macosProject.existsSync()).thenReturn(false);
|
2020-09-09 23:08:19 +00:00
|
|
|
|
2019-10-29 23:05:13 +00:00
|
|
|
androidProject = MockAndroidProject();
|
|
|
|
when(flutterProject.android).thenReturn(androidProject);
|
2020-09-09 23:08:19 +00:00
|
|
|
final Directory androidDirectory = flutterProject.directory.childDirectory('android');
|
|
|
|
when(androidProject.pluginRegistrantHost).thenReturn(androidDirectory.childDirectory('app'));
|
|
|
|
when(androidProject.hostAppGradleRoot).thenReturn(androidDirectory);
|
2020-01-17 22:43:34 +00:00
|
|
|
when(androidProject.pluginConfigKey).thenReturn('android');
|
|
|
|
when(androidProject.existsSync()).thenReturn(false);
|
2020-09-09 23:08:19 +00:00
|
|
|
|
2019-10-29 23:05:13 +00:00
|
|
|
webProject = MockWebProject();
|
|
|
|
when(flutterProject.web).thenReturn(webProject);
|
|
|
|
when(webProject.libDirectory).thenReturn(flutterProject.directory.childDirectory('lib'));
|
|
|
|
when(webProject.existsSync()).thenReturn(true);
|
2020-01-17 22:43:34 +00:00
|
|
|
when(webProject.pluginConfigKey).thenReturn('web');
|
|
|
|
when(webProject.existsSync()).thenReturn(false);
|
2020-09-09 23:08:19 +00:00
|
|
|
|
2020-01-17 22:43:34 +00:00
|
|
|
windowsProject = MockWindowsProject();
|
|
|
|
when(flutterProject.windows).thenReturn(windowsProject);
|
|
|
|
when(windowsProject.pluginConfigKey).thenReturn('windows');
|
2020-02-13 20:53:28 +00:00
|
|
|
final Directory windowsManagedDirectory = flutterProject.directory.childDirectory('windows').childDirectory('flutter');
|
|
|
|
when(windowsProject.managedDirectory).thenReturn(windowsManagedDirectory);
|
2020-07-06 19:59:16 +00:00
|
|
|
when(windowsProject.cmakeFile).thenReturn(windowsManagedDirectory.parent.childFile('CMakeLists.txt'));
|
|
|
|
when(windowsProject.generatedPluginCmakeFile).thenReturn(windowsManagedDirectory.childFile('generated_plugins.mk'));
|
2020-02-13 20:53:28 +00:00
|
|
|
when(windowsProject.pluginSymlinkDirectory).thenReturn(windowsManagedDirectory.childDirectory('ephemeral').childDirectory('.plugin_symlinks'));
|
2020-01-17 22:43:34 +00:00
|
|
|
when(windowsProject.existsSync()).thenReturn(false);
|
2020-09-09 23:08:19 +00:00
|
|
|
|
2020-01-17 22:43:34 +00:00
|
|
|
linuxProject = MockLinuxProject();
|
|
|
|
when(flutterProject.linux).thenReturn(linuxProject);
|
|
|
|
when(linuxProject.pluginConfigKey).thenReturn('linux');
|
2020-02-27 17:45:22 +00:00
|
|
|
final Directory linuxManagedDirectory = flutterProject.directory.childDirectory('linux').childDirectory('flutter');
|
|
|
|
final Directory linuxEphemeralDirectory = linuxManagedDirectory.childDirectory('ephemeral');
|
|
|
|
when(linuxProject.managedDirectory).thenReturn(linuxManagedDirectory);
|
|
|
|
when(linuxProject.ephemeralDirectory).thenReturn(linuxEphemeralDirectory);
|
|
|
|
when(linuxProject.pluginSymlinkDirectory).thenReturn(linuxEphemeralDirectory.childDirectory('.plugin_symlinks'));
|
2020-05-16 22:07:34 +00:00
|
|
|
when(linuxProject.cmakeFile).thenReturn(linuxManagedDirectory.parent.childFile('CMakeLists.txt'));
|
|
|
|
when(linuxProject.generatedPluginCmakeFile).thenReturn(linuxManagedDirectory.childFile('generated_plugins.mk'));
|
2020-01-17 22:43:34 +00:00
|
|
|
when(linuxProject.existsSync()).thenReturn(false);
|
2020-09-09 23:08:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
setUp(() async {
|
2020-09-30 00:50:33 +00:00
|
|
|
fs = MemoryFileSystem.test();
|
2020-09-09 23:08:19 +00:00
|
|
|
fsWindows = MemoryFileSystem(style: FileSystemStyle.windows);
|
|
|
|
mockClock = MockClock();
|
|
|
|
mockVersion = MockFlutterVersion();
|
|
|
|
|
|
|
|
// Add basic properties to the Flutter project and subprojects
|
|
|
|
setUpProject(fs);
|
|
|
|
flutterProject.directory.childFile('.packages').createSync(recursive: true);
|
2020-01-17 22:43:34 +00:00
|
|
|
|
|
|
|
when(mockClock.now()).thenAnswer(
|
|
|
|
(Invocation _) => DateTime(1970, 1, 1)
|
|
|
|
);
|
|
|
|
when(mockVersion.frameworkVersion).thenAnswer(
|
|
|
|
(Invocation _) => '1.0.0'
|
|
|
|
);
|
2019-09-19 04:42:57 +00:00
|
|
|
});
|
|
|
|
|
2020-09-15 00:57:11 +00:00
|
|
|
// Makes fake plugin packages for each plugin, adds them to flutterProject,
|
|
|
|
// and returns their directories.
|
|
|
|
//
|
|
|
|
// If an entry contains a path separator, it will be treated as a path for
|
|
|
|
// the location of the package, with the name being the last component.
|
|
|
|
// Otherwise it will be treated as a name, and put in a default location
|
|
|
|
// (a fake pub cache).
|
|
|
|
List<Directory> createFakePlugins(FileSystem fileSystem, List<String> pluginNamesOrPaths) {
|
|
|
|
const String pluginYamlTemplate = '''
|
2019-10-29 23:05:13 +00:00
|
|
|
flutter:
|
|
|
|
plugin:
|
|
|
|
platforms:
|
|
|
|
ios:
|
2020-09-15 00:57:11 +00:00
|
|
|
pluginClass: PLUGIN_CLASS
|
2020-01-17 22:43:34 +00:00
|
|
|
macos:
|
2020-09-15 00:57:11 +00:00
|
|
|
pluginClass: PLUGIN_CLASS
|
2020-01-17 22:43:34 +00:00
|
|
|
windows:
|
2020-09-15 00:57:11 +00:00
|
|
|
pluginClass: PLUGIN_CLASS
|
2020-01-17 22:43:34 +00:00
|
|
|
linux:
|
2020-09-15 00:57:11 +00:00
|
|
|
pluginClass: PLUGIN_CLASS
|
2020-01-17 22:43:34 +00:00
|
|
|
web:
|
2020-09-15 00:57:11 +00:00
|
|
|
pluginClass: PLUGIN_CLASS
|
|
|
|
fileName: lib/PLUGIN_CLASS.dart
|
2020-01-17 22:43:34 +00:00
|
|
|
android:
|
2020-09-15 00:57:11 +00:00
|
|
|
pluginClass: PLUGIN_CLASS
|
2020-01-17 22:43:34 +00:00
|
|
|
package: AndroidPackage
|
2020-06-24 00:38:03 +00:00
|
|
|
''';
|
2020-09-09 23:08:19 +00:00
|
|
|
|
2020-09-15 00:57:11 +00:00
|
|
|
final List<Directory> directories = <Directory>[];
|
|
|
|
final Directory fakePubCache = fileSystem.systemTempDirectory.childDirectory('cache');
|
|
|
|
final File packagesFile = flutterProject.directory.childFile('.packages')
|
|
|
|
..createSync(recursive: true);
|
|
|
|
for (final String nameOrPath in pluginNamesOrPaths) {
|
|
|
|
final String name = fileSystem.path.basename(nameOrPath);
|
|
|
|
final Directory pluginDirectory = (nameOrPath == name)
|
|
|
|
? fakePubCache.childDirectory(name)
|
|
|
|
: fileSystem.directory(nameOrPath);
|
|
|
|
packagesFile.writeAsStringSync(
|
|
|
|
'$name:file://${pluginDirectory.childFile('lib').uri}\n',
|
|
|
|
mode: FileMode.writeOnlyAppend);
|
|
|
|
pluginDirectory.childFile('pubspec.yaml')
|
|
|
|
..createSync(recursive: true)
|
|
|
|
..writeAsStringSync(pluginYamlTemplate.replaceAll('PLUGIN_CLASS', toTitleCase(camelCase(name))));
|
|
|
|
directories.add(pluginDirectory);
|
|
|
|
}
|
|
|
|
return directories;
|
|
|
|
}
|
|
|
|
|
2020-09-09 23:08:19 +00:00
|
|
|
// Makes a fake plugin package, adds it to flutterProject, and returns its directory.
|
|
|
|
Directory createFakePlugin(FileSystem fileSystem) {
|
2020-09-15 00:57:11 +00:00
|
|
|
return createFakePlugins(fileSystem, <String>['some_plugin'])[0];
|
2019-10-29 23:05:13 +00:00
|
|
|
}
|
|
|
|
|
2019-11-11 23:56:43 +00:00
|
|
|
void createNewJavaPlugin1() {
|
|
|
|
final Directory pluginUsingJavaAndNewEmbeddingDir =
|
|
|
|
fs.systemTempDirectory.createTempSync('flutter_plugin_using_java_and_new_embedding_dir.');
|
|
|
|
pluginUsingJavaAndNewEmbeddingDir
|
|
|
|
.childFile('pubspec.yaml')
|
|
|
|
.writeAsStringSync('''
|
|
|
|
flutter:
|
|
|
|
plugin:
|
|
|
|
androidPackage: plugin1
|
|
|
|
pluginClass: UseNewEmbedding
|
|
|
|
''');
|
|
|
|
pluginUsingJavaAndNewEmbeddingDir
|
|
|
|
.childDirectory('android')
|
|
|
|
.childDirectory('src')
|
|
|
|
.childDirectory('main')
|
|
|
|
.childDirectory('java')
|
|
|
|
.childDirectory('plugin1')
|
|
|
|
.childFile('UseNewEmbedding.java')
|
|
|
|
..createSync(recursive: true)
|
|
|
|
..writeAsStringSync('import io.flutter.embedding.engine.plugins.FlutterPlugin;');
|
|
|
|
|
|
|
|
flutterProject.directory
|
|
|
|
.childFile('.packages')
|
|
|
|
.writeAsStringSync(
|
|
|
|
'plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString()}\n',
|
|
|
|
mode: FileMode.append,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-03-06 01:14:27 +00:00
|
|
|
Directory createPluginWithInvalidAndroidPackage() {
|
2020-01-29 01:48:26 +00:00
|
|
|
final Directory pluginUsingJavaAndNewEmbeddingDir =
|
|
|
|
fs.systemTempDirectory.createTempSync('flutter_plugin_invalid_package.');
|
|
|
|
pluginUsingJavaAndNewEmbeddingDir
|
|
|
|
.childFile('pubspec.yaml')
|
|
|
|
.writeAsStringSync('''
|
|
|
|
flutter:
|
|
|
|
plugin:
|
|
|
|
androidPackage: plugin1.invalid
|
|
|
|
pluginClass: UseNewEmbedding
|
|
|
|
''');
|
|
|
|
pluginUsingJavaAndNewEmbeddingDir
|
|
|
|
.childDirectory('android')
|
|
|
|
.childDirectory('src')
|
|
|
|
.childDirectory('main')
|
|
|
|
.childDirectory('java')
|
|
|
|
.childDirectory('plugin1')
|
|
|
|
.childDirectory('correct')
|
|
|
|
.childFile('UseNewEmbedding.java')
|
|
|
|
..createSync(recursive: true)
|
|
|
|
..writeAsStringSync('import io.flutter.embedding.engine.plugins.FlutterPlugin;');
|
|
|
|
|
|
|
|
flutterProject.directory
|
|
|
|
.childFile('.packages')
|
|
|
|
.writeAsStringSync(
|
|
|
|
'plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString()}\n',
|
|
|
|
mode: FileMode.append,
|
|
|
|
);
|
2020-03-06 01:14:27 +00:00
|
|
|
return pluginUsingJavaAndNewEmbeddingDir;
|
2020-01-29 01:48:26 +00:00
|
|
|
}
|
|
|
|
|
2019-11-11 23:56:43 +00:00
|
|
|
void createNewKotlinPlugin2() {
|
|
|
|
final Directory pluginUsingKotlinAndNewEmbeddingDir =
|
|
|
|
fs.systemTempDirectory.createTempSync('flutter_plugin_using_kotlin_and_new_embedding_dir.');
|
|
|
|
pluginUsingKotlinAndNewEmbeddingDir
|
|
|
|
.childFile('pubspec.yaml')
|
|
|
|
.writeAsStringSync('''
|
|
|
|
flutter:
|
|
|
|
plugin:
|
|
|
|
androidPackage: plugin2
|
|
|
|
pluginClass: UseNewEmbedding
|
|
|
|
''');
|
|
|
|
pluginUsingKotlinAndNewEmbeddingDir
|
|
|
|
.childDirectory('android')
|
|
|
|
.childDirectory('src')
|
|
|
|
.childDirectory('main')
|
|
|
|
.childDirectory('kotlin')
|
|
|
|
.childDirectory('plugin2')
|
|
|
|
.childFile('UseNewEmbedding.kt')
|
|
|
|
..createSync(recursive: true)
|
|
|
|
..writeAsStringSync('import io.flutter.embedding.engine.plugins.FlutterPlugin');
|
|
|
|
|
|
|
|
flutterProject.directory
|
|
|
|
.childFile('.packages')
|
|
|
|
.writeAsStringSync(
|
|
|
|
'plugin2:${pluginUsingKotlinAndNewEmbeddingDir.childDirectory('lib').uri.toString()}\n',
|
|
|
|
mode: FileMode.append,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-01-14 05:30:42 +00:00
|
|
|
void createOldJavaPlugin(String pluginName) {
|
2019-11-11 23:56:43 +00:00
|
|
|
final Directory pluginUsingOldEmbeddingDir =
|
|
|
|
fs.systemTempDirectory.createTempSync('flutter_plugin_using_old_embedding_dir.');
|
|
|
|
pluginUsingOldEmbeddingDir
|
|
|
|
.childFile('pubspec.yaml')
|
|
|
|
.writeAsStringSync('''
|
|
|
|
flutter:
|
|
|
|
plugin:
|
2020-01-14 05:30:42 +00:00
|
|
|
androidPackage: $pluginName
|
2019-11-11 23:56:43 +00:00
|
|
|
pluginClass: UseOldEmbedding
|
|
|
|
''');
|
|
|
|
pluginUsingOldEmbeddingDir
|
|
|
|
.childDirectory('android')
|
|
|
|
.childDirectory('src')
|
|
|
|
.childDirectory('main')
|
|
|
|
.childDirectory('java')
|
2020-01-14 05:30:42 +00:00
|
|
|
.childDirectory(pluginName)
|
2019-11-11 23:56:43 +00:00
|
|
|
.childFile('UseOldEmbedding.java')
|
2020-03-05 07:03:26 +00:00
|
|
|
.createSync(recursive: true);
|
2019-11-11 23:56:43 +00:00
|
|
|
|
|
|
|
flutterProject.directory
|
|
|
|
.childFile('.packages')
|
|
|
|
.writeAsStringSync(
|
2020-01-14 05:30:42 +00:00
|
|
|
'$pluginName:${pluginUsingOldEmbeddingDir.childDirectory('lib').uri.toString()}\n',
|
2019-11-11 23:56:43 +00:00
|
|
|
mode: FileMode.append,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
void createDualSupportJavaPlugin4() {
|
|
|
|
final Directory pluginUsingJavaAndNewEmbeddingDir =
|
|
|
|
fs.systemTempDirectory.createTempSync('flutter_plugin_using_java_and_new_embedding_dir.');
|
|
|
|
pluginUsingJavaAndNewEmbeddingDir
|
|
|
|
.childFile('pubspec.yaml')
|
|
|
|
.writeAsStringSync('''
|
|
|
|
flutter:
|
|
|
|
plugin:
|
|
|
|
androidPackage: plugin4
|
|
|
|
pluginClass: UseBothEmbedding
|
|
|
|
''');
|
|
|
|
pluginUsingJavaAndNewEmbeddingDir
|
|
|
|
.childDirectory('android')
|
|
|
|
.childDirectory('src')
|
|
|
|
.childDirectory('main')
|
|
|
|
.childDirectory('java')
|
|
|
|
.childDirectory('plugin4')
|
|
|
|
.childFile('UseBothEmbedding.java')
|
|
|
|
..createSync(recursive: true)
|
|
|
|
..writeAsStringSync(
|
|
|
|
'import io.flutter.embedding.engine.plugins.FlutterPlugin;\n'
|
|
|
|
'PluginRegistry\n'
|
|
|
|
'registerWith(Irrelevant registrar)\n'
|
|
|
|
);
|
|
|
|
|
|
|
|
flutterProject.directory
|
|
|
|
.childFile('.packages')
|
|
|
|
.writeAsStringSync(
|
|
|
|
'plugin4:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString()}',
|
|
|
|
mode: FileMode.append,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-03-06 01:14:27 +00:00
|
|
|
Directory createPluginWithDependencies({
|
2019-11-22 23:02:20 +00:00
|
|
|
@required String name,
|
|
|
|
@required List<String> dependencies,
|
|
|
|
}) {
|
|
|
|
assert(name != null);
|
|
|
|
assert(dependencies != null);
|
|
|
|
|
|
|
|
final Directory pluginDirectory = fs.systemTempDirectory.createTempSync('plugin.');
|
|
|
|
pluginDirectory
|
|
|
|
.childFile('pubspec.yaml')
|
|
|
|
.writeAsStringSync('''
|
|
|
|
name: $name
|
|
|
|
flutter:
|
|
|
|
plugin:
|
|
|
|
androidPackage: plugin2
|
|
|
|
pluginClass: UseNewEmbedding
|
|
|
|
dependencies:
|
|
|
|
''');
|
2020-01-07 15:32:04 +00:00
|
|
|
for (final String dependency in dependencies) {
|
2019-11-22 23:02:20 +00:00
|
|
|
pluginDirectory
|
|
|
|
.childFile('pubspec.yaml')
|
|
|
|
.writeAsStringSync(' $dependency:\n', mode: FileMode.append);
|
|
|
|
}
|
|
|
|
flutterProject.directory
|
|
|
|
.childFile('.packages')
|
|
|
|
.writeAsStringSync(
|
|
|
|
'$name:${pluginDirectory.childDirectory('lib').uri.toString()}\n',
|
|
|
|
mode: FileMode.append,
|
|
|
|
);
|
2020-03-06 01:14:27 +00:00
|
|
|
return pluginDirectory;
|
2019-11-22 23:02:20 +00:00
|
|
|
}
|
|
|
|
|
2019-10-29 23:05:13 +00:00
|
|
|
// Creates the files that would indicate that pod install has run for the
|
|
|
|
// given project.
|
|
|
|
void simulatePodInstallRun(XcodeBasedProject project) {
|
|
|
|
project.podManifestLock.createSync(recursive: true);
|
|
|
|
}
|
|
|
|
|
|
|
|
group('refreshPlugins', () {
|
2020-04-22 02:55:15 +00:00
|
|
|
testUsingContext('Refreshing the plugin list is a no-op when the plugins list stays empty', () async {
|
|
|
|
await refreshPluginsList(flutterProject);
|
|
|
|
|
2019-10-29 23:05:13 +00:00
|
|
|
expect(flutterProject.flutterPluginsFile.existsSync(), false);
|
2019-11-22 23:02:20 +00:00
|
|
|
expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), false);
|
2019-10-29 23:05:13 +00:00
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
});
|
|
|
|
|
2020-04-22 02:55:15 +00:00
|
|
|
testUsingContext('Refreshing the plugin list deletes the plugin file when there were plugins but no longer are', () async {
|
2019-10-29 23:05:13 +00:00
|
|
|
flutterProject.flutterPluginsFile.createSync();
|
2020-01-17 22:43:34 +00:00
|
|
|
flutterProject.flutterPluginsDependenciesFile.createSync();
|
|
|
|
|
2020-04-22 02:55:15 +00:00
|
|
|
await refreshPluginsList(flutterProject);
|
|
|
|
|
2019-10-29 23:05:13 +00:00
|
|
|
expect(flutterProject.flutterPluginsFile.existsSync(), false);
|
2019-11-22 23:02:20 +00:00
|
|
|
expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), false);
|
2019-10-29 23:05:13 +00:00
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
});
|
|
|
|
|
2020-04-22 02:55:15 +00:00
|
|
|
testUsingContext('Refreshing the plugin list creates a plugin directory when there are plugins', () async {
|
2020-09-09 23:08:19 +00:00
|
|
|
createFakePlugin(fs);
|
2020-01-17 22:43:34 +00:00
|
|
|
when(iosProject.existsSync()).thenReturn(true);
|
|
|
|
|
2020-04-22 02:55:15 +00:00
|
|
|
await refreshPluginsList(flutterProject);
|
|
|
|
|
2019-10-29 23:05:13 +00:00
|
|
|
expect(flutterProject.flutterPluginsFile.existsSync(), true);
|
2019-11-22 23:02:20 +00:00
|
|
|
expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), true);
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
});
|
|
|
|
|
2020-04-22 02:55:15 +00:00
|
|
|
testUsingContext(
|
|
|
|
'Refreshing the plugin list modifies .flutter-plugins '
|
|
|
|
'and .flutter-plugins-dependencies when there are plugins', () async {
|
2020-03-06 01:14:27 +00:00
|
|
|
final Directory pluginA = createPluginWithDependencies(name: 'plugin-a', dependencies: const <String>['plugin-b', 'plugin-c', 'random-package']);
|
|
|
|
final Directory pluginB = createPluginWithDependencies(name: 'plugin-b', dependencies: const <String>['plugin-c']);
|
|
|
|
final Directory pluginC = createPluginWithDependencies(name: 'plugin-c', dependencies: const <String>[]);
|
2020-01-17 22:43:34 +00:00
|
|
|
when(iosProject.existsSync()).thenReturn(true);
|
|
|
|
|
|
|
|
final DateTime dateCreated = DateTime(1970, 1, 1);
|
|
|
|
when(mockClock.now()).thenAnswer(
|
|
|
|
(Invocation _) => dateCreated
|
|
|
|
);
|
|
|
|
const String version = '1.0.0';
|
|
|
|
when(mockVersion.frameworkVersion).thenAnswer(
|
|
|
|
(Invocation _) => version
|
|
|
|
);
|
2019-11-22 23:02:20 +00:00
|
|
|
|
2020-04-22 02:55:15 +00:00
|
|
|
await refreshPluginsList(flutterProject);
|
2019-11-22 23:02:20 +00:00
|
|
|
|
2020-01-17 22:43:34 +00:00
|
|
|
// Verify .flutter-plugins-dependencies is configured correctly.
|
2019-11-22 23:02:20 +00:00
|
|
|
expect(flutterProject.flutterPluginsFile.existsSync(), true);
|
|
|
|
expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), true);
|
|
|
|
expect(flutterProject.flutterPluginsFile.readAsStringSync(),
|
2019-11-25 19:47:20 +00:00
|
|
|
'# This is a generated file; do not edit or check into version control.\n'
|
2020-03-06 01:14:27 +00:00
|
|
|
'plugin-a=${pluginA.path}/\n'
|
|
|
|
'plugin-b=${pluginB.path}/\n'
|
|
|
|
'plugin-c=${pluginC.path}/\n'
|
2019-11-22 23:02:20 +00:00
|
|
|
''
|
|
|
|
);
|
2020-01-17 22:43:34 +00:00
|
|
|
|
|
|
|
final String pluginsString = flutterProject.flutterPluginsDependenciesFile.readAsStringSync();
|
|
|
|
final Map<String, dynamic> jsonContent = json.decode(pluginsString) as Map<String, dynamic>;
|
|
|
|
expect(jsonContent['info'], 'This is a generated file; do not edit or check into version control.');
|
|
|
|
|
|
|
|
final Map<String, dynamic> plugins = jsonContent['plugins'] as Map<String, dynamic>;
|
|
|
|
final List<dynamic> expectedPlugins = <dynamic>[
|
|
|
|
<String, dynamic> {
|
|
|
|
'name': 'plugin-a',
|
2020-03-06 01:14:27 +00:00
|
|
|
'path': '${pluginA.path}/',
|
2020-01-17 22:43:34 +00:00
|
|
|
'dependencies': <String>[
|
|
|
|
'plugin-b',
|
|
|
|
'plugin-c'
|
|
|
|
]
|
|
|
|
},
|
|
|
|
<String, dynamic> {
|
|
|
|
'name': 'plugin-b',
|
2020-03-06 01:14:27 +00:00
|
|
|
'path': '${pluginB.path}/',
|
2020-01-17 22:43:34 +00:00
|
|
|
'dependencies': <String>[
|
|
|
|
'plugin-c'
|
|
|
|
]
|
|
|
|
},
|
|
|
|
<String, dynamic> {
|
|
|
|
'name': 'plugin-c',
|
2020-03-06 01:14:27 +00:00
|
|
|
'path': '${pluginC.path}/',
|
2020-01-17 22:43:34 +00:00
|
|
|
'dependencies': <String>[]
|
|
|
|
},
|
|
|
|
];
|
|
|
|
expect(plugins['ios'], expectedPlugins);
|
|
|
|
expect(plugins['android'], expectedPlugins);
|
|
|
|
expect(plugins['macos'], <dynamic>[]);
|
|
|
|
expect(plugins['windows'], <dynamic>[]);
|
|
|
|
expect(plugins['linux'], <dynamic>[]);
|
|
|
|
expect(plugins['web'], <dynamic>[]);
|
|
|
|
|
|
|
|
final List<dynamic> expectedDependencyGraph = <dynamic>[
|
|
|
|
<String, dynamic> {
|
|
|
|
'name': 'plugin-a',
|
|
|
|
'dependencies': <String>[
|
|
|
|
'plugin-b',
|
|
|
|
'plugin-c'
|
|
|
|
]
|
|
|
|
},
|
|
|
|
<String, dynamic> {
|
|
|
|
'name': 'plugin-b',
|
|
|
|
'dependencies': <String>[
|
|
|
|
'plugin-c'
|
|
|
|
]
|
|
|
|
},
|
|
|
|
<String, dynamic> {
|
|
|
|
'name': 'plugin-c',
|
|
|
|
'dependencies': <String>[]
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
expect(jsonContent['dependencyGraph'], expectedDependencyGraph);
|
|
|
|
expect(jsonContent['date_created'], dateCreated.toString());
|
|
|
|
expect(jsonContent['version'], version);
|
|
|
|
|
|
|
|
// Make sure tests are updated if a new object is added/removed.
|
|
|
|
final List<String> expectedKeys = <String>[
|
|
|
|
'info',
|
|
|
|
'plugins',
|
|
|
|
'dependencyGraph',
|
|
|
|
'date_created',
|
|
|
|
'version',
|
|
|
|
];
|
|
|
|
expect(jsonContent.keys, expectedKeys);
|
2019-10-29 23:05:13 +00:00
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
2020-01-17 22:43:34 +00:00
|
|
|
SystemClock: () => mockClock,
|
|
|
|
FlutterVersion: () => mockVersion
|
2019-10-29 23:05:13 +00:00
|
|
|
});
|
|
|
|
|
2020-04-22 02:55:15 +00:00
|
|
|
testUsingContext('Changes to the plugin list invalidates the Cocoapod lockfiles', () async {
|
2019-10-29 23:05:13 +00:00
|
|
|
simulatePodInstallRun(iosProject);
|
|
|
|
simulatePodInstallRun(macosProject);
|
2020-09-09 23:08:19 +00:00
|
|
|
createFakePlugin(fs);
|
2019-10-29 23:05:13 +00:00
|
|
|
when(iosProject.existsSync()).thenReturn(true);
|
|
|
|
when(macosProject.existsSync()).thenReturn(true);
|
2020-04-22 02:55:15 +00:00
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await refreshPluginsList(flutterProject, iosPlatform: true, macOSPlatform: true);
|
2019-10-29 23:05:13 +00:00
|
|
|
expect(iosProject.podManifestLock.existsSync(), false);
|
|
|
|
expect(macosProject.podManifestLock.existsSync(), false);
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
2020-01-17 22:43:34 +00:00
|
|
|
SystemClock: () => mockClock,
|
|
|
|
FlutterVersion: () => mockVersion
|
|
|
|
});
|
|
|
|
|
2020-04-22 02:55:15 +00:00
|
|
|
testUsingContext('No changes to the plugin list does not invalidate the Cocoapod lockfiles', () async {
|
2020-09-09 23:08:19 +00:00
|
|
|
createFakePlugin(fs);
|
2020-01-17 22:43:34 +00:00
|
|
|
when(iosProject.existsSync()).thenReturn(true);
|
|
|
|
when(macosProject.existsSync()).thenReturn(true);
|
|
|
|
|
|
|
|
// First call will create the .flutter-plugins-dependencies and the legacy .flutter-plugins file.
|
|
|
|
// Since there was no plugins list, the lock files will be invalidated.
|
|
|
|
// The second call is where the plugins list is compared to the existing one, and if there is no change,
|
|
|
|
// the podfiles shouldn't be invalidated.
|
2020-10-19 21:17:43 +00:00
|
|
|
await refreshPluginsList(flutterProject, iosPlatform: true, macOSPlatform: true);
|
2020-01-17 22:43:34 +00:00
|
|
|
simulatePodInstallRun(iosProject);
|
|
|
|
simulatePodInstallRun(macosProject);
|
|
|
|
|
2020-04-22 02:55:15 +00:00
|
|
|
await refreshPluginsList(flutterProject);
|
2020-01-17 22:43:34 +00:00
|
|
|
expect(iosProject.podManifestLock.existsSync(), true);
|
|
|
|
expect(macosProject.podManifestLock.existsSync(), true);
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
SystemClock: () => mockClock,
|
|
|
|
FlutterVersion: () => mockVersion
|
2019-10-29 23:05:13 +00:00
|
|
|
});
|
2019-09-19 04:42:57 +00:00
|
|
|
});
|
|
|
|
|
2019-10-29 23:05:13 +00:00
|
|
|
group('injectPlugins', () {
|
|
|
|
MockXcodeProjectInterpreter xcodeProjectInterpreter;
|
|
|
|
|
|
|
|
setUp(() {
|
|
|
|
xcodeProjectInterpreter = MockXcodeProjectInterpreter();
|
|
|
|
when(xcodeProjectInterpreter.isInstalled).thenReturn(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
testUsingContext('Registrant uses old embedding in app project', () async {
|
|
|
|
when(flutterProject.isModule).thenReturn(false);
|
2019-11-05 20:38:42 +00:00
|
|
|
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
|
2019-10-29 23:05:13 +00:00
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, androidPlatform: true);
|
2019-10-29 23:05:13 +00:00
|
|
|
|
|
|
|
final File registrant = flutterProject.directory
|
|
|
|
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
|
|
|
|
.childFile('GeneratedPluginRegistrant.java');
|
|
|
|
|
|
|
|
expect(registrant.existsSync(), isTrue);
|
|
|
|
expect(registrant.readAsStringSync(), contains('package io.flutter.plugins'));
|
|
|
|
expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
|
2019-11-11 23:56:43 +00:00
|
|
|
expect(registrant.readAsStringSync(), contains('public static void registerWith(PluginRegistry registry)'));
|
2019-10-29 23:05:13 +00:00
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
});
|
|
|
|
|
|
|
|
testUsingContext('Registrant uses new embedding if app uses new embedding', () async {
|
|
|
|
when(flutterProject.isModule).thenReturn(false);
|
2019-11-05 20:38:42 +00:00
|
|
|
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
|
2019-10-29 23:05:13 +00:00
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, androidPlatform: true);
|
2019-10-29 23:05:13 +00:00
|
|
|
|
|
|
|
final File registrant = flutterProject.directory
|
2019-11-01 23:58:26 +00:00
|
|
|
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
|
2019-10-29 23:05:13 +00:00
|
|
|
.childFile('GeneratedPluginRegistrant.java');
|
|
|
|
|
|
|
|
expect(registrant.existsSync(), isTrue);
|
2019-11-01 23:58:26 +00:00
|
|
|
expect(registrant.readAsStringSync(), contains('package io.flutter.plugins'));
|
2019-10-29 23:05:13 +00:00
|
|
|
expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
|
2019-11-11 23:56:43 +00:00
|
|
|
expect(registrant.readAsStringSync(), contains('public static void registerWith(@NonNull FlutterEngine flutterEngine)'));
|
2019-10-29 23:05:13 +00:00
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
});
|
|
|
|
|
|
|
|
testUsingContext('Registrant uses shim for plugins using old embedding if app uses new embedding', () async {
|
|
|
|
when(flutterProject.isModule).thenReturn(false);
|
2019-11-05 20:38:42 +00:00
|
|
|
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
|
2019-10-29 23:05:13 +00:00
|
|
|
|
2019-11-11 23:56:43 +00:00
|
|
|
createNewJavaPlugin1();
|
|
|
|
createNewKotlinPlugin2();
|
2020-01-14 05:30:42 +00:00
|
|
|
createOldJavaPlugin('plugin3');
|
2019-10-04 13:23:03 +00:00
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, androidPlatform: true);
|
2019-10-29 23:05:13 +00:00
|
|
|
|
|
|
|
final File registrant = flutterProject.directory
|
2019-11-01 23:58:26 +00:00
|
|
|
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
|
2019-10-29 23:05:13 +00:00
|
|
|
.childFile('GeneratedPluginRegistrant.java');
|
|
|
|
|
|
|
|
expect(registrant.readAsStringSync(),
|
|
|
|
contains('flutterEngine.getPlugins().add(new plugin1.UseNewEmbedding());'));
|
|
|
|
expect(registrant.readAsStringSync(),
|
|
|
|
contains('flutterEngine.getPlugins().add(new plugin2.UseNewEmbedding());'));
|
|
|
|
expect(registrant.readAsStringSync(),
|
|
|
|
contains('plugin3.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin3.UseOldEmbedding"));'));
|
|
|
|
|
2019-11-11 23:56:43 +00:00
|
|
|
// There should be no warning message
|
|
|
|
expect(testLogger.statusText, isNot(contains('go/android-plugin-migration')));
|
2019-10-29 23:05:13 +00:00
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
|
|
|
|
});
|
|
|
|
|
2019-11-07 16:24:19 +00:00
|
|
|
testUsingContext('exits the tool if an app uses the v1 embedding and a plugin only supports the v2 embedding', () async {
|
|
|
|
when(flutterProject.isModule).thenReturn(false);
|
|
|
|
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
|
|
|
|
|
2019-11-11 23:56:43 +00:00
|
|
|
createNewJavaPlugin1();
|
|
|
|
|
2019-11-07 16:24:19 +00:00
|
|
|
await expectLater(
|
|
|
|
() async {
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, androidPlatform: true);
|
2019-11-07 16:24:19 +00:00
|
|
|
},
|
|
|
|
throwsToolExit(
|
|
|
|
message: 'The plugin `plugin1` requires your app to be migrated to the Android embedding v2. '
|
|
|
|
'Follow the steps on https://flutter.dev/go/android-project-migration and re-run this command.'
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
|
|
|
|
});
|
|
|
|
|
2020-01-29 01:48:26 +00:00
|
|
|
// Issue: https://github.com/flutter/flutter/issues/47803
|
|
|
|
testUsingContext('exits the tool if a plugin sets an invalid android package in pubspec.yaml', () async {
|
|
|
|
when(flutterProject.isModule).thenReturn(false);
|
|
|
|
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
|
|
|
|
|
2020-03-06 01:14:27 +00:00
|
|
|
final Directory pluginDir = createPluginWithInvalidAndroidPackage();
|
2020-01-29 01:48:26 +00:00
|
|
|
|
|
|
|
await expectLater(
|
|
|
|
() async {
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, androidPlatform: true);
|
2020-01-29 01:48:26 +00:00
|
|
|
},
|
|
|
|
throwsToolExit(
|
2020-02-11 19:58:27 +00:00
|
|
|
message: "The plugin `plugin1` doesn't have a main class defined in "
|
2020-03-06 01:14:27 +00:00
|
|
|
'${pluginDir.path}/android/src/main/java/plugin1/invalid/UseNewEmbedding.java or '
|
|
|
|
'${pluginDir.path}/android/src/main/kotlin/plugin1/invalid/UseNewEmbedding.kt. '
|
2020-02-11 19:58:27 +00:00
|
|
|
"This is likely to due to an incorrect `androidPackage: plugin1.invalid` or `mainClass` entry in the plugin's pubspec.yaml.\n"
|
2020-01-29 01:48:26 +00:00
|
|
|
'If you are the author of this plugin, fix the `androidPackage` entry or move the main class to any of locations used above. '
|
|
|
|
'Otherwise, please contact the author of this plugin and consider using a different plugin in the meanwhile.',
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
|
|
|
|
});
|
|
|
|
|
2020-08-20 05:32:08 +00:00
|
|
|
testUsingContext('old embedding app uses a plugin that supports v1 and v2 embedding works', () async {
|
2019-11-07 16:24:19 +00:00
|
|
|
when(flutterProject.isModule).thenReturn(false);
|
|
|
|
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
|
|
|
|
|
2019-11-11 23:56:43 +00:00
|
|
|
createDualSupportJavaPlugin4();
|
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, androidPlatform: true);
|
2019-11-07 16:24:19 +00:00
|
|
|
|
|
|
|
final File registrant = flutterProject.directory
|
|
|
|
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
|
|
|
|
.childFile('GeneratedPluginRegistrant.java');
|
|
|
|
|
|
|
|
expect(registrant.existsSync(), isTrue);
|
|
|
|
expect(registrant.readAsStringSync(), contains('package io.flutter.plugins'));
|
|
|
|
expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
|
2019-11-11 23:56:43 +00:00
|
|
|
expect(registrant.readAsStringSync(),
|
|
|
|
contains('UseBothEmbedding.registerWith(registry.registrarFor("plugin4.UseBothEmbedding"));'));
|
2019-11-07 16:24:19 +00:00
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
|
|
|
|
});
|
|
|
|
|
2019-11-11 23:56:43 +00:00
|
|
|
testUsingContext('new embedding app uses a plugin that supports v1 and v2 embedding', () async {
|
2019-10-29 23:05:13 +00:00
|
|
|
when(flutterProject.isModule).thenReturn(false);
|
2019-11-11 23:56:43 +00:00
|
|
|
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
|
|
|
|
|
|
|
|
createDualSupportJavaPlugin4();
|
2019-10-29 23:05:13 +00:00
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, androidPlatform: true);
|
2019-10-29 23:05:13 +00:00
|
|
|
|
|
|
|
final File registrant = flutterProject.directory
|
|
|
|
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
|
|
|
|
.childFile('GeneratedPluginRegistrant.java');
|
|
|
|
|
|
|
|
expect(registrant.existsSync(), isTrue);
|
|
|
|
expect(registrant.readAsStringSync(), contains('package io.flutter.plugins'));
|
|
|
|
expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
|
2019-11-11 23:56:43 +00:00
|
|
|
expect(registrant.readAsStringSync(),
|
|
|
|
contains('flutterEngine.getPlugins().add(new plugin4.UseBothEmbedding());'));
|
2019-10-29 23:05:13 +00:00
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
2019-11-11 23:56:43 +00:00
|
|
|
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
|
2019-10-29 23:05:13 +00:00
|
|
|
});
|
|
|
|
|
2019-11-11 23:56:43 +00:00
|
|
|
testUsingContext('Modules use new embedding', () async {
|
2019-10-29 23:05:13 +00:00
|
|
|
when(flutterProject.isModule).thenReturn(true);
|
2019-11-11 23:56:43 +00:00
|
|
|
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
|
2019-10-29 23:05:13 +00:00
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, androidPlatform: true);
|
2019-10-29 23:05:13 +00:00
|
|
|
|
|
|
|
final File registrant = flutterProject.directory
|
|
|
|
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
|
|
|
|
.childFile('GeneratedPluginRegistrant.java');
|
|
|
|
|
|
|
|
expect(registrant.existsSync(), isTrue);
|
|
|
|
expect(registrant.readAsStringSync(), contains('package io.flutter.plugins'));
|
|
|
|
expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
|
2019-11-11 23:56:43 +00:00
|
|
|
expect(registrant.readAsStringSync(), contains('public static void registerWith(@NonNull FlutterEngine flutterEngine)'));
|
2019-10-29 23:05:13 +00:00
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
});
|
|
|
|
|
2019-11-11 23:56:43 +00:00
|
|
|
testUsingContext('Module using old plugin shows warning', () async {
|
2019-10-29 23:05:13 +00:00
|
|
|
when(flutterProject.isModule).thenReturn(true);
|
2019-11-05 20:38:42 +00:00
|
|
|
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
|
2019-10-29 23:05:13 +00:00
|
|
|
|
2020-01-14 05:30:42 +00:00
|
|
|
createOldJavaPlugin('plugin3');
|
2019-11-11 23:56:43 +00:00
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, androidPlatform: true);
|
2019-10-29 23:05:13 +00:00
|
|
|
|
|
|
|
final File registrant = flutterProject.directory
|
2019-11-01 23:58:26 +00:00
|
|
|
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
|
2019-10-29 23:05:13 +00:00
|
|
|
.childFile('GeneratedPluginRegistrant.java');
|
2019-11-11 23:56:43 +00:00
|
|
|
expect(registrant.readAsStringSync(),
|
|
|
|
contains('plugin3.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin3.UseOldEmbedding"));'));
|
|
|
|
expect(testLogger.statusText, contains('The plugin `plugin3` is built using an older version of the Android plugin API'));
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
|
|
|
|
});
|
2019-10-29 23:05:13 +00:00
|
|
|
|
2019-11-11 23:56:43 +00:00
|
|
|
testUsingContext('Module using new plugin shows no warnings', () async {
|
|
|
|
when(flutterProject.isModule).thenReturn(true);
|
|
|
|
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
|
|
|
|
|
|
|
|
createNewJavaPlugin1();
|
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, androidPlatform: true);
|
2019-11-11 23:56:43 +00:00
|
|
|
|
|
|
|
final File registrant = flutterProject.directory
|
|
|
|
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
|
|
|
|
.childFile('GeneratedPluginRegistrant.java');
|
|
|
|
expect(registrant.readAsStringSync(),
|
|
|
|
contains('flutterEngine.getPlugins().add(new plugin1.UseNewEmbedding());'));
|
|
|
|
|
|
|
|
expect(testLogger.statusText, isNot(contains('go/android-plugin-migration')));
|
2019-10-29 23:05:13 +00:00
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
2019-11-11 23:56:43 +00:00
|
|
|
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
|
2019-10-29 23:05:13 +00:00
|
|
|
});
|
|
|
|
|
2019-11-11 23:56:43 +00:00
|
|
|
testUsingContext('Module using plugin with v1 and v2 support shows no warning', () async {
|
2019-10-29 23:05:13 +00:00
|
|
|
when(flutterProject.isModule).thenReturn(true);
|
2019-11-11 23:56:43 +00:00
|
|
|
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
|
|
|
|
|
|
|
|
createDualSupportJavaPlugin4();
|
2019-10-29 23:05:13 +00:00
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, androidPlatform: true);
|
2019-10-29 23:05:13 +00:00
|
|
|
|
|
|
|
final File registrant = flutterProject.directory
|
|
|
|
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
|
|
|
|
.childFile('GeneratedPluginRegistrant.java');
|
2019-11-11 23:56:43 +00:00
|
|
|
expect(registrant.readAsStringSync(),
|
|
|
|
contains('flutterEngine.getPlugins().add(new plugin4.UseBothEmbedding());'));
|
2019-10-29 23:05:13 +00:00
|
|
|
|
2019-11-11 23:56:43 +00:00
|
|
|
expect(testLogger.statusText, isNot(contains('go/android-plugin-migration')));
|
2019-10-29 23:05:13 +00:00
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
2019-11-11 23:56:43 +00:00
|
|
|
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
|
2019-10-29 23:05:13 +00:00
|
|
|
});
|
|
|
|
|
2020-01-14 05:30:42 +00:00
|
|
|
testUsingContext('Module using multiple old plugins all show warnings', () async {
|
|
|
|
when(flutterProject.isModule).thenReturn(true);
|
|
|
|
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
|
|
|
|
|
|
|
|
createOldJavaPlugin('plugin3');
|
|
|
|
createOldJavaPlugin('plugin4');
|
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, androidPlatform: true);
|
2020-01-14 05:30:42 +00:00
|
|
|
|
|
|
|
final File registrant = flutterProject.directory
|
|
|
|
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
|
|
|
|
.childFile('GeneratedPluginRegistrant.java');
|
|
|
|
expect(registrant.readAsStringSync(),
|
|
|
|
contains('plugin3.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin3.UseOldEmbedding"));'));
|
|
|
|
expect(registrant.readAsStringSync(),
|
|
|
|
contains('plugin4.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin4.UseOldEmbedding"));'));
|
|
|
|
expect(testLogger.statusText, contains('The plugin `plugin3` is built using an older version of the Android plugin API'));
|
|
|
|
expect(testLogger.statusText, contains('The plugin `plugin4` is built using an older version of the Android plugin API'));
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
|
|
|
|
});
|
|
|
|
|
2019-10-29 23:05:13 +00:00
|
|
|
testUsingContext('Does not throw when AndroidManifest.xml is not found', () async {
|
|
|
|
when(flutterProject.isModule).thenReturn(false);
|
|
|
|
|
2020-09-30 00:50:33 +00:00
|
|
|
final File manifest = fs.file('AndroidManifest.xml');
|
2019-10-29 23:05:13 +00:00
|
|
|
when(androidProject.appManifestFile).thenReturn(manifest);
|
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, androidPlatform: true);
|
2019-10-29 23:05:13 +00:00
|
|
|
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
});
|
|
|
|
|
2020-02-11 19:58:27 +00:00
|
|
|
testUsingContext("Registrant for web doesn't escape slashes in imports", () async {
|
2019-10-29 23:05:13 +00:00
|
|
|
when(flutterProject.isModule).thenReturn(true);
|
|
|
|
final Directory webPluginWithNestedFile =
|
|
|
|
fs.systemTempDirectory.createTempSync('web_plugin_with_nested');
|
|
|
|
webPluginWithNestedFile.childFile('pubspec.yaml').writeAsStringSync('''
|
|
|
|
flutter:
|
|
|
|
plugin:
|
|
|
|
platforms:
|
|
|
|
web:
|
|
|
|
pluginClass: WebPlugin
|
|
|
|
fileName: src/web_plugin.dart
|
|
|
|
''');
|
|
|
|
webPluginWithNestedFile
|
|
|
|
.childDirectory('lib')
|
|
|
|
.childDirectory('src')
|
|
|
|
.childFile('web_plugin.dart')
|
2020-03-05 07:03:26 +00:00
|
|
|
.createSync(recursive: true);
|
2019-10-25 22:00:05 +00:00
|
|
|
|
2019-10-29 23:05:13 +00:00
|
|
|
flutterProject.directory
|
|
|
|
.childFile('.packages')
|
|
|
|
.writeAsStringSync('''
|
2019-10-25 22:00:05 +00:00
|
|
|
web_plugin_with_nested:${webPluginWithNestedFile.childDirectory('lib').uri.toString()}
|
|
|
|
''');
|
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, webPlatform: true);
|
2019-10-25 22:00:05 +00:00
|
|
|
|
2019-10-29 23:05:13 +00:00
|
|
|
final File registrant = flutterProject.directory
|
|
|
|
.childDirectory('lib')
|
|
|
|
.childFile('generated_plugin_registrant.dart');
|
|
|
|
|
|
|
|
expect(registrant.existsSync(), isTrue);
|
|
|
|
expect(registrant.readAsStringSync(), contains("import 'package:web_plugin_with_nested/src/web_plugin.dart';"));
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
});
|
2020-02-13 20:53:28 +00:00
|
|
|
|
2020-04-03 18:51:01 +00:00
|
|
|
testUsingContext('Injecting creates generated macos registrant, but does not include Dart-only plugins', () async {
|
|
|
|
when(flutterProject.isModule).thenReturn(true);
|
|
|
|
// Create a plugin without a pluginClass.
|
2020-09-09 23:08:19 +00:00
|
|
|
final Directory pluginDirectory = createFakePlugin(fs);
|
|
|
|
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
|
2020-04-03 18:51:01 +00:00
|
|
|
flutter:
|
|
|
|
plugin:
|
|
|
|
platforms:
|
|
|
|
macos:
|
|
|
|
dartPluginClass: SomePlugin
|
|
|
|
''');
|
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, macOSPlatform: true);
|
2020-04-03 18:51:01 +00:00
|
|
|
|
|
|
|
final File registrantFile = macosProject.managedDirectory.childFile('GeneratedPluginRegistrant.swift');
|
|
|
|
|
|
|
|
expect(registrantFile, exists);
|
|
|
|
expect(registrantFile, isNot(contains('SomePlugin')));
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
});
|
|
|
|
|
2020-05-18 16:26:37 +00:00
|
|
|
testUsingContext('pluginClass: none doesn\'t trigger registrant entry on macOS', () async {
|
|
|
|
when(flutterProject.isModule).thenReturn(true);
|
|
|
|
// Create a plugin without a pluginClass.
|
2020-09-09 23:08:19 +00:00
|
|
|
final Directory pluginDirectory = createFakePlugin(fs);
|
|
|
|
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
|
2020-05-18 16:26:37 +00:00
|
|
|
flutter:
|
|
|
|
plugin:
|
|
|
|
platforms:
|
|
|
|
macos:
|
|
|
|
pluginClass: none
|
|
|
|
dartPluginClass: SomePlugin
|
|
|
|
''');
|
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, macOSPlatform: true);
|
2020-05-18 16:26:37 +00:00
|
|
|
|
|
|
|
final File registrantFile = macosProject.managedDirectory.childFile('GeneratedPluginRegistrant.swift');
|
|
|
|
|
|
|
|
expect(registrantFile, exists);
|
|
|
|
expect(registrantFile, isNot(contains('SomePlugin')));
|
|
|
|
expect(registrantFile, isNot(contains('none')));
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
});
|
|
|
|
|
2020-08-12 02:31:03 +00:00
|
|
|
testUsingContext('Invalid yaml does not crash plugin lookup.', () async {
|
|
|
|
when(flutterProject.isModule).thenReturn(true);
|
|
|
|
// Create a plugin without a pluginClass.
|
2020-09-09 23:08:19 +00:00
|
|
|
final Directory pluginDirectory = createFakePlugin(fs);
|
|
|
|
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync(r'''
|
2020-08-12 02:31:03 +00:00
|
|
|
"aws ... \"Branch\": $BITBUCKET_BRANCH, \"Date\": $(date +"%m-%d-%y"), \"Time\": $(date +"%T")}\"
|
|
|
|
''');
|
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, macOSPlatform: true);
|
2020-08-12 02:31:03 +00:00
|
|
|
|
|
|
|
final File registrantFile = macosProject.managedDirectory.childFile('GeneratedPluginRegistrant.swift');
|
|
|
|
|
|
|
|
expect(registrantFile, exists);
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
});
|
|
|
|
|
2020-02-27 17:45:22 +00:00
|
|
|
testUsingContext('Injecting creates generated Linux registrant', () async {
|
|
|
|
when(flutterProject.isModule).thenReturn(false);
|
2020-09-09 23:08:19 +00:00
|
|
|
createFakePlugin(fs);
|
2020-02-27 17:45:22 +00:00
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, linuxPlatform: true);
|
2020-02-27 17:45:22 +00:00
|
|
|
|
|
|
|
final File registrantHeader = linuxProject.managedDirectory.childFile('generated_plugin_registrant.h');
|
|
|
|
final File registrantImpl = linuxProject.managedDirectory.childFile('generated_plugin_registrant.cc');
|
|
|
|
|
|
|
|
expect(registrantHeader.existsSync(), isTrue);
|
|
|
|
expect(registrantImpl.existsSync(), isTrue);
|
2020-06-16 20:31:24 +00:00
|
|
|
expect(registrantImpl.readAsStringSync(), contains('some_plugin_register_with_registrar'));
|
2020-02-27 17:45:22 +00:00
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
});
|
|
|
|
|
2020-04-03 18:51:01 +00:00
|
|
|
testUsingContext('Injecting creates generated Linux registrant, but does not include Dart-only plugins', () async {
|
|
|
|
when(flutterProject.isModule).thenReturn(false);
|
|
|
|
// Create a plugin without a pluginClass.
|
2020-09-09 23:08:19 +00:00
|
|
|
final Directory pluginDirectory = createFakePlugin(fs);
|
|
|
|
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
|
2020-04-03 18:51:01 +00:00
|
|
|
flutter:
|
|
|
|
plugin:
|
|
|
|
platforms:
|
|
|
|
linux:
|
|
|
|
dartPluginClass: SomePlugin
|
|
|
|
''');
|
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, linuxPlatform: true);
|
2020-04-03 18:51:01 +00:00
|
|
|
|
|
|
|
final File registrantImpl = linuxProject.managedDirectory.childFile('generated_plugin_registrant.cc');
|
|
|
|
|
|
|
|
expect(registrantImpl, exists);
|
|
|
|
expect(registrantImpl, isNot(contains('SomePlugin')));
|
2020-06-16 20:31:24 +00:00
|
|
|
expect(registrantImpl, isNot(contains('some_plugin')));
|
2020-04-03 18:51:01 +00:00
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
});
|
|
|
|
|
2020-05-18 16:26:37 +00:00
|
|
|
testUsingContext('pluginClass: none doesn\'t trigger registrant entry on Linux', () async {
|
|
|
|
when(flutterProject.isModule).thenReturn(false);
|
|
|
|
// Create a plugin without a pluginClass.
|
2020-09-09 23:08:19 +00:00
|
|
|
final Directory pluginDirectory = createFakePlugin(fs);
|
|
|
|
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
|
2020-05-18 16:26:37 +00:00
|
|
|
flutter:
|
|
|
|
plugin:
|
|
|
|
platforms:
|
|
|
|
linux:
|
|
|
|
pluginClass: none
|
|
|
|
dartPluginClass: SomePlugin
|
|
|
|
''');
|
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, linuxPlatform: true);
|
2020-05-18 16:26:37 +00:00
|
|
|
|
|
|
|
final File registrantImpl = linuxProject.managedDirectory.childFile('generated_plugin_registrant.cc');
|
|
|
|
|
|
|
|
expect(registrantImpl, exists);
|
|
|
|
expect(registrantImpl, isNot(contains('SomePlugin')));
|
|
|
|
expect(registrantImpl, isNot(contains('none')));
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
});
|
|
|
|
|
2020-05-16 22:07:34 +00:00
|
|
|
testUsingContext('Injecting creates generated Linux plugin Cmake file', () async {
|
2020-02-27 17:45:22 +00:00
|
|
|
when(flutterProject.isModule).thenReturn(false);
|
2020-09-09 23:08:19 +00:00
|
|
|
createFakePlugin(fs);
|
2020-02-27 17:45:22 +00:00
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, linuxPlatform: true);
|
2020-02-27 17:45:22 +00:00
|
|
|
|
2020-05-16 22:07:34 +00:00
|
|
|
final File pluginMakefile = linuxProject.generatedPluginCmakeFile;
|
2020-02-27 17:45:22 +00:00
|
|
|
|
|
|
|
expect(pluginMakefile.existsSync(), isTrue);
|
|
|
|
final String contents = pluginMakefile.readAsStringSync();
|
2020-09-15 00:57:11 +00:00
|
|
|
expect(contents, contains('some_plugin'));
|
2020-10-26 19:42:05 +00:00
|
|
|
expect(contents, contains(r'target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)'));
|
|
|
|
expect(contents, contains(r'list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)'));
|
|
|
|
expect(contents, contains(r'list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})'));
|
2020-02-27 17:45:22 +00:00
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
});
|
|
|
|
|
2020-09-15 00:57:11 +00:00
|
|
|
testUsingContext('Generated Linux plugin files sorts by plugin name', () async {
|
|
|
|
when(flutterProject.isModule).thenReturn(false);
|
|
|
|
createFakePlugins(fs, <String>[
|
|
|
|
'plugin_d',
|
|
|
|
'plugin_a',
|
|
|
|
'/local_plugins/plugin_c',
|
|
|
|
'/local_plugins/plugin_b'
|
|
|
|
]);
|
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, linuxPlatform: true);
|
2020-09-15 00:57:11 +00:00
|
|
|
|
|
|
|
final File pluginCmakeFile = linuxProject.generatedPluginCmakeFile;
|
|
|
|
final File pluginRegistrant = linuxProject.managedDirectory.childFile('generated_plugin_registrant.cc');
|
|
|
|
for (final File file in <File>[pluginCmakeFile, pluginRegistrant]) {
|
|
|
|
final String contents = file.readAsStringSync();
|
|
|
|
expect(contents.indexOf('plugin_a'), lessThan(contents.indexOf('plugin_b')));
|
|
|
|
expect(contents.indexOf('plugin_b'), lessThan(contents.indexOf('plugin_c')));
|
|
|
|
expect(contents.indexOf('plugin_c'), lessThan(contents.indexOf('plugin_d')));
|
|
|
|
}
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
});
|
2020-02-27 17:45:22 +00:00
|
|
|
|
2020-02-13 20:53:28 +00:00
|
|
|
testUsingContext('Injecting creates generated Windows registrant', () async {
|
|
|
|
when(flutterProject.isModule).thenReturn(false);
|
2020-09-09 23:08:19 +00:00
|
|
|
createFakePlugin(fs);
|
2020-02-13 20:53:28 +00:00
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, windowsPlatform: true);
|
2020-02-13 20:53:28 +00:00
|
|
|
|
|
|
|
final File registrantHeader = windowsProject.managedDirectory.childFile('generated_plugin_registrant.h');
|
|
|
|
final File registrantImpl = windowsProject.managedDirectory.childFile('generated_plugin_registrant.cc');
|
|
|
|
|
|
|
|
expect(registrantHeader.existsSync(), isTrue);
|
|
|
|
expect(registrantImpl.existsSync(), isTrue);
|
|
|
|
expect(registrantImpl.readAsStringSync(), contains('SomePluginRegisterWithRegistrar'));
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
});
|
|
|
|
|
2020-04-03 18:51:01 +00:00
|
|
|
testUsingContext('Injecting creates generated Windows registrant, but does not include Dart-only plugins', () async {
|
|
|
|
when(flutterProject.isModule).thenReturn(false);
|
|
|
|
// Create a plugin without a pluginClass.
|
2020-09-09 23:08:19 +00:00
|
|
|
final Directory pluginDirectory = createFakePlugin(fs);
|
|
|
|
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
|
2020-04-03 18:51:01 +00:00
|
|
|
flutter:
|
|
|
|
plugin:
|
|
|
|
platforms:
|
|
|
|
windows:
|
|
|
|
dartPluginClass: SomePlugin
|
|
|
|
''');
|
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, windowsPlatform: true);
|
2020-04-03 18:51:01 +00:00
|
|
|
|
|
|
|
final File registrantImpl = windowsProject.managedDirectory.childFile('generated_plugin_registrant.cc');
|
|
|
|
|
|
|
|
expect(registrantImpl, exists);
|
|
|
|
expect(registrantImpl, isNot(contains('SomePlugin')));
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
});
|
|
|
|
|
2020-05-18 16:26:37 +00:00
|
|
|
testUsingContext('pluginClass: none doesn\'t trigger registrant entry on Windows', () async {
|
|
|
|
when(flutterProject.isModule).thenReturn(false);
|
|
|
|
// Create a plugin without a pluginClass.
|
2020-09-09 23:08:19 +00:00
|
|
|
final Directory pluginDirectory = createFakePlugin(fs);
|
|
|
|
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
|
2020-05-18 16:26:37 +00:00
|
|
|
flutter:
|
|
|
|
plugin:
|
|
|
|
platforms:
|
|
|
|
windows:
|
|
|
|
pluginClass: none
|
|
|
|
dartPluginClass: SomePlugin
|
|
|
|
''');
|
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, windowsPlatform: true);
|
2020-05-18 16:26:37 +00:00
|
|
|
|
|
|
|
final File registrantImpl = windowsProject.managedDirectory.childFile('generated_plugin_registrant.cc');
|
|
|
|
|
|
|
|
expect(registrantImpl, exists);
|
|
|
|
expect(registrantImpl, isNot(contains('SomePlugin')));
|
|
|
|
expect(registrantImpl, isNot(contains('none')));
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
});
|
|
|
|
|
2020-09-15 00:57:11 +00:00
|
|
|
testUsingContext('Generated Windows plugin files sorts by plugin name', () async {
|
2020-02-13 20:53:28 +00:00
|
|
|
when(flutterProject.isModule).thenReturn(false);
|
2020-09-15 00:57:11 +00:00
|
|
|
createFakePlugins(fs, <String>[
|
|
|
|
'plugin_d',
|
|
|
|
'plugin_a',
|
|
|
|
'/local_plugins/plugin_c',
|
|
|
|
'/local_plugins/plugin_b'
|
|
|
|
]);
|
2020-02-13 20:53:28 +00:00
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, windowsPlatform: true);
|
2020-02-13 20:53:28 +00:00
|
|
|
|
2020-09-15 00:57:11 +00:00
|
|
|
final File pluginCmakeFile = windowsProject.generatedPluginCmakeFile;
|
|
|
|
final File pluginRegistrant = windowsProject.managedDirectory.childFile('generated_plugin_registrant.cc');
|
|
|
|
for (final File file in <File>[pluginCmakeFile, pluginRegistrant]) {
|
|
|
|
final String contents = file.readAsStringSync();
|
|
|
|
expect(contents.indexOf('plugin_a'), lessThan(contents.indexOf('plugin_b')));
|
|
|
|
expect(contents.indexOf('plugin_b'), lessThan(contents.indexOf('plugin_c')));
|
|
|
|
expect(contents.indexOf('plugin_c'), lessThan(contents.indexOf('plugin_d')));
|
|
|
|
}
|
2020-02-25 22:16:27 +00:00
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
});
|
2020-09-09 23:08:19 +00:00
|
|
|
|
|
|
|
testUsingContext('Generated plugin CMake files always use posix-style paths', () async {
|
|
|
|
// Re-run the setup using the Windows filesystem.
|
|
|
|
setUpProject(fsWindows);
|
|
|
|
createFakePlugin(fsWindows);
|
|
|
|
|
|
|
|
when(flutterProject.isModule).thenReturn(false);
|
|
|
|
|
2020-10-19 21:17:43 +00:00
|
|
|
await injectPlugins(flutterProject, linuxPlatform: true, windowsPlatform: true);
|
2020-09-09 23:08:19 +00:00
|
|
|
|
|
|
|
for (final CmakeBasedProject project in <CmakeBasedProject>[linuxProject, windowsProject]) {
|
|
|
|
final File pluginCmakefile = project.generatedPluginCmakeFile;
|
|
|
|
|
|
|
|
expect(pluginCmakefile.existsSync(), isTrue);
|
|
|
|
final String contents = pluginCmakefile.readAsStringSync();
|
|
|
|
expect(contents, contains('add_subdirectory(flutter/ephemeral/.plugin_symlinks'));
|
|
|
|
}
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fsWindows,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
});
|
2019-10-25 22:00:05 +00:00
|
|
|
});
|
2020-02-13 00:23:27 +00:00
|
|
|
|
|
|
|
group('createPluginSymlinks', () {
|
|
|
|
MockFeatureFlags featureFlags;
|
|
|
|
|
|
|
|
setUp(() {
|
|
|
|
featureFlags = MockFeatureFlags();
|
|
|
|
when(featureFlags.isLinuxEnabled).thenReturn(true);
|
|
|
|
when(featureFlags.isWindowsEnabled).thenReturn(true);
|
|
|
|
});
|
|
|
|
|
2020-04-22 02:55:15 +00:00
|
|
|
testUsingContext('Symlinks are created for Linux plugins', () async {
|
2020-02-13 00:23:27 +00:00
|
|
|
when(linuxProject.existsSync()).thenReturn(true);
|
2020-09-09 23:08:19 +00:00
|
|
|
createFakePlugin(fs);
|
2020-02-13 00:23:27 +00:00
|
|
|
// refreshPluginsList should call createPluginSymlinks.
|
2020-04-22 02:55:15 +00:00
|
|
|
await refreshPluginsList(flutterProject);
|
2020-02-13 00:23:27 +00:00
|
|
|
|
2020-09-15 00:57:11 +00:00
|
|
|
expect(linuxProject.pluginSymlinkDirectory.childLink('some_plugin').existsSync(), true);
|
2020-02-13 00:23:27 +00:00
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
FeatureFlags: () => featureFlags,
|
|
|
|
});
|
|
|
|
|
2020-04-22 02:55:15 +00:00
|
|
|
testUsingContext('Symlinks are created for Windows plugins', () async {
|
2020-02-13 00:23:27 +00:00
|
|
|
when(windowsProject.existsSync()).thenReturn(true);
|
2020-09-09 23:08:19 +00:00
|
|
|
createFakePlugin(fs);
|
2020-02-13 00:23:27 +00:00
|
|
|
// refreshPluginsList should call createPluginSymlinks.
|
2020-04-22 02:55:15 +00:00
|
|
|
await refreshPluginsList(flutterProject);
|
2020-02-13 00:23:27 +00:00
|
|
|
|
2020-09-15 00:57:11 +00:00
|
|
|
expect(windowsProject.pluginSymlinkDirectory.childLink('some_plugin').existsSync(), true);
|
2020-02-13 00:23:27 +00:00
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
FeatureFlags: () => featureFlags,
|
|
|
|
});
|
|
|
|
|
|
|
|
testUsingContext('Existing symlinks are removed when no longer in use with force', () {
|
|
|
|
when(linuxProject.existsSync()).thenReturn(true);
|
|
|
|
when(windowsProject.existsSync()).thenReturn(true);
|
|
|
|
|
|
|
|
final List<File> dummyFiles = <File>[
|
|
|
|
flutterProject.linux.pluginSymlinkDirectory.childFile('dummy'),
|
|
|
|
flutterProject.windows.pluginSymlinkDirectory.childFile('dummy'),
|
|
|
|
];
|
|
|
|
for (final File file in dummyFiles) {
|
|
|
|
file.createSync(recursive: true);
|
|
|
|
}
|
|
|
|
|
|
|
|
createPluginSymlinks(flutterProject, force: true);
|
|
|
|
|
|
|
|
for (final File file in dummyFiles) {
|
|
|
|
expect(file.existsSync(), false);
|
|
|
|
}
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
FeatureFlags: () => featureFlags,
|
|
|
|
});
|
|
|
|
|
2020-04-22 02:55:15 +00:00
|
|
|
testUsingContext('Existing symlinks are removed automatically on refresh when no longer in use', () async {
|
2020-02-13 00:23:27 +00:00
|
|
|
when(linuxProject.existsSync()).thenReturn(true);
|
|
|
|
when(windowsProject.existsSync()).thenReturn(true);
|
|
|
|
|
|
|
|
final List<File> dummyFiles = <File>[
|
|
|
|
flutterProject.linux.pluginSymlinkDirectory.childFile('dummy'),
|
|
|
|
flutterProject.windows.pluginSymlinkDirectory.childFile('dummy'),
|
|
|
|
];
|
|
|
|
for (final File file in dummyFiles) {
|
|
|
|
file.createSync(recursive: true);
|
|
|
|
}
|
|
|
|
|
|
|
|
// refreshPluginsList should remove existing links and recreate on changes.
|
2020-09-09 23:08:19 +00:00
|
|
|
createFakePlugin(fs);
|
2020-04-22 02:55:15 +00:00
|
|
|
await refreshPluginsList(flutterProject);
|
2020-02-13 00:23:27 +00:00
|
|
|
|
|
|
|
for (final File file in dummyFiles) {
|
|
|
|
expect(file.existsSync(), false);
|
|
|
|
}
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
FeatureFlags: () => featureFlags,
|
|
|
|
});
|
|
|
|
|
|
|
|
testUsingContext('createPluginSymlinks is a no-op without force when up to date', () {
|
|
|
|
when(linuxProject.existsSync()).thenReturn(true);
|
|
|
|
when(windowsProject.existsSync()).thenReturn(true);
|
|
|
|
|
|
|
|
final List<File> dummyFiles = <File>[
|
|
|
|
flutterProject.linux.pluginSymlinkDirectory.childFile('dummy'),
|
|
|
|
flutterProject.windows.pluginSymlinkDirectory.childFile('dummy'),
|
|
|
|
];
|
|
|
|
for (final File file in dummyFiles) {
|
|
|
|
file.createSync(recursive: true);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Without force, this should do nothing to existing files.
|
|
|
|
createPluginSymlinks(flutterProject);
|
|
|
|
|
|
|
|
for (final File file in dummyFiles) {
|
|
|
|
expect(file.existsSync(), true);
|
|
|
|
}
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
FeatureFlags: () => featureFlags,
|
|
|
|
});
|
|
|
|
|
2020-04-22 02:55:15 +00:00
|
|
|
testUsingContext('createPluginSymlinks repairs missing links', () async {
|
2020-02-13 00:23:27 +00:00
|
|
|
when(linuxProject.existsSync()).thenReturn(true);
|
|
|
|
when(windowsProject.existsSync()).thenReturn(true);
|
2020-09-09 23:08:19 +00:00
|
|
|
createFakePlugin(fs);
|
2020-04-22 02:55:15 +00:00
|
|
|
await refreshPluginsList(flutterProject);
|
2020-02-13 00:23:27 +00:00
|
|
|
|
|
|
|
final List<Link> links = <Link>[
|
2020-09-15 00:57:11 +00:00
|
|
|
linuxProject.pluginSymlinkDirectory.childLink('some_plugin'),
|
|
|
|
windowsProject.pluginSymlinkDirectory.childLink('some_plugin'),
|
2020-02-13 00:23:27 +00:00
|
|
|
];
|
|
|
|
for (final Link link in links) {
|
|
|
|
link.deleteSync();
|
|
|
|
}
|
|
|
|
createPluginSymlinks(flutterProject);
|
|
|
|
|
|
|
|
for (final Link link in links) {
|
|
|
|
expect(link.existsSync(), true);
|
|
|
|
}
|
|
|
|
}, overrides: <Type, Generator>{
|
|
|
|
FileSystem: () => fs,
|
|
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
|
|
FeatureFlags: () => featureFlags,
|
|
|
|
});
|
|
|
|
});
|
2020-06-24 00:38:03 +00:00
|
|
|
|
|
|
|
group('pubspec', () {
|
|
|
|
|
|
|
|
Directory projectDir;
|
|
|
|
Directory tempDir;
|
|
|
|
setUp(() {
|
|
|
|
tempDir = globals.fs.systemTempDirectory.createTempSync('plugin_test.');
|
|
|
|
projectDir = tempDir.childDirectory('flutter_project');
|
|
|
|
});
|
|
|
|
|
|
|
|
tearDown(() {
|
|
|
|
tryToDelete(tempDir);
|
|
|
|
});
|
|
|
|
|
|
|
|
void _createPubspecFile(String yamlString) {
|
|
|
|
projectDir.childFile('pubspec.yaml')..createSync(recursive: true)..writeAsStringSync(yamlString);
|
|
|
|
}
|
|
|
|
|
|
|
|
test('validatePubspecForPlugin works', () async {
|
2020-09-15 00:57:11 +00:00
|
|
|
const String pluginYaml = '''
|
|
|
|
flutter:
|
|
|
|
plugin:
|
|
|
|
platforms:
|
|
|
|
ios:
|
|
|
|
pluginClass: SomePlugin
|
|
|
|
macos:
|
|
|
|
pluginClass: SomePlugin
|
|
|
|
windows:
|
|
|
|
pluginClass: SomePlugin
|
|
|
|
linux:
|
|
|
|
pluginClass: SomePlugin
|
|
|
|
web:
|
|
|
|
pluginClass: SomePlugin
|
|
|
|
fileName: lib/SomeFile.dart
|
|
|
|
android:
|
|
|
|
pluginClass: SomePlugin
|
|
|
|
package: AndroidPackage
|
|
|
|
''';
|
|
|
|
_createPubspecFile(pluginYaml);
|
2020-06-24 00:38:03 +00:00
|
|
|
validatePubspecForPlugin(projectDir: projectDir.absolute.path, pluginClass: 'SomePlugin', expectedPlatforms: <String>[
|
|
|
|
'ios', 'macos', 'windows', 'linux', 'android', 'web'
|
|
|
|
], androidIdentifier: 'AndroidPackage', webFileName: 'lib/SomeFile.dart');
|
|
|
|
});
|
|
|
|
|
|
|
|
test('createPlatformsYamlMap should create the correct map', () async {
|
|
|
|
final YamlMap map = Plugin.createPlatformsYamlMap(<String>['ios', 'android', 'linux'], 'PluginClass', 'some.android.package');
|
|
|
|
expect(map['ios'], <String, String> {
|
|
|
|
'pluginClass' : 'PluginClass'
|
|
|
|
});
|
|
|
|
expect(map['android'], <String, String> {
|
|
|
|
'pluginClass' : 'PluginClass',
|
|
|
|
'package': 'some.android.package',
|
|
|
|
});
|
|
|
|
expect(map['linux'], <String, String> {
|
|
|
|
'pluginClass' : 'PluginClass'
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
test('createPlatformsYamlMap should create empty map', () async {
|
|
|
|
final YamlMap map = Plugin.createPlatformsYamlMap(<String>[], null, null);
|
|
|
|
expect(map.isEmpty, true);
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
2020-10-06 15:32:54 +00:00
|
|
|
|
|
|
|
testWithoutContext('Symlink failures give developer mode instructions on recent versions of Windows', () async {
|
|
|
|
final Platform platform = FakePlatform(operatingSystem: 'windows');
|
|
|
|
final MockOperatingSystemUtils os = MockOperatingSystemUtils();
|
|
|
|
when(os.name).thenReturn('Microsoft Windows [Version 10.0.14972.1]');
|
|
|
|
|
|
|
|
const FileSystemException e = FileSystemException('', '', OSError('', 1314));
|
|
|
|
|
|
|
|
expect(() => handleSymlinkException(e, platform: platform, os: os),
|
|
|
|
throwsToolExit(message: 'start ms-settings:developers'));
|
|
|
|
});
|
|
|
|
|
|
|
|
testWithoutContext('Symlink failures instruct developers to run as administrator on older versions of Windows', () async {
|
|
|
|
final Platform platform = FakePlatform(operatingSystem: 'windows');
|
|
|
|
final MockOperatingSystemUtils os = MockOperatingSystemUtils();
|
|
|
|
when(os.name).thenReturn('Microsoft Windows [Version 10.0.14393]');
|
|
|
|
|
|
|
|
const FileSystemException e = FileSystemException('', '', OSError('', 1314));
|
|
|
|
|
|
|
|
expect(() => handleSymlinkException(e, platform: platform, os: os),
|
|
|
|
throwsToolExit(message: 'administrator'));
|
|
|
|
});
|
|
|
|
|
|
|
|
testWithoutContext('Symlink failures only give instructions for specific errors', () async {
|
|
|
|
final Platform platform = FakePlatform(operatingSystem: 'windows');
|
|
|
|
final MockOperatingSystemUtils os = MockOperatingSystemUtils();
|
|
|
|
when(os.name).thenReturn('Microsoft Windows [Version 10.0.14393]');
|
|
|
|
|
|
|
|
const FileSystemException e = FileSystemException('', '', OSError('', 999));
|
|
|
|
|
|
|
|
expect(() => handleSymlinkException(e, platform: platform, os: os), returnsNormally);
|
|
|
|
});
|
2019-10-04 13:23:03 +00:00
|
|
|
});
|
2019-09-19 04:42:57 +00:00
|
|
|
}
|
2019-10-04 13:23:03 +00:00
|
|
|
|
|
|
|
class MockAndroidProject extends Mock implements AndroidProject {}
|
|
|
|
class MockFeatureFlags extends Mock implements FeatureFlags {}
|
|
|
|
class MockFlutterProject extends Mock implements FlutterProject {}
|
|
|
|
class MockIosProject extends Mock implements IosProject {}
|
|
|
|
class MockMacOSProject extends Mock implements MacOSProject {}
|
|
|
|
class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {}
|
2019-10-25 22:00:05 +00:00
|
|
|
class MockWebProject extends Mock implements WebProject {}
|
2020-01-17 22:43:34 +00:00
|
|
|
class MockWindowsProject extends Mock implements WindowsProject {}
|
|
|
|
class MockLinuxProject extends Mock implements LinuxProject {}
|
2020-10-06 15:32:54 +00:00
|
|
|
class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {}
|