mirror of
https://github.com/flutter/packages.git
synced 2025-05-22 03:06:49 +08:00

Adds a new tool command (and runs it in CI) to check that each package: - is listed correctly in the repo-level README.md table - has a CODEOWNERS entry In the future we could add other things (e.g., auto-label), but these were the main things we've had issues with recently. Updates README.md and CODEOWNERS to fix failures it found: - Adds a couple of missing CODEOWNERS - Expands the web implementation CODEOWNERS to individual packages so that we don't have to special-case handling in the tool - Fixes some minor mistakes in README.md - URL-encodes all `:`s in the README.md links (which is why ever line shows as changed); it worked without that in practice, but it should really be encoded, and having it consistently encoded made things easier for the tooling.
417 lines
17 KiB
Dart
417 lines
17 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/common/core.dart';
|
|
import 'package:flutter_plugin_tools/src/repo_package_info_check_command.dart';
|
|
import 'package:mockito/mockito.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
import 'common/package_command_test.mocks.dart';
|
|
import 'util.dart';
|
|
|
|
void main() {
|
|
late CommandRunner<void> runner;
|
|
late FileSystem fileSystem;
|
|
late Directory root;
|
|
late Directory packagesDir;
|
|
|
|
setUp(() {
|
|
fileSystem = MemoryFileSystem();
|
|
root = fileSystem.currentDirectory;
|
|
packagesDir = root.childDirectory('packages');
|
|
|
|
final MockGitDir gitDir = MockGitDir();
|
|
when(gitDir.path).thenReturn(root.path);
|
|
|
|
final RepoPackageInfoCheckCommand command = RepoPackageInfoCheckCommand(
|
|
packagesDir,
|
|
gitDir: gitDir,
|
|
);
|
|
runner = CommandRunner<void>(
|
|
'dependabot_test', 'Test for $RepoPackageInfoCheckCommand');
|
|
runner.addCommand(command);
|
|
});
|
|
|
|
String readmeTableHeader() {
|
|
return '''
|
|
| Package | Pub | Points | Popularity | Issues | Pull requests |
|
|
|---------|-----|--------|------------|--------|---------------|
|
|
''';
|
|
}
|
|
|
|
void writeCodeOwners(List<RepositoryPackage> ownedPackages) {
|
|
final List<String> subpaths = ownedPackages
|
|
.map((RepositoryPackage p) => p.isFederated
|
|
? <String>[p.directory.parent.basename, p.directory.basename]
|
|
.join('/')
|
|
: p.directory.basename)
|
|
.toList();
|
|
root.childFile('CODEOWNERS').writeAsStringSync('''
|
|
${subpaths.map((String subpath) => 'packages/$subpath/** @someone').join('\n')}
|
|
''');
|
|
}
|
|
|
|
String readmeTableEntry(String packageName) {
|
|
final String encodedTag = Uri.encodeComponent('p: $packageName');
|
|
return '| [$packageName](./packages/$packageName/) | '
|
|
'[](https://pub.dev/packages/$packageName) | '
|
|
'[](https://pub.dev/packages/$packageName/score) | '
|
|
'[](https://pub.dev/packages/$packageName/score) | '
|
|
'[](https://github.com/flutter/flutter/labels/$encodedTag) | '
|
|
'[](https://github.com/flutter/packages/labels/$encodedTag) |';
|
|
}
|
|
|
|
test('passes for correct README coverage', () async {
|
|
final List<RepositoryPackage> packages = <RepositoryPackage>[
|
|
createFakePackage('a_package', packagesDir),
|
|
];
|
|
|
|
root.childFile('README.md').writeAsStringSync('''
|
|
${readmeTableHeader()}
|
|
${readmeTableEntry('a_package')}
|
|
''');
|
|
writeCodeOwners(packages);
|
|
|
|
final List<String> output =
|
|
await runCapturingPrint(runner, <String>['repo-package-info-check']);
|
|
|
|
expect(output,
|
|
containsAllInOrder(<Matcher>[contains('Ran for 1 package(s)')]));
|
|
});
|
|
|
|
test('passes for federated plugins with only app-facing package listed',
|
|
() async {
|
|
const String pluginName = 'foo';
|
|
final Directory pluginDir = packagesDir.childDirectory(pluginName);
|
|
final List<RepositoryPackage> packages = <RepositoryPackage>[
|
|
createFakePlugin(pluginName, pluginDir),
|
|
createFakePlugin('${pluginName}_platform_interface', pluginDir),
|
|
createFakePlugin('${pluginName}_android', pluginDir),
|
|
createFakePlugin('${pluginName}_ios', pluginDir),
|
|
];
|
|
|
|
root.childFile('README.md').writeAsStringSync('''
|
|
${readmeTableHeader()}
|
|
${readmeTableEntry(pluginName)}
|
|
''');
|
|
writeCodeOwners(packages);
|
|
|
|
final List<String> output =
|
|
await runCapturingPrint(runner, <String>['repo-package-info-check']);
|
|
|
|
expect(output,
|
|
containsAllInOrder(<Matcher>[contains('Ran for 4 package(s)')]));
|
|
});
|
|
|
|
test('fails for unexpected README table entry', () async {
|
|
final List<RepositoryPackage> packages = <RepositoryPackage>[
|
|
createFakePackage('a_package', packagesDir),
|
|
];
|
|
|
|
root.childFile('README.md').writeAsStringSync('''
|
|
${readmeTableHeader()}
|
|
${readmeTableEntry('another_package')}
|
|
''');
|
|
writeCodeOwners(packages);
|
|
|
|
Error? commandError;
|
|
final List<String> output = await runCapturingPrint(
|
|
runner, <String>['repo-package-info-check'], errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('Unknown package "another_package" in root README.md table'),
|
|
]));
|
|
});
|
|
|
|
test('fails for missing README table entry', () async {
|
|
final List<RepositoryPackage> packages = <RepositoryPackage>[
|
|
createFakePackage('a_package', packagesDir),
|
|
createFakePackage('another_package', packagesDir),
|
|
];
|
|
|
|
root.childFile('README.md').writeAsStringSync('''
|
|
${readmeTableHeader()}
|
|
${readmeTableEntry('another_package')}
|
|
''');
|
|
writeCodeOwners(packages);
|
|
|
|
Error? commandError;
|
|
final List<String> output = await runCapturingPrint(
|
|
runner, <String>['repo-package-info-check'], errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('Missing repo root README.md table entry'),
|
|
contains('a_package:\n'
|
|
' Missing repo root README.md table entry')
|
|
]));
|
|
});
|
|
|
|
test('fails for unexpected format in README table entry', () async {
|
|
const String packageName = 'a_package';
|
|
final String encodedTag = Uri.encodeComponent('p: $packageName');
|
|
final List<RepositoryPackage> packages = <RepositoryPackage>[
|
|
createFakePackage('a_package', packagesDir),
|
|
];
|
|
|
|
final String entry = '| [$packageName](./packages/$packageName/) | '
|
|
'Some random text | '
|
|
'[](https://pub.dev/packages/$packageName/score) | '
|
|
'[](https://pub.dev/packages/$packageName/score) | '
|
|
'[](https://github.com/flutter/flutter/labels/$encodedTag) | '
|
|
'[](https://github.com/flutter/packages/labels/$encodedTag) |';
|
|
|
|
root.childFile('README.md').writeAsStringSync('''
|
|
${readmeTableHeader()}
|
|
$entry
|
|
''');
|
|
writeCodeOwners(packages);
|
|
|
|
Error? commandError;
|
|
final List<String> output = await runCapturingPrint(
|
|
runner, <String>['repo-package-info-check'], errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains(
|
|
'Invalid repo root README.md table entry: "Some random text"'),
|
|
contains('a_package:\n'
|
|
' Invalid root README.md table entry')
|
|
]));
|
|
});
|
|
|
|
test('fails for incorrect source link in README table entry', () async {
|
|
const String packageName = 'a_package';
|
|
final String encodedTag = Uri.encodeComponent('p: $packageName');
|
|
const String incorrectPackageName = 'a_pakage';
|
|
final List<RepositoryPackage> packages = <RepositoryPackage>[
|
|
createFakePackage('a_package', packagesDir),
|
|
];
|
|
|
|
final String entry =
|
|
'| [$packageName](./packages/$incorrectPackageName/) | '
|
|
'[](https://pub.dev/packages/$packageName) | '
|
|
'[](https://pub.dev/packages/$packageName/score) | '
|
|
'[](https://pub.dev/packages/$packageName/score) | '
|
|
'[](https://github.com/flutter/flutter/labels/$encodedTag) | '
|
|
'[](https://github.com/flutter/packages/labels/$encodedTag) |';
|
|
|
|
root.childFile('README.md').writeAsStringSync('''
|
|
${readmeTableHeader()}
|
|
$entry
|
|
''');
|
|
writeCodeOwners(packages);
|
|
|
|
Error? commandError;
|
|
final List<String> output = await runCapturingPrint(
|
|
runner, <String>['repo-package-info-check'], errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains(
|
|
'Incorrect link in root README.md table: "./packages/$incorrectPackageName/"'),
|
|
contains('a_package:\n'
|
|
' Incorrect link in root README.md table')
|
|
]));
|
|
});
|
|
|
|
test('fails for incorrect packages/* link in README table entry', () async {
|
|
const String packageName = 'a_package';
|
|
final String encodedTag = Uri.encodeComponent('p: $packageName');
|
|
const String incorrectPackageName = 'a_pakage';
|
|
final List<RepositoryPackage> packages = <RepositoryPackage>[
|
|
createFakePackage('a_package', packagesDir),
|
|
];
|
|
|
|
final String entry = '| [$packageName](./packages/$packageName/) | '
|
|
'[](https://pub.dev/packages/$packageName) | '
|
|
'[](https://pub.dev/packages/$incorrectPackageName/score) | '
|
|
'[](https://pub.dev/packages/$packageName/score) | '
|
|
'[](https://github.com/flutter/flutter/labels/$encodedTag) | '
|
|
'[](https://github.com/flutter/packages/labels/$encodedTag) |';
|
|
|
|
root.childFile('README.md').writeAsStringSync('''
|
|
${readmeTableHeader()}
|
|
$entry
|
|
''');
|
|
writeCodeOwners(packages);
|
|
|
|
Error? commandError;
|
|
final List<String> output = await runCapturingPrint(
|
|
runner, <String>['repo-package-info-check'], errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains(
|
|
'Incorrect link in root README.md table: "https://pub.dev/packages/$incorrectPackageName/score"'),
|
|
contains('a_package:\n'
|
|
' Incorrect link in root README.md table')
|
|
]));
|
|
});
|
|
|
|
test('fails for incorrect labels/* link in README table entry', () async {
|
|
const String packageName = 'a_package';
|
|
final String encodedTag = Uri.encodeComponent('p: $packageName');
|
|
final String incorrectTag = Uri.encodeComponent('p: a_pakage');
|
|
final List<RepositoryPackage> packages = <RepositoryPackage>[
|
|
createFakePackage('a_package', packagesDir),
|
|
];
|
|
|
|
final String entry = '| [$packageName](./packages/$packageName/) | '
|
|
'[](https://pub.dev/packages/$packageName) | '
|
|
'[](https://pub.dev/packages/$packageName/score) | '
|
|
'[](https://pub.dev/packages/$packageName/score) | '
|
|
'[](https://github.com/flutter/flutter/labels/$incorrectTag) | '
|
|
'[](https://github.com/flutter/packages/labels/$encodedTag) |';
|
|
|
|
root.childFile('README.md').writeAsStringSync('''
|
|
${readmeTableHeader()}
|
|
$entry
|
|
''');
|
|
writeCodeOwners(packages);
|
|
|
|
Error? commandError;
|
|
final List<String> output = await runCapturingPrint(
|
|
runner, <String>['repo-package-info-check'], errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains(
|
|
'Incorrect link in root README.md table: "https://github.com/flutter/flutter/labels/$incorrectTag"'),
|
|
contains('a_package:\n'
|
|
' Incorrect link in root README.md table')
|
|
]));
|
|
});
|
|
|
|
test('fails for incorrect packages/* anchor in README table entry', () async {
|
|
const String packageName = 'a_package';
|
|
final String encodedTag = Uri.encodeComponent('p: $packageName');
|
|
const String incorrectPackageName = 'a_pakage';
|
|
final List<RepositoryPackage> packages = <RepositoryPackage>[
|
|
createFakePackage('a_package', packagesDir),
|
|
];
|
|
|
|
final String entry = '| [$packageName](./packages/$packageName/) | '
|
|
'[](https://pub.dev/packages/$packageName) | '
|
|
'[](https://pub.dev/packages/$packageName/score) | '
|
|
'[](https://pub.dev/packages/$packageName/score) | '
|
|
'[](https://github.com/flutter/flutter/labels/$encodedTag) | '
|
|
'[](https://github.com/flutter/packages/labels/$encodedTag) |';
|
|
|
|
root.childFile('README.md').writeAsStringSync('''
|
|
${readmeTableHeader()}
|
|
$entry
|
|
''');
|
|
writeCodeOwners(packages);
|
|
|
|
Error? commandError;
|
|
final List<String> output = await runCapturingPrint(
|
|
runner, <String>['repo-package-info-check'], errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains(
|
|
'Incorrect anchor in root README.md table: ""'),
|
|
contains('a_package:\n'
|
|
' Incorrect anchor in root README.md table')
|
|
]));
|
|
});
|
|
|
|
test('fails for incorrect tag query anchor in README table entry', () async {
|
|
const String packageName = 'a_package';
|
|
final String encodedTag = Uri.encodeComponent('p: $packageName');
|
|
final String incorrectTag = Uri.encodeComponent('p: a_pakage');
|
|
final List<RepositoryPackage> packages = <RepositoryPackage>[
|
|
createFakePackage('a_package', packagesDir),
|
|
];
|
|
|
|
final String entry = '| [$packageName](./packages/$packageName/) | '
|
|
'[](https://pub.dev/packages/$packageName) | '
|
|
'[](https://pub.dev/packages/$packageName/score) | '
|
|
'[](https://pub.dev/packages/$packageName/score) | '
|
|
'[](https://github.com/flutter/flutter/labels/$encodedTag) | '
|
|
'[](https://github.com/flutter/packages/labels/$encodedTag) |';
|
|
|
|
root.childFile('README.md').writeAsStringSync('''
|
|
${readmeTableHeader()}
|
|
$entry
|
|
''');
|
|
writeCodeOwners(packages);
|
|
|
|
Error? commandError;
|
|
final List<String> output = await runCapturingPrint(
|
|
runner, <String>['repo-package-info-check'], errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains(
|
|
'Incorrect anchor in root README.md table: "'),
|
|
contains('a_package:\n'
|
|
' Incorrect anchor in root README.md table')
|
|
]));
|
|
});
|
|
|
|
test('fails for missing CODEOWNER', () async {
|
|
const String packageName = 'a_package';
|
|
createFakePackage(packageName, packagesDir);
|
|
|
|
root.childFile('README.md').writeAsStringSync('''
|
|
${readmeTableHeader()}
|
|
${readmeTableEntry(packageName)}
|
|
''');
|
|
writeCodeOwners(<RepositoryPackage>[]);
|
|
|
|
Error? commandError;
|
|
final List<String> output = await runCapturingPrint(
|
|
runner, <String>['repo-package-info-check'], errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('Missing CODEOWNERS entry.'),
|
|
contains('a_package:\n'
|
|
' Missing CODEOWNERS entry')
|
|
]));
|
|
});
|
|
}
|