blob: 5405ab51696b2e593641c67690c65ced5ecb0332 [file] [log] [blame]
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. 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 'dart:io';
Future<int> main(List<String> args) async {
Options options;
try {
options = Options.read(args);
} on EmptyOptionsException {
return 0;
} on OptionsException catch (e) {
print(e.text);
return 1;
}
Map<String, List<String>> map;
try {
map = findAndReadStatusFiles(options.sdk);
} on StatusFilesException catch (e) {
print(e.text);
return -1;
}
List<String> arguments = [
'log',
'--pretty=format:"%h"',
'--after=${options.after}',
if (options.before != null) '--before=${options.before}'
];
ProcessResult res = await Process.run('git', arguments);
if (res.exitCode != 0) {
print(res.stderr);
return 2;
}
int foundCount = 0;
List<String> commits = (res.stdout as String).split("\n");
for (var c in commits) {
if (c.isEmpty) {
continue;
}
c = c.replaceAll('"', '');
ProcessResult r = await Process.run(
'git', ['show', '--pretty=format:""', '--name-only', c]);
if (r.exitCode != 0) {
print(r.stderr);
return 3;
}
List files = (r.stdout as String).split("\n");
for (String f in files) {
if (f.isEmpty) {
continue;
}
if (f.endsWith(".dart")) {
String testName = f.substring(0, f.indexOf(".dart"));
var found = map[testName];
if (found != null) {
print(
"Test $testName was changed in commit $c and presents in the following status files");
print(found);
foundCount++;
}
}
}
}
print("$foundCount status files entries was found in ${commits.length} commits");
return 0;
}
class Options {
String? after = null;
String? before = null;
String sdk = '.';
int days = 30;
static Options read(List<String> args) {
Options options = new Options();
if (args.length > 0) {
for (var arg in args) {
if (!_checkFormat(arg)) {
throw OptionsException("Unknown argument: '$arg'");
}
List<String> pair = arg.substring(1).split("=");
switch (pair[0]) {
case "after":
options.after = pair[1];
break;
case "before":
options.before = pair[1];
break;
case "days":
try {
options.days = int.parse(pair[1]);
} on FormatException catch (_) {
throw new OptionsException(
"Unable to parse 'days' value: '${pair[0]}'");
}
DateTime da = DateTime.now().add(Duration(days: -options.days));
options.after = _formatDate(da);
break;
case "sdk":
options.sdk = pair[1];
break;
default:
throw new OptionsException("Unknown argument: '${pair[0]}'");
}
}
if (options.after == null) {
DateTime da = DateTime.now().add(Duration(days: -options.days));
options.after = _formatDate(da);
}
return options;
} else {
print('''
This tool checks if recent commits contain tests mentioned in .status files. Usage:
dart status_files_checker.dart <options>
Options:
-after=yyyy-MM-dd - by default current date minus ${options.days} days
-before=yyyy-MM-dd - current date by default
-days=N - check commits for N latest days (${options.days} by default)
-sdk=path_to_sdk - path to Dart SDK (by default it's the current directory)
''');
throw EmptyOptionsException();
}
}
static bool _checkFormat(String arg) {
RegExp regExp = new RegExp("-.*=.*");
return regExp.hasMatch(arg);
}
static String _formatDate(DateTime date) {
return "${date.year}-${date.month < 10 ? "0" + date.month.toString() : date.month}-${date.day < 01 ? "0" + date.day.toString() : date.day}";
}
}
Map<String, List<String>> findAndReadStatusFiles(String sdk) {
Directory testsDir = new Directory(sdk + Platform.pathSeparator + "tests");
if (!testsDir.existsSync()) {
throw StatusFilesException("Directory ${testsDir.path} doesn't exist");
}
Map<String, List<String>> map = new Map<String, List<String>>();
List<FileSystemEntity> list = testsDir.listSync(recursive: true);
for (FileSystemEntity fse in list) {
if (fse is File && fse.path.endsWith(".status")) {
addAll(map, readStatusFile(fse));
}
}
return map;
}
Map<String, List<String>> readStatusFile(File f) {
// get test suite prefix
String prefix = "";
final test = Platform.pathSeparator + "tests" + Platform.pathSeparator;
final ind = f.path.indexOf(test);
final suite = f.path.substring(ind + test.length,
f.path.indexOf(Platform.pathSeparator, ind + test.length + 1));
if (suite != "co19" && suite != "co19_2") {
prefix = "tests/" + suite + "/";
}
// now read the file
final regExp = new RegExp(".*:(\\s)*(Skip|Crash|RuntimeError){1}(\\s|#)");
List<String> strings = f.readAsLinesSync();
Map<String, List<String>> map = new Map<String, List<String>>();
for (String s in strings) {
RegExpMatch? match = regExp.firstMatch(s);
if (match != null) {
String testAndStatus = prefix +
s.substring(match.start,
match.end); // LibTest/ffi/Array/PointerArray_A01_t01: Skip
// Now process multitests like io/platform_resolved_executable_test/06: RuntimeError
testAndStatus =
testAndStatus.replaceFirst(new RegExp(r"\/([0-9]{2,3}|none):"), ":");
List<String> parts = testAndStatus.split(":");
map[parts[0]] = [f.path + parts[1]];
}
}
return map;
}
void addAll(Map<String, List<String>> map1, Map<String, List<String>> map2) {
for (String k in map2.keys) {
if (map1.containsKey(k)) {
map1[k]?.addAll(map2[k]!);
} else {
map1[k] = map2[k]!;
}
}
}
class EmptyOptionsException implements Exception {}
class OptionsException implements Exception {
final String text;
OptionsException(this.text);
}
class StatusFilesException implements Exception {
final String text;
StatusFilesException(this.text);
}