flutter/packages/flutter_tools/test/integration.shard/web_plugin_registrant_test.dart
Andrew Kolos a9c50335c7
remove dependency on Usage from Pub class (#162279)
Towards https://github.com/flutter/flutter/issues/150575

Removes more usage of `Usage`. This PR is scoped to removing all
references to it in `pub`-related code.

<details>

<summary> Pre-launch checklist </summary> 


- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

</details>


If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
2025-01-28 17:32:27 +00:00

417 lines
14 KiB
Dart

// 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 'dart:async';
import 'package:args/command_runner.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/create.dart';
import 'package:flutter_tools/src/dart/pub.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import '../commands.shard/permeable/utils/project_testing_utils.dart';
import '../src/common.dart';
import '../src/context.dart';
import '../src/test_flutter_command_runner.dart';
import 'test_utils.dart';
void main() {
late Directory tempDir;
late Directory projectDir;
setUpAll(() async {
Cache.disableLocking();
await ensureFlutterToolsSnapshot();
});
setUp(() {
tempDir = globals.fs.systemTempDirectory.createTempSync(
'flutter_tools_generated_plugin_registrant_test.',
);
projectDir = tempDir.childDirectory('flutter_project');
});
tearDown(() {
tryToDelete(tempDir);
});
tearDownAll(() async {
await restoreFlutterToolsSnapshot();
});
testUsingContext(
'generated plugin registrant passes analysis',
() async {
await _createProject(projectDir, <String>[]);
// We need a dependency so the plugin registrant is not completely empty.
await _editPubspecFile(
projectDir,
_addDependencyEditor('shared_preferences', version: '^2.0.0'),
);
// The plugin registrant is created on build...
await _buildWebProject(projectDir);
// Find the web_plugin_registrant, now that it lives outside "lib":
final Directory buildDir =
projectDir
.childDirectory('.dart_tool/flutter_build')
.listSync()
.firstWhere((FileSystemEntity entity) => entity is Directory)
as Directory;
// Ensure the file exists, and passes analysis.
final File registrant = buildDir.childFile('web_plugin_registrant.dart');
expect(registrant, exists);
await _analyzeEntity(registrant);
// Ensure the contents match what we expect for a non-empty plugin registrant.
final String contents = registrant.readAsStringSync();
expect(contents, contains('// @dart = 2.13'));
expect(
contents,
contains("import 'package:shared_preferences_web/shared_preferences_web.dart';"),
);
expect(contents, contains('void registerPlugins([final Registrar? pluginRegistrar]) {'));
expect(contents, contains('SharedPreferencesPlugin.registerWith(registrar);'));
expect(contents, contains('registrar.registerMessageHandler();'));
},
overrides: <Type, Generator>{
Pub:
() => Pub.test(
fileSystem: globals.fs,
logger: globals.logger,
processManager: globals.processManager,
botDetector: globals.botDetector,
platform: globals.platform,
stdio: globals.stdio,
),
},
);
testUsingContext(
'generated plugin registrant passes analysis with null safety',
() async {
await _createProject(projectDir, <String>[]);
// We need a dependency so the plugin registrant is not completely empty.
await _editPubspecFile(
projectDir,
_composeEditors(<PubspecEditor>[
_addDependencyEditor('shared_preferences', version: '^2.0.0'),
_setDartSDKVersionEditor('>=2.12.0 <4.0.0'),
]),
);
// Replace main file with a no-op dummy. We aren't testing it in this scenario anyway.
await _replaceMainFile(projectDir, 'void main() {}');
// The plugin registrant is created on build...
await _buildWebProject(projectDir);
// Find the web_plugin_registrant, now that it lives outside "lib":
final Directory buildDir =
projectDir
.childDirectory('.dart_tool/flutter_build')
.listSync()
.firstWhere((FileSystemEntity entity) => entity is Directory)
as Directory;
// Ensure the file exists, and passes analysis.
final File registrant = buildDir.childFile('web_plugin_registrant.dart');
expect(registrant, exists);
await _analyzeEntity(registrant);
// Ensure the contents match what we expect for a non-empty plugin registrant.
final String contents = registrant.readAsStringSync();
expect(contents, contains('// @dart = 2.13'));
expect(
contents,
contains("import 'package:shared_preferences_web/shared_preferences_web.dart';"),
);
expect(contents, contains('void registerPlugins([final Registrar? pluginRegistrar]) {'));
expect(contents, contains('SharedPreferencesPlugin.registerWith(registrar);'));
expect(contents, contains('registrar.registerMessageHandler();'));
},
overrides: <Type, Generator>{
Pub:
() => Pub.test(
fileSystem: globals.fs,
logger: globals.logger,
processManager: globals.processManager,
botDetector: globals.botDetector,
platform: globals.platform,
stdio: globals.stdio,
),
},
);
testUsingContext(
'(no-op) generated plugin registrant passes analysis',
() async {
await _createProject(projectDir, <String>[]);
// No dependencies on web plugins this time!
await _buildWebProject(projectDir);
// Find the web_plugin_registrant, now that it lives outside "lib":
final Directory buildDir =
projectDir
.childDirectory('.dart_tool/flutter_build')
.listSync()
.firstWhere((FileSystemEntity entity) => entity is Directory)
as Directory;
// Ensure the file exists, and passes analysis.
final File registrant = buildDir.childFile('web_plugin_registrant.dart');
expect(registrant, exists);
await _analyzeEntity(registrant);
// Ensure the contents match what we expect for an empty (noop) plugin registrant.
final String contents = registrant.readAsStringSync();
expect(contents, contains('void registerPlugins() {}'));
},
overrides: <Type, Generator>{
Pub:
() => Pub.test(
fileSystem: globals.fs,
logger: globals.logger,
processManager: globals.processManager,
botDetector: globals.botDetector,
platform: globals.platform,
stdio: globals.stdio,
),
},
);
// See: https://github.com/dart-lang/dart-services/pull/874
testUsingContext(
'generated plugin registrant for dartpad is created on pub get',
() async {
await _createProject(projectDir, <String>[]);
await _editPubspecFile(
projectDir,
_addDependencyEditor('shared_preferences', version: '^2.0.0'),
);
// The plugin registrant for dartpad is created on flutter pub get.
await _doFlutterPubGet(projectDir);
final File registrant = projectDir
.childDirectory('.dart_tool/dartpad')
.childFile('web_plugin_registrant.dart');
// Ensure the file exists, and passes analysis.
expect(registrant, exists);
await _analyzeEntity(registrant);
// Assert the full build hasn't happened!
final Directory buildDir = projectDir.childDirectory('.dart_tool/flutter_build');
expect(buildDir, isNot(exists));
},
overrides: <Type, Generator>{
Pub:
() => Pub.test(
fileSystem: globals.fs,
logger: globals.logger,
processManager: globals.processManager,
botDetector: globals.botDetector,
platform: globals.platform,
stdio: globals.stdio,
),
},
);
testUsingContext(
'generated plugin registrant ignores lines longer than 80 chars',
() async {
await _createProject(projectDir, <String>[]);
await _addAnalysisOptions(projectDir, <String>['lines_longer_than_80_chars']);
await _createProject(tempDir.childDirectory('test_plugin'), <String>[
'--template=plugin',
'--platforms=web',
'--project-name',
'test_web_plugin_with_a_purposefully_extremely_long_package_name',
]);
// The line for the test web plugin (` TestWebPluginWithAPurposefullyExtremelyLongPackageNameWeb.registerWith(registrar);`)
// exceeds 80 chars.
// With the above lint rule added, we want to ensure that the `generated_plugin_registrant.dart`
// file does not fail analysis (this is a regression test - an ignore was
// added to cover this case).
await _editPubspecFile(
projectDir,
_addDependencyEditor(
'test_web_plugin_with_a_purposefully_extremely_long_package_name',
path: '../test_plugin',
),
);
// The plugin registrant is only created after a build...
await _buildWebProject(projectDir);
// Find the web_plugin_registrant, now that it lives outside "lib":
final Directory buildDir =
projectDir
.childDirectory('.dart_tool/flutter_build')
.listSync()
.firstWhere((FileSystemEntity entity) => entity is Directory)
as Directory;
expect(buildDir.childFile('web_plugin_registrant.dart'), exists);
await _analyzeEntity(buildDir.childFile('web_plugin_registrant.dart'));
},
overrides: <Type, Generator>{
Pub:
() => Pub.test(
fileSystem: globals.fs,
logger: globals.logger,
processManager: globals.processManager,
botDetector: globals.botDetector,
platform: globals.platform,
stdio: globals.stdio,
),
},
);
}
Future<void> _createProject(Directory dir, List<String> createArgs) async {
Cache.flutterRoot = '../..';
final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['create', ...createArgs, dir.path]);
}
typedef PubspecEditor = void Function(List<String> pubSpecContents);
Future<void> _editPubspecFile(Directory projectDir, PubspecEditor editor) async {
final File pubspecYaml = projectDir.childFile('pubspec.yaml');
expect(pubspecYaml, exists);
final List<String> lines = await pubspecYaml.readAsLines();
editor(lines);
await pubspecYaml.writeAsString(lines.join('\n'));
}
Future<void> _replaceMainFile(Directory projectDir, String fileContents) async {
final File mainFile = projectDir.childDirectory('lib').childFile('main.dart');
await mainFile.writeAsString(fileContents);
}
PubspecEditor _addDependencyEditor(String packageToAdd, {String? version, String? path}) {
assert(version != null || path != null, 'Need to define a source for the package.');
assert(
version == null || path == null,
'Cannot only load a package from path or from Pub, not both.',
);
void editor(List<String> lines) {
for (int i = 0; i < lines.length; i++) {
final String line = lines[i];
if (line.startsWith('dependencies:')) {
lines.insert(i + 1, ' $packageToAdd: ${version ?? '\n'
' path: $path'}');
break;
}
}
}
return editor;
}
PubspecEditor _setDartSDKVersionEditor(String version) {
void editor(List<String> lines) {
for (int i = 0; i < lines.length; i++) {
final String line = lines[i];
if (line.startsWith('environment:')) {
for (i++; i < lines.length; i++) {
final String innerLine = lines[i];
final String sdkLine = " sdk: '$version'";
if (innerLine.isNotEmpty && !innerLine.startsWith(' ')) {
lines.insert(i, sdkLine);
break;
}
if (innerLine.startsWith(' sdk:')) {
lines[i] = sdkLine;
break;
}
}
break;
}
}
}
return editor;
}
PubspecEditor _composeEditors(Iterable<PubspecEditor> editors) {
void composedEditor(List<String> lines) {
for (final PubspecEditor editor in editors) {
editor(lines);
}
}
return composedEditor;
}
Future<void> _addAnalysisOptions(Directory projectDir, List<String> linterRules) async {
assert(linterRules.isNotEmpty);
await projectDir.childFile('analysis_options.yaml').writeAsString('''
linter:
rules:
${linterRules.map((String rule) => ' - $rule').join('\n')}
''');
}
Future<void> _analyzeEntity(FileSystemEntity target) async {
final String flutterToolsSnapshotPath = globals.fs.path.absolute(
globals.fs.path.join('..', '..', 'bin', 'cache', 'flutter_tools.snapshot'),
);
final List<String> args = <String>[flutterToolsSnapshotPath, 'analyze', target.path];
final ProcessResult exec = await Process.run(
globals.artifacts!.getArtifactPath(
Artifact.engineDartBinary,
platform: TargetPlatform.web_javascript,
),
args,
workingDirectory: target is Directory ? target.path : target.dirname,
);
expect(exec, const ProcessResultMatcher());
}
Future<void> _buildWebProject(Directory workingDir) async {
return _runFlutterSnapshot(<String>['build', 'web'], workingDir);
}
Future<void> _doFlutterPubGet(Directory workingDir) async {
return _runFlutterSnapshot(<String>['pub', 'get'], workingDir);
}
// Runs a flutter command from a snapshot build.
// `flutterCommandArgs` are the arguments passed to flutter, like: ['build', 'web']
// to run `flutter build web`.
// `workingDir` is the directory on which the flutter command will be run.
Future<void> _runFlutterSnapshot(List<String> flutterCommandArgs, Directory workingDir) async {
final String flutterToolsSnapshotPath = globals.fs.path.absolute(
globals.fs.path.join('..', '..', 'bin', 'cache', 'flutter_tools.snapshot'),
);
final List<String> args = <String>[
globals.artifacts!.getArtifactPath(
Artifact.engineDartBinary,
platform: TargetPlatform.web_javascript,
),
flutterToolsSnapshotPath,
...flutterCommandArgs,
];
final ProcessResult exec = await globals.processManager.run(
args,
workingDirectory: workingDir.path,
);
expect(exec, const ProcessResultMatcher());
}