mirror of
https://github.com/flutter/packages.git
synced 2025-06-20 14:38:40 +08:00
[ci] Ensure complete dependabot coverage (#5976)
This commit is contained in:
@ -3,6 +3,7 @@
|
|||||||
- Supports empty custom analysis allow list files.
|
- Supports empty custom analysis allow list files.
|
||||||
- `drive-examples` now validates files to ensure that they don't accidentally
|
- `drive-examples` now validates files to ensure that they don't accidentally
|
||||||
use `test(...)`.
|
use `test(...)`.
|
||||||
|
- Adds a new `dependabot-check` command to ensure complete Dependabot coverage.
|
||||||
|
|
||||||
## 0.8.6
|
## 0.8.6
|
||||||
|
|
||||||
|
114
script/tool/lib/src/dependabot_check_command.dart
Normal file
114
script/tool/lib/src/dependabot_check_command.dart
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
// 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 'dart:async';
|
||||||
|
|
||||||
|
import 'package:file/file.dart';
|
||||||
|
import 'package:git/git.dart';
|
||||||
|
import 'package:yaml/yaml.dart';
|
||||||
|
|
||||||
|
import 'common/core.dart';
|
||||||
|
import 'common/package_looping_command.dart';
|
||||||
|
import 'common/repository_package.dart';
|
||||||
|
|
||||||
|
/// A command to verify Dependabot configuration coverage of packages.
|
||||||
|
class DependabotCheckCommand extends PackageLoopingCommand {
|
||||||
|
/// Creates Dependabot check command instance.
|
||||||
|
DependabotCheckCommand(Directory packagesDir, {GitDir? gitDir})
|
||||||
|
: super(packagesDir, gitDir: gitDir) {
|
||||||
|
argParser.addOption(_configPathFlag,
|
||||||
|
help: 'Path to the Dependabot configuration file',
|
||||||
|
defaultsTo: '.github/dependabot.yml');
|
||||||
|
}
|
||||||
|
|
||||||
|
static const String _configPathFlag = 'config';
|
||||||
|
|
||||||
|
late Directory _repoRoot;
|
||||||
|
|
||||||
|
// The set of directories covered by "gradle" entries in the config.
|
||||||
|
Set<String> _gradleDirs = const <String>{};
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String name = 'dependabot-check';
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String description =
|
||||||
|
'Checks that all packages have Dependabot coverage.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
final PackageLoopingType packageLoopingType =
|
||||||
|
PackageLoopingType.includeAllSubpackages;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final bool hasLongOutput = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> initializeRun() async {
|
||||||
|
_repoRoot = packagesDir.fileSystem.directory((await gitDir).path);
|
||||||
|
|
||||||
|
final YamlMap config = loadYaml(_repoRoot
|
||||||
|
.childFile(getStringArg(_configPathFlag))
|
||||||
|
.readAsStringSync()) as YamlMap;
|
||||||
|
final dynamic entries = config['updates'];
|
||||||
|
if (entries is! YamlList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String typeKey = 'package-ecosystem';
|
||||||
|
const String dirKey = 'directory';
|
||||||
|
_gradleDirs = entries
|
||||||
|
.where((dynamic entry) => entry[typeKey] == 'gradle')
|
||||||
|
.map((dynamic entry) => (entry as YamlMap)[dirKey] as String)
|
||||||
|
.toSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PackageResult> runForPackage(RepositoryPackage package) async {
|
||||||
|
bool skipped = true;
|
||||||
|
final List<String> errors = <String>[];
|
||||||
|
|
||||||
|
final RunState gradleState = _validateDependabotGradleCoverage(package);
|
||||||
|
skipped = skipped && gradleState == RunState.skipped;
|
||||||
|
if (gradleState == RunState.failed) {
|
||||||
|
printError('${indentation}Missing Gradle coverage.');
|
||||||
|
errors.add('Missing Gradle coverage');
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(stuartmorgan): Add other ecosystem checks here as more are enabled.
|
||||||
|
|
||||||
|
if (skipped) {
|
||||||
|
return PackageResult.skip('No supported package ecosystems');
|
||||||
|
}
|
||||||
|
return errors.isEmpty
|
||||||
|
? PackageResult.success()
|
||||||
|
: PackageResult.fail(errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the state for the Dependabot coverage of the Gradle ecosystem for
|
||||||
|
/// [package]:
|
||||||
|
/// - succeeded if it includes gradle and is covered.
|
||||||
|
/// - failed if it includes gradle and is not covered.
|
||||||
|
/// - skipped if it doesn't include gradle.
|
||||||
|
RunState _validateDependabotGradleCoverage(RepositoryPackage package) {
|
||||||
|
final Directory androidDir =
|
||||||
|
package.platformDirectory(FlutterPlatform.android);
|
||||||
|
final Directory appDir = androidDir.childDirectory('app');
|
||||||
|
if (appDir.existsSync()) {
|
||||||
|
// It's an app, so only check for the app directory to be covered.
|
||||||
|
final String dependabotPath =
|
||||||
|
'/${getRelativePosixPath(appDir, from: _repoRoot)}';
|
||||||
|
return _gradleDirs.contains(dependabotPath)
|
||||||
|
? RunState.succeeded
|
||||||
|
: RunState.failed;
|
||||||
|
} else if (androidDir.existsSync()) {
|
||||||
|
// It's a library, so only check for the android directory to be covered.
|
||||||
|
final String dependabotPath =
|
||||||
|
'/${getRelativePosixPath(androidDir, from: _repoRoot)}';
|
||||||
|
return _gradleDirs.contains(dependabotPath)
|
||||||
|
? RunState.succeeded
|
||||||
|
: RunState.failed;
|
||||||
|
}
|
||||||
|
return RunState.skipped;
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ import 'dart:io' as io;
|
|||||||
import 'package:args/command_runner.dart';
|
import 'package:args/command_runner.dart';
|
||||||
import 'package:file/file.dart';
|
import 'package:file/file.dart';
|
||||||
import 'package:file/local.dart';
|
import 'package:file/local.dart';
|
||||||
|
import 'package:flutter_plugin_tools/src/dependabot_check_command.dart';
|
||||||
|
|
||||||
import 'analyze_command.dart';
|
import 'analyze_command.dart';
|
||||||
import 'build_examples_command.dart';
|
import 'build_examples_command.dart';
|
||||||
@ -55,6 +56,7 @@ void main(List<String> args) {
|
|||||||
..addCommand(BuildExamplesCommand(packagesDir))
|
..addCommand(BuildExamplesCommand(packagesDir))
|
||||||
..addCommand(CreateAllPluginsAppCommand(packagesDir))
|
..addCommand(CreateAllPluginsAppCommand(packagesDir))
|
||||||
..addCommand(CustomTestCommand(packagesDir))
|
..addCommand(CustomTestCommand(packagesDir))
|
||||||
|
..addCommand(DependabotCheckCommand(packagesDir))
|
||||||
..addCommand(DriveExamplesCommand(packagesDir))
|
..addCommand(DriveExamplesCommand(packagesDir))
|
||||||
..addCommand(FederationSafetyCheckCommand(packagesDir))
|
..addCommand(FederationSafetyCheckCommand(packagesDir))
|
||||||
..addCommand(FirebaseTestLabCommand(packagesDir))
|
..addCommand(FirebaseTestLabCommand(packagesDir))
|
||||||
|
141
script/tool/test/dependabot_check_command_test.dart
Normal file
141
script/tool/test/dependabot_check_command_test.dart
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
// 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/dependabot_check_command.dart';
|
||||||
|
import 'package:mockito/mockito.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import 'common/plugin_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 DependabotCheckCommand command = DependabotCheckCommand(
|
||||||
|
packagesDir,
|
||||||
|
gitDir: gitDir,
|
||||||
|
);
|
||||||
|
runner = CommandRunner<void>(
|
||||||
|
'dependabot_test', 'Test for $DependabotCheckCommand');
|
||||||
|
runner.addCommand(command);
|
||||||
|
});
|
||||||
|
|
||||||
|
void _setDependabotCoverage({
|
||||||
|
Iterable<String> gradleDirs = const <String>[],
|
||||||
|
}) {
|
||||||
|
final Iterable<String> gradleEntries =
|
||||||
|
gradleDirs.map((String directory) => '''
|
||||||
|
- package-ecosystem: "gradle"
|
||||||
|
directory: "/$directory"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
''');
|
||||||
|
final File configFile =
|
||||||
|
root.childDirectory('.github').childFile('dependabot.yml');
|
||||||
|
configFile.createSync(recursive: true);
|
||||||
|
configFile.writeAsStringSync('''
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
${gradleEntries.join('\n')}
|
||||||
|
''');
|
||||||
|
}
|
||||||
|
|
||||||
|
test('skips with no supported ecosystems', () async {
|
||||||
|
_setDependabotCoverage();
|
||||||
|
createFakePackage('a_package', packagesDir);
|
||||||
|
|
||||||
|
final List<String> output =
|
||||||
|
await runCapturingPrint(runner, <String>['dependabot-check']);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(<Matcher>[
|
||||||
|
contains('SKIPPING: No supported package ecosystems'),
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails for app missing Gradle coverage', () async {
|
||||||
|
_setDependabotCoverage();
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePackage('a_package', packagesDir);
|
||||||
|
package.directory
|
||||||
|
.childDirectory('example')
|
||||||
|
.childDirectory('android')
|
||||||
|
.childDirectory('app')
|
||||||
|
.createSync(recursive: true);
|
||||||
|
|
||||||
|
Error? commandError;
|
||||||
|
final List<String> output = await runCapturingPrint(
|
||||||
|
runner, <String>['dependabot-check'], errorHandler: (Error e) {
|
||||||
|
commandError = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(commandError, isA<ToolExit>());
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(<Matcher>[
|
||||||
|
contains('Missing Gradle coverage.'),
|
||||||
|
contains('a_package/example:\n'
|
||||||
|
' Missing Gradle coverage')
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails for plugin missing Gradle coverage', () async {
|
||||||
|
_setDependabotCoverage();
|
||||||
|
final RepositoryPackage plugin = createFakePlugin('a_plugin', packagesDir);
|
||||||
|
plugin.directory.childDirectory('android').createSync(recursive: true);
|
||||||
|
|
||||||
|
Error? commandError;
|
||||||
|
final List<String> output = await runCapturingPrint(
|
||||||
|
runner, <String>['dependabot-check'], errorHandler: (Error e) {
|
||||||
|
commandError = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(commandError, isA<ToolExit>());
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(<Matcher>[
|
||||||
|
contains('Missing Gradle coverage.'),
|
||||||
|
contains('a_plugin:\n'
|
||||||
|
' Missing Gradle coverage')
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('passes for correct Gradle coverage', () async {
|
||||||
|
_setDependabotCoverage(gradleDirs: <String>[
|
||||||
|
'packages/a_plugin/android',
|
||||||
|
'packages/a_plugin/example/android/app',
|
||||||
|
]);
|
||||||
|
final RepositoryPackage plugin = createFakePlugin('a_plugin', packagesDir);
|
||||||
|
// Test the plugin.
|
||||||
|
plugin.directory.childDirectory('android').createSync(recursive: true);
|
||||||
|
// And its example app.
|
||||||
|
plugin.directory
|
||||||
|
.childDirectory('example')
|
||||||
|
.childDirectory('android')
|
||||||
|
.childDirectory('app')
|
||||||
|
.createSync(recursive: true);
|
||||||
|
|
||||||
|
final List<String> output =
|
||||||
|
await runCapturingPrint(runner, <String>['dependabot-check']);
|
||||||
|
|
||||||
|
expect(output,
|
||||||
|
containsAllInOrder(<Matcher>[contains('Ran for 2 package(s)')]));
|
||||||
|
});
|
||||||
|
}
|
Reference in New Issue
Block a user