blob: 32da1cba31d628b918de835913e23166ff5cf896 [file]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'package:analysis_server/src/plugin/plugin_locator.dart';
import 'package:analysis_server/src/plugin/plugin_watcher.dart';
import 'package:analysis_server/src/utilities/mocks.dart';
import 'package:analyzer/src/analysis_options/analysis_options.dart';
import 'package:analyzer_plugin/protocol/protocol_generated.dart' as protocol;
import 'package:analyzer_testing/package_config_file_builder.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../abstract_context.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(PluginWatcherTest);
});
}
@reflectiveTest
class PluginWatcherTest extends AbstractContextTest {
late TestPluginManager manager;
late PluginWatcher watcher;
@override
void setUp() {
super.setUp();
manager = TestPluginManager(resourceProvider);
watcher = PluginWatcher(resourceProvider, manager, pluginsAreEnabled: true);
}
Future<void> test_addedDriver() async {
newPubspecYamlFile('/foo', 'name: foo');
newFile(
join(
'/foo',
PluginLocator.toolsFolderName,
PluginLocator.defaultPluginFolderName,
'bin',
'plugin.dart',
),
'',
);
newFile(join(testPackageRootPath, 'analysis_options.yaml'), '''
analyzer:
plugins:
- foo
''');
writeTestPackageConfig(
config: PackageConfigFileBuilder()
..add(name: 'foo', rootFolder: getFolder('/foo')),
);
var driver = driverFor(testFile);
expect(manager.contextRootPlugins, isEmpty);
watcher.addedDriver(driver);
await _waitForEvents();
expect(manager.contextRootPlugins, hasLength(1));
}
Future<void> test_addedDriver_missingPackage() async {
newFile(join(testPackageRootPath, 'analysis_options.yaml'), '''
analyzer:
plugins:
- no_such_package
''');
var driver = driverFor(testFile);
watcher.addedDriver(driver);
expect(manager.contextRootPlugins, isEmpty);
await _waitForEvents();
expect(manager.contextRootPlugins, isEmpty);
}
Future<void> test_addedDriver_multiple_plugins_conflicting() async {
newPubspecYamlFile('/foo', 'name: foo');
newFile('/foo/tools/analyzer_plugin/bin/plugin.dart', '');
newFile(join(testPackageRootPath, 'analysis_options.yaml'), '''
plugins:
foo: ^1.0.0
''');
var innerFolderPath = join(testPackageRootPath, 'inner');
newFile(join(innerFolderPath, 'analysis_options.yaml'), '''
plugins:
foo: ^2.0.0
''');
writeTestPackageConfig(
config: PackageConfigFileBuilder()
..add(name: 'foo', rootFolder: getFolder('/foo')),
);
var driver = driverFor(testFile);
// Manually add the inner options to the driver's options map to simulate
// them being in the same context (e.g. in a Blaze workspace).
var innerFolder = getFolder(innerFolderPath);
var builder = AnalysisOptionsBuilder(
file: innerFolder.getFile('analysis_options.yaml'),
);
builder.pluginsOptions = PluginsOptions(
configurations: [
PluginConfiguration(
name: 'foo',
source: VersionedPluginSource(constraint: '^2.0.0'),
),
],
dependencyOverrides: null,
);
driver.analysisOptionsMap[innerFolder] = builder.build();
watcher.addedDriver(driver);
await _waitForEvents();
expect(manager.contextRootPlugins.values.first, hasLength(2));
expect(manager.pluginIsolates, hasLength(2));
var isolate0 = manager.pluginIsolates.firstWhere(
(e) => !e.pluginId.contains('_group_1'),
) as TestPluginIsolate;
var isolate1 = manager.pluginIsolates.firstWhere(
(e) => e.pluginId.contains('_group_1'),
) as TestPluginIsolate;
var setConfigurations0 =
isolate0.requests.first as protocol.AnalysisSetConfigurationsParams;
var config0 = setConfigurations0.configurations;
var setConfigurations1 =
isolate1.requests.first as protocol.AnalysisSetConfigurationsParams;
var config1 = setConfigurations1.configurations;
var rootPath = convertPath(testPackageRootPath);
var innerPath = innerFolder.path;
expect(config0[rootPath], isEmpty);
expect(config0[innerPath]!['foo']!.enabled, isTrue);
expect(config1[rootPath]!['foo']!.enabled, isTrue);
expect(config1[innerPath], isEmpty);
}
Future<void> test_addedDriver_multiple_plugins_conflictingOverrides() async {
newPubspecYamlFile('/foo', 'name: foo');
newFile('/foo/tools/analyzer_plugin/bin/plugin.dart', '');
newFile(join(testPackageRootPath, 'analysis_options.yaml'), '''
plugins:
foo: ^1.0.0
''');
var innerFolderPath = join(testPackageRootPath, 'inner');
newFile(join(innerFolderPath, 'analysis_options.yaml'), '''
plugins:
foo: ^1.0.0
''');
writeTestPackageConfig(
config: PackageConfigFileBuilder()
..add(name: 'foo', rootFolder: getFolder('/foo')),
);
var driver = driverFor(testFile);
// Root has override for 'dep' to path '/override1'
var rootFolder = getFolder(testPackageRootPath);
var rootBuilder = AnalysisOptionsBuilder(
file: rootFolder.getFile('analysis_options.yaml'),
);
rootBuilder.pluginsOptions = PluginsOptions(
configurations: [
PluginConfiguration(
name: 'foo',
source: VersionedPluginSource(constraint: '^1.0.0'),
),
],
dependencyOverrides: {'dep': PathPluginSource(path: '/override1')},
);
driver.analysisOptionsMap[rootFolder] = rootBuilder.build();
// Inner has override for 'dep' to path '/override2'
var innerFolder = getFolder(innerFolderPath);
var innerBuilder = AnalysisOptionsBuilder(
file: innerFolder.getFile('analysis_options.yaml'),
);
innerBuilder.pluginsOptions = PluginsOptions(
configurations: [
PluginConfiguration(
name: 'foo',
source: VersionedPluginSource(constraint: '^1.0.0'),
),
],
dependencyOverrides: {'dep': PathPluginSource(path: '/override2')},
);
driver.analysisOptionsMap[innerFolder] = innerBuilder.build();
watcher.addedDriver(driver);
await _waitForEvents();
expect(manager.contextRootPlugins.values.first, hasLength(2));
var pluginFolder1 = getFolder(manager.contextRootPlugins.values.first[0]);
var pubspecFile1 = pluginFolder1.getFile('pubspec.yaml');
var pubspecContent1 = pubspecFile1.readAsStringSync();
var pluginFolder2 = getFolder(manager.contextRootPlugins.values.first[1]);
var pubspecFile2 = pluginFolder2.getFile('pubspec.yaml');
var pubspecContent2 = pubspecFile2.readAsStringSync();
var paths = [
if (pubspecContent1.contains('/override1')) 1,
if (pubspecContent1.contains('/override2')) 2,
if (pubspecContent2.contains('/override1')) 1,
if (pubspecContent2.contains('/override2')) 2,
];
expect(paths, unorderedEquals([1, 2]));
}
Future<void>
test_addedDriver_multiple_plugins_conflictingOverridesOmission() async {
newPubspecYamlFile('/foo', 'name: foo');
newFile('/foo/tools/analyzer_plugin/bin/plugin.dart', '');
newFile(join(testPackageRootPath, 'analysis_options.yaml'), '''
plugins:
foo: ^1.0.0
''');
var innerFolderPath = join(testPackageRootPath, 'inner');
newFile(join(innerFolderPath, 'analysis_options.yaml'), '''
plugins:
foo: ^1.0.0
''');
writeTestPackageConfig(
config: PackageConfigFileBuilder()
..add(name: 'foo', rootFolder: getFolder('/foo')),
);
var driver = driverFor(testFile);
// Root options file has no overrides.
var rootFolder = getFolder(testPackageRootPath);
var rootBuilder = AnalysisOptionsBuilder(
file: rootFolder.getFile('analysis_options.yaml'),
);
rootBuilder.pluginsOptions = PluginsOptions(
configurations: [
PluginConfiguration(
name: 'foo',
source: VersionedPluginSource(constraint: '^1.0.0'),
),
],
dependencyOverrides: null,
);
driver.analysisOptionsMap[rootFolder] = rootBuilder.build();
// Inner optinos file has override for 'dep' to path '/override'.
var innerFolder = getFolder(innerFolderPath);
var innerBuilder = AnalysisOptionsBuilder(
file: innerFolder.getFile('analysis_options.yaml'),
);
innerBuilder.pluginsOptions = PluginsOptions(
configurations: [
PluginConfiguration(
name: 'foo',
source: VersionedPluginSource(constraint: '^1.0.0'),
),
],
dependencyOverrides: {'dep': PathPluginSource(path: '/override')},
);
driver.analysisOptionsMap[innerFolder] = innerBuilder.build();
watcher.addedDriver(driver);
await _waitForEvents();
expect(manager.contextRootPlugins.values.first, hasLength(2));
var pluginFolder1 = getFolder(manager.contextRootPlugins.values.first[0]);
var pubspecFile1 = pluginFolder1.getFile('pubspec.yaml');
var pubspecContent1 = pubspecFile1.readAsStringSync();
var pluginFolder2 = getFolder(manager.contextRootPlugins.values.first[1]);
var pubspecFile2 = pluginFolder2.getFile('pubspec.yaml');
var pubspecContent2 = pubspecFile2.readAsStringSync();
// One must have the override, and the other must not have
// 'dependency_overrides'.
expect(pubspecContent1, contains('dependency_overrides:'));
expect(pubspecContent1, contains('/override'));
expect(pubspecContent2, isNot(contains('dependency_overrides:')));
}
Future<void> test_addedDriver_multiple_plugins_grouped() async {
newPubspecYamlFile('/foo', 'name: foo');
newFile('/foo/tools/analyzer_plugin/bin/plugin.dart', '');
newPubspecYamlFile('/bar', 'name: bar');
newFile('/bar/tools/analyzer_plugin/bin/plugin.dart', '');
newFile(join(testPackageRootPath, 'analysis_options.yaml'), '''
plugins:
foo: ^1.0.0
''');
var innerFolderPath = join(testPackageRootPath, 'inner');
newFile(join(innerFolderPath, 'analysis_options.yaml'), '''
plugins:
foo: ^1.0.0
bar: ^1.0.0
''');
writeTestPackageConfig(
config: PackageConfigFileBuilder()
..add(name: 'foo', rootFolder: getFolder('/foo'))
..add(name: 'bar', rootFolder: getFolder('/bar')),
);
var driver = driverFor(testFile);
// Manually add the inner options to the driver's options map to simulate
// them being in the same context (e.g. in a Blaze workspace).
var innerFolder = getFolder(innerFolderPath);
var builder = AnalysisOptionsBuilder(
file: innerFolder.getFile('analysis_options.yaml'),
);
builder.pluginsOptions = PluginsOptions(
configurations: [
PluginConfiguration(
name: 'foo',
source: VersionedPluginSource(constraint: '^1.0.0'),
),
PluginConfiguration(
name: 'bar',
source: VersionedPluginSource(constraint: '^1.0.0'),
),
],
dependencyOverrides: null,
);
driver.analysisOptionsMap[innerFolder] = builder.build();
watcher.addedDriver(driver);
await _waitForEvents();
expect(manager.contextRootPlugins.values.first, hasLength(1));
var pluginFolder = getFolder(manager.contextRootPlugins.values.first.first);
var pubspecFile = pluginFolder.getFile('pubspec.yaml');
var pubspecContent = pubspecFile.readAsStringSync();
// The 'foo' dependency should only appear once in the pubspec.
expect(' foo:'.allMatches(pubspecContent), hasLength(1));
var entrypointFile = pluginFolder.getFolder('bin').getFile('plugin.dart');
var entrypointContent = entrypointFile.readAsStringSync();
// The 'foo' import should only appear once in the pubspec.
expect(
"import 'package:foo/main.dart'".allMatches(entrypointContent),
hasLength(1),
);
// 'foo' should only appear once in the entrypoint.
expect("'foo': foo.plugin".allMatches(entrypointContent), hasLength(1));
}
Future<void> test_addedDriver_pluginsInInnerOnly() async {
newPubspecYamlFile('/foo', 'name: foo');
newFile('/foo/tools/analyzer_plugin/bin/plugin.dart', '');
newFile(join(testPackageRootPath, 'analysis_options.yaml'), '''
analyzer:
''');
var innerFolderPath = join(testPackageRootPath, 'inner');
newFile(join(innerFolderPath, 'analysis_options.yaml'), '''
plugins:
foo: ^1.0.0
''');
writeTestPackageConfig(
config: PackageConfigFileBuilder()
..add(name: 'foo', rootFolder: getFolder('/foo')),
);
var driver = driverFor(testFile);
var innerFolder = getFolder(innerFolderPath);
var builder = AnalysisOptionsBuilder(
file: innerFolder.getFile('analysis_options.yaml'),
);
builder.pluginsOptions = PluginsOptions(
configurations: [
PluginConfiguration(
name: 'foo',
source: VersionedPluginSource(constraint: '^1.0.0'),
),
],
dependencyOverrides: null,
);
driver.analysisOptionsMap[innerFolder] = builder.build();
expect(manager.contextRootPlugins, isEmpty);
watcher.addedDriver(driver);
await _waitForEvents();
expect(manager.contextRootPlugins, hasLength(1));
}
Future<void> test_addedDriver_pluginsInRoot_emptyInInner() async {
newPubspecYamlFile('/foo', 'name: foo');
newFile('/foo/tools/analyzer_plugin/bin/plugin.dart', '');
newFile(join(testPackageRootPath, 'analysis_options.yaml'), '''
plugins:
foo: ^1.0.0
''');
var innerFolderPath = join(testPackageRootPath, 'inner');
newFile(join(innerFolderPath, 'analysis_options.yaml'), '');
writeTestPackageConfig(
config: PackageConfigFileBuilder()
..add(name: 'foo', rootFolder: getFolder('/foo')),
);
var driver = driverFor(testFile);
// Manually add the empty inner options to the driver's options map.
var innerFolder = getFolder(innerFolderPath);
var builder = AnalysisOptionsBuilder(
file: innerFolder.getFile('analysis_options.yaml'),
);
builder.pluginsOptions = PluginsOptions(
configurations: [],
dependencyOverrides: null,
);
driver.analysisOptionsMap[innerFolder] = builder.build();
watcher.addedDriver(driver);
await _waitForEvents();
expect(manager.contextRootPlugins.values.first, hasLength(1));
expect(manager.pluginIsolates, hasLength(1));
var isolate = manager.pluginIsolates.first as TestPluginIsolate;
var setConfigurations =
isolate.requests.first as protocol.AnalysisSetConfigurationsParams;
var config = setConfigurations.configurations;
var rootPath = convertPath(testPackageRootPath);
var innerPath = innerFolder.path;
expect(config[rootPath]!['foo']!.enabled, isTrue);
expect(config[innerPath], isEmpty);
}
void test_creation() {
expect(watcher.resourceProvider, resourceProvider);
expect(watcher.manager, manager);
}
void test_removedDriver() {
var driver = driverFor(testFile);
watcher.addedDriver(driver);
watcher.removedDriver(driver);
expect(manager.contextRootPlugins, isEmpty);
}
/// Wait until the timer associated with the driver's FileSystemState is
/// guaranteed to have expired and the list of changed files will have been
/// delivered.
Future<void> _waitForEvents() async {
await Future.delayed(Duration(seconds: 1));
}
}