forked from flutter/plugins
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[ci] Ensure complete dependabot coverage (flutter#5976)
- Loading branch information
1 parent
a136cb8
commit dfa9324
Showing
6 changed files
with
286 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)')])); | ||
}); | ||
} |