Files
packages/script/tool/test/make_deps_path_based_command_test.dart
stuartmorgan 5cc71d00ac Update Flutter wiki links (#6789)
Updates all links to the Flutter wiki to point to their new location in the flutter/flutter repository. (The sole exception is a link to a doc that doesn't have a final home yet, and is linked from legacy code anyway so doesn't really need to be updated.)

While touching the PR template, makes a few minor improvements:
- Removes the breaking change discussion that doesn't apply to this repository, as breaking changes are handled totally differently for packages (and is covered by the link to docs about Dart versioning).
- Adds text and a link to reflect the fact that some PRs can be changelog-exempt.
2024-05-26 12:47:23 +00:00

675 lines
25 KiB
Dart

// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:args/command_runner.dart';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_plugin_tools/src/make_deps_path_based_command.dart';
import 'package:mockito/mockito.dart';
import 'package:pubspec_parse/pubspec_parse.dart';
import 'package:test/test.dart';
import 'common/package_command_test.mocks.dart';
import 'mocks.dart';
import 'util.dart';
void main() {
FileSystem fileSystem;
late Directory packagesDir;
late Directory thirdPartyPackagesDir;
late CommandRunner<void> runner;
late RecordingProcessRunner processRunner;
setUp(() {
fileSystem = MemoryFileSystem();
packagesDir = createPackagesDirectory(fileSystem: fileSystem);
thirdPartyPackagesDir = packagesDir.parent
.childDirectory('third_party')
.childDirectory('packages');
final MockGitDir gitDir = MockGitDir();
when(gitDir.path).thenReturn(packagesDir.parent.path);
when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError')))
.thenAnswer((Invocation invocation) {
final List<String> arguments =
invocation.positionalArguments[0]! as List<String>;
// Route git calls through the process runner, to make mock output
// consistent with other processes. Attach the first argument to the
// command to make targeting the mock results easier.
final String gitCommand = arguments.removeAt(0);
return processRunner.run('git-$gitCommand', arguments);
});
processRunner = RecordingProcessRunner();
final MakeDepsPathBasedCommand command =
MakeDepsPathBasedCommand(packagesDir, gitDir: gitDir);
runner = CommandRunner<void>(
'make-deps-path-based_command', 'Test for $MakeDepsPathBasedCommand');
runner.addCommand(command);
});
/// Adds dummy 'dependencies:' entries for each package in [dependencies]
/// to [package].
void addDependencies(RepositoryPackage package, Iterable<String> dependencies,
{String constraint = '<2.0.0'}) {
final List<String> lines = package.pubspecFile.readAsLinesSync();
final int dependenciesStartIndex = lines.indexOf('dependencies:');
assert(dependenciesStartIndex != -1);
lines.insertAll(dependenciesStartIndex + 1, <String>[
for (final String dependency in dependencies)
' $dependency: $constraint',
]);
package.pubspecFile.writeAsStringSync(lines.join('\n'));
}
/// Adds a 'dev_dependencies:' section with entries for each package in
/// [dependencies] to [package].
void addDevDependenciesSection(
RepositoryPackage package, Iterable<String> devDependencies,
{String constraint = '<2.0.0'}) {
final String originalContent = package.pubspecFile.readAsStringSync();
package.pubspecFile.writeAsStringSync('''
$originalContent
dev_dependencies:
${devDependencies.map((String dep) => ' $dep: $constraint').join('\n')}
''');
}
Map<String, String> getDependencyOverrides(RepositoryPackage package) {
final Pubspec pubspec = package.parsePubspec();
return pubspec.dependencyOverrides.map((String name, Dependency dep) =>
MapEntry<String, String>(
name, (dep is PathDependency) ? dep.path : dep.toString()));
}
test('no-ops for no plugins', () async {
createFakePackage('foo', packagesDir, isFlutter: true);
final RepositoryPackage packageBar =
createFakePackage('bar', packagesDir, isFlutter: true);
addDependencies(packageBar, <String>['foo']);
final String originalPubspecContents =
packageBar.pubspecFile.readAsStringSync();
final List<String> output =
await runCapturingPrint(runner, <String>['make-deps-path-based']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('No target dependencies'),
]),
);
// The 'foo' reference should not have been modified.
expect(packageBar.pubspecFile.readAsStringSync(), originalPubspecContents);
});
test('includes explanatory comment', () async {
final RepositoryPackage packageA =
createFakePackage('package_a', packagesDir, isFlutter: true);
createFakePackage('package_b', packagesDir, isFlutter: true);
addDependencies(packageA, <String>[
'package_b',
]);
await runCapturingPrint(runner,
<String>['make-deps-path-based', '--target-dependencies=package_b']);
expect(
packageA.pubspecFile.readAsLinesSync(),
containsAllInOrder(<String>[
'# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.',
'# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins',
'dependency_overrides:',
]));
});
test('rewrites "dependencies" references', () async {
final RepositoryPackage simplePackage =
createFakePackage('foo', packagesDir, isFlutter: true);
final Directory pluginGroup = packagesDir.childDirectory('bar');
createFakePackage('bar_platform_interface', pluginGroup, isFlutter: true);
final RepositoryPackage pluginImplementation =
createFakePlugin('bar_android', pluginGroup);
final RepositoryPackage pluginAppFacing =
createFakePlugin('bar', pluginGroup);
addDependencies(simplePackage, <String>[
'bar',
'bar_android',
'bar_platform_interface',
]);
addDependencies(pluginAppFacing, <String>[
'bar_platform_interface',
'bar_android',
]);
addDependencies(pluginImplementation, <String>[
'bar_platform_interface',
]);
final List<String> output = await runCapturingPrint(runner, <String>[
'make-deps-path-based',
'--target-dependencies=bar,bar_platform_interface'
]);
expect(
output,
containsAll(<String>[
'Rewriting references to: bar, bar_platform_interface...',
' Modified packages/bar/bar/pubspec.yaml',
' Modified packages/bar/bar_android/pubspec.yaml',
' Modified packages/foo/pubspec.yaml',
]));
expect(
output,
isNot(contains(
' Modified packages/bar/bar_platform_interface/pubspec.yaml')));
final Map<String, String?> simplePackageOverrides =
getDependencyOverrides(simplePackage);
expect(simplePackageOverrides.length, 2);
expect(simplePackageOverrides['bar'], '../bar/bar');
expect(simplePackageOverrides['bar_platform_interface'],
'../bar/bar_platform_interface');
final Map<String, String?> appFacingPackageOverrides =
getDependencyOverrides(pluginAppFacing);
expect(appFacingPackageOverrides.length, 1);
expect(appFacingPackageOverrides['bar_platform_interface'],
'../../bar/bar_platform_interface');
});
test('rewrites "dev_dependencies" references', () async {
createFakePackage('foo', packagesDir);
final RepositoryPackage builderPackage =
createFakePackage('foo_builder', packagesDir);
addDevDependenciesSection(builderPackage, <String>[
'foo',
]);
final List<String> output = await runCapturingPrint(
runner, <String>['make-deps-path-based', '--target-dependencies=foo']);
expect(
output,
containsAll(<String>[
'Rewriting references to: foo...',
' Modified packages/foo_builder/pubspec.yaml',
]));
final Map<String, String?> overrides =
getDependencyOverrides(builderPackage);
expect(overrides.length, 1);
expect(overrides['foo'], '../foo');
});
test('rewrites examples when rewriting the main package', () async {
final Directory pluginGroup = packagesDir.childDirectory('bar');
createFakePackage('bar_platform_interface', pluginGroup, isFlutter: true);
final RepositoryPackage pluginImplementation =
createFakePlugin('bar_android', pluginGroup);
final RepositoryPackage pluginAppFacing =
createFakePlugin('bar', pluginGroup);
addDependencies(pluginAppFacing, <String>[
'bar_platform_interface',
'bar_android',
]);
addDependencies(pluginImplementation, <String>[
'bar_platform_interface',
]);
await runCapturingPrint(runner,
<String>['make-deps-path-based', '--target-dependencies=bar_android']);
final Map<String, String?> exampleOverrides =
getDependencyOverrides(pluginAppFacing.getExamples().first);
expect(exampleOverrides.length, 1);
expect(exampleOverrides['bar_android'], '../../../bar/bar_android');
});
test('example overrides include both local and main-package dependencies',
() async {
final Directory pluginGroup = packagesDir.childDirectory('bar');
createFakePackage('bar_platform_interface', pluginGroup, isFlutter: true);
createFakePlugin('bar_android', pluginGroup);
final RepositoryPackage pluginAppFacing =
createFakePlugin('bar', pluginGroup);
createFakePackage('another_package', packagesDir);
addDependencies(pluginAppFacing, <String>[
'bar_platform_interface',
'bar_android',
]);
addDependencies(pluginAppFacing.getExamples().first, <String>[
'another_package',
]);
await runCapturingPrint(runner, <String>[
'make-deps-path-based',
'--target-dependencies=bar_android,another_package'
]);
final Map<String, String?> exampleOverrides =
getDependencyOverrides(pluginAppFacing.getExamples().first);
expect(exampleOverrides.length, 2);
expect(exampleOverrides['another_package'], '../../../another_package');
expect(exampleOverrides['bar_android'], '../../../bar/bar_android');
});
test(
'alphabetizes overrides from different sections to avoid lint warnings in analysis',
() async {
createFakePackage('a', packagesDir);
createFakePackage('b', packagesDir);
createFakePackage('c', packagesDir);
final RepositoryPackage targetPackage =
createFakePackage('target', packagesDir);
addDependencies(targetPackage, <String>['a', 'c']);
addDevDependenciesSection(targetPackage, <String>['b']);
final List<String> output = await runCapturingPrint(runner,
<String>['make-deps-path-based', '--target-dependencies=c,a,b']);
expect(
output,
containsAllInOrder(<String>[
'Rewriting references to: c, a, b...',
' Modified packages/target/pubspec.yaml',
]));
// This matches with a regex in order to all for either flow style or
// expanded style output.
expect(
targetPackage.pubspecFile.readAsStringSync(),
matches(RegExp(r'dependency_overrides:.*a:.*b:.*c:.*',
multiLine: true, dotAll: true)));
});
test('finds third_party packages', () async {
createFakePackage('bar', thirdPartyPackagesDir, isFlutter: true);
final RepositoryPackage firstPartyPackge =
createFakePlugin('foo', packagesDir);
addDependencies(firstPartyPackge, <String>[
'bar',
]);
final List<String> output = await runCapturingPrint(
runner, <String>['make-deps-path-based', '--target-dependencies=bar']);
expect(
output,
containsAll(<String>[
'Rewriting references to: bar...',
' Modified packages/foo/pubspec.yaml',
]));
final Map<String, String?> simplePackageOverrides =
getDependencyOverrides(firstPartyPackge);
expect(simplePackageOverrides.length, 1);
expect(simplePackageOverrides['bar'], '../../third_party/packages/bar');
});
// This test case ensures that running CI using this command on an interim
// PR that itself used this command won't fail on the rewrite step.
test('running a second time no-ops without failing', () async {
final RepositoryPackage simplePackage =
createFakePackage('foo', packagesDir, isFlutter: true);
final Directory pluginGroup = packagesDir.childDirectory('bar');
createFakePackage('bar_platform_interface', pluginGroup, isFlutter: true);
final RepositoryPackage pluginImplementation =
createFakePlugin('bar_android', pluginGroup);
final RepositoryPackage pluginAppFacing =
createFakePlugin('bar', pluginGroup);
addDependencies(simplePackage, <String>[
'bar',
'bar_android',
'bar_platform_interface',
]);
addDependencies(pluginAppFacing, <String>[
'bar_platform_interface',
'bar_android',
]);
addDependencies(pluginImplementation, <String>[
'bar_platform_interface',
]);
await runCapturingPrint(runner, <String>[
'make-deps-path-based',
'--target-dependencies=bar,bar_platform_interface'
]);
final String simplePackageUpdatedContent =
simplePackage.pubspecFile.readAsStringSync();
final String appFacingPackageUpdatedContent =
pluginAppFacing.pubspecFile.readAsStringSync();
final String implementationPackageUpdatedContent =
pluginImplementation.pubspecFile.readAsStringSync();
final List<String> output = await runCapturingPrint(runner, <String>[
'make-deps-path-based',
'--target-dependencies=bar,bar_platform_interface'
]);
expect(
output,
containsAll(<String>[
'Rewriting references to: bar, bar_platform_interface...',
' Modified packages/bar/bar/pubspec.yaml',
' Modified packages/bar/bar_android/pubspec.yaml',
' Modified packages/foo/pubspec.yaml',
]));
expect(simplePackageUpdatedContent,
simplePackage.pubspecFile.readAsStringSync());
expect(appFacingPackageUpdatedContent,
pluginAppFacing.pubspecFile.readAsStringSync());
expect(implementationPackageUpdatedContent,
pluginImplementation.pubspecFile.readAsStringSync());
});
group('target-dependencies-with-non-breaking-updates', () {
test('no-ops for no published changes', () async {
final RepositoryPackage package = createFakePackage('foo', packagesDir);
final String changedFileOutput = <File>[
package.pubspecFile,
].map((File file) => file.path).join('\n');
processRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: changedFileOutput)),
];
// Simulate no change to the version in the interface's pubspec.yaml.
processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[
FakeProcessInfo(
MockProcess(stdout: package.pubspecFile.readAsStringSync())),
];
final List<String> output = await runCapturingPrint(runner, <String>[
'make-deps-path-based',
'--target-dependencies-with-non-breaking-updates'
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('No target dependencies'),
]),
);
});
test('no-ops for no deleted packages', () async {
final String changedFileOutput = <File>[
// A change for a file that's not on disk simulates a deletion.
packagesDir.childDirectory('foo').childFile('pubspec.yaml'),
].map((File file) => file.path).join('\n');
processRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: changedFileOutput)),
];
final List<String> output = await runCapturingPrint(runner, <String>[
'make-deps-path-based',
'--target-dependencies-with-non-breaking-updates'
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Skipping foo; deleted.'),
contains('No target dependencies'),
]),
);
});
test('includes bugfix version changes as targets', () async {
const String newVersion = '1.0.1';
final RepositoryPackage package =
createFakePackage('foo', packagesDir, version: newVersion);
final File pubspecFile = package.pubspecFile;
final String changedFileOutput = <File>[
pubspecFile,
].map((File file) => file.path).join('\n');
processRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: changedFileOutput)),
];
final String gitPubspecContents =
pubspecFile.readAsStringSync().replaceAll(newVersion, '1.0.0');
// Simulate no change to the version in the interface's pubspec.yaml.
processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: gitPubspecContents)),
];
final List<String> output = await runCapturingPrint(runner, <String>[
'make-deps-path-based',
'--target-dependencies-with-non-breaking-updates'
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Rewriting references to: foo...'),
]),
);
});
test('includes minor version changes to 1.0+ as targets', () async {
const String newVersion = '1.1.0';
final RepositoryPackage package =
createFakePackage('foo', packagesDir, version: newVersion);
final File pubspecFile = package.pubspecFile;
final String changedFileOutput = <File>[
pubspecFile,
].map((File file) => file.path).join('\n');
processRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: changedFileOutput)),
];
final String gitPubspecContents =
pubspecFile.readAsStringSync().replaceAll(newVersion, '1.0.0');
// Simulate no change to the version in the interface's pubspec.yaml.
processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: gitPubspecContents)),
];
final List<String> output = await runCapturingPrint(runner, <String>[
'make-deps-path-based',
'--target-dependencies-with-non-breaking-updates'
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Rewriting references to: foo...'),
]),
);
});
test('does not include major version changes as targets', () async {
const String newVersion = '2.0.0';
final RepositoryPackage package =
createFakePackage('foo', packagesDir, version: newVersion);
final File pubspecFile = package.pubspecFile;
final String changedFileOutput = <File>[
pubspecFile,
].map((File file) => file.path).join('\n');
processRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: changedFileOutput)),
];
final String gitPubspecContents =
pubspecFile.readAsStringSync().replaceAll(newVersion, '1.0.0');
// Simulate no change to the version in the interface's pubspec.yaml.
processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: gitPubspecContents)),
];
final List<String> output = await runCapturingPrint(runner, <String>[
'make-deps-path-based',
'--target-dependencies-with-non-breaking-updates'
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('No target dependencies'),
]),
);
});
test('does not include minor version changes to 0.x as targets', () async {
const String newVersion = '0.8.0';
final RepositoryPackage package =
createFakePackage('foo', packagesDir, version: newVersion);
final File pubspecFile = package.pubspecFile;
final String changedFileOutput = <File>[
pubspecFile,
].map((File file) => file.path).join('\n');
processRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: changedFileOutput)),
];
final String gitPubspecContents =
pubspecFile.readAsStringSync().replaceAll(newVersion, '0.7.0');
// Simulate no change to the version in the interface's pubspec.yaml.
processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: gitPubspecContents)),
];
final List<String> output = await runCapturingPrint(runner, <String>[
'make-deps-path-based',
'--target-dependencies-with-non-breaking-updates'
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('No target dependencies'),
]),
);
});
test('does not update references with an older major version', () async {
const String newVersion = '2.0.1';
final RepositoryPackage targetPackage =
createFakePackage('foo', packagesDir, version: newVersion);
final RepositoryPackage referencingPackage =
createFakePackage('bar', packagesDir);
// For a dependency on ^1.0.0, the 2.0.0->2.0.1 update should not apply.
addDependencies(referencingPackage, <String>['foo'],
constraint: '^1.0.0');
final File pubspecFile = targetPackage.pubspecFile;
final String changedFileOutput = <File>[
pubspecFile,
].map((File file) => file.path).join('\n');
processRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: changedFileOutput)),
];
final String gitPubspecContents =
pubspecFile.readAsStringSync().replaceAll(newVersion, '2.0.0');
processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: gitPubspecContents)),
];
final List<String> output = await runCapturingPrint(runner, <String>[
'make-deps-path-based',
'--target-dependencies-with-non-breaking-updates'
]);
final Pubspec referencingPubspec = referencingPackage.parsePubspec();
expect(
output,
containsAllInOrder(<Matcher>[
contains('Rewriting references to: foo'),
]),
);
expect(referencingPubspec.dependencyOverrides.isEmpty, true);
});
test('does update references with a matching version range', () async {
const String newVersion = '2.0.1';
final RepositoryPackage targetPackage =
createFakePackage('foo', packagesDir, version: newVersion);
final RepositoryPackage referencingPackage =
createFakePackage('bar', packagesDir);
// For a dependency on ^1.0.0, the 2.0.0->2.0.1 update should not apply.
addDependencies(referencingPackage, <String>['foo'],
constraint: '^2.0.0');
final File pubspecFile = targetPackage.pubspecFile;
final String changedFileOutput = <File>[
pubspecFile,
].map((File file) => file.path).join('\n');
processRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: changedFileOutput)),
];
final String gitPubspecContents =
pubspecFile.readAsStringSync().replaceAll(newVersion, '2.0.0');
processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: gitPubspecContents)),
];
final List<String> output = await runCapturingPrint(runner, <String>[
'make-deps-path-based',
'--target-dependencies-with-non-breaking-updates'
]);
final Pubspec referencingPubspec = referencingPackage.parsePubspec();
expect(
output,
containsAllInOrder(<Matcher>[
contains('Rewriting references to: foo'),
]),
);
expect(referencingPubspec.dependencyOverrides['foo'] is PathDependency,
true);
});
test('skips anything outside of the packages directory', () async {
final Directory toolDir = packagesDir.parent.childDirectory('tool');
const String newVersion = '1.1.0';
final RepositoryPackage package = createFakePackage(
'flutter_plugin_tools', toolDir,
version: newVersion);
// Simulate a minor version change so it would be a target.
final File pubspecFile = package.pubspecFile;
final String changedFileOutput = <File>[
pubspecFile,
].map((File file) => file.path).join('\n');
processRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: changedFileOutput)),
];
final String gitPubspecContents =
pubspecFile.readAsStringSync().replaceAll(newVersion, '1.0.0');
processRunner.mockProcessesForExecutable['git-show'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: gitPubspecContents)),
];
final List<String> output = await runCapturingPrint(runner, <String>[
'make-deps-path-based',
'--target-dependencies-with-non-breaking-updates'
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'Skipping /tool/flutter_plugin_tools/pubspec.yaml; not in packages directory.'),
contains('No target dependencies'),
]),
);
});
});
}