blob: e593e15e9a3d84a5d1e944b4648eb8c859eb9f1d [file] [log] [blame]
// 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.
// ignore_for_file: avoid_print
////////////////////////////////////////////////////////////////////////////////
/// CI entrypoint for running Pigeon tests.
///
/// For any use other than CI, use test.dart instead.
////////////////////////////////////////////////////////////////////////////////
import 'dart:io';
import 'package:path/path.dart' as p;
import 'shared/generation.dart';
import 'shared/test_runner.dart';
import 'shared/test_suites.dart';
/// Exits with failure if any tests in [testSuites] are not included in any of
/// the given test [shards].
void _validateTestCoverage(List<List<String>> shards) {
final Set<String> missing = testSuites.keys.toSet();
shards.forEach(missing.removeAll);
if (missing.isNotEmpty) {
print('The following test suites are not being run on any host:');
for (final String suite in missing) {
print(' $suite');
}
exit(1);
}
}
Future<void> _validateGeneratedTestFiles() async {
final String baseDir = p.dirname(p.dirname(Platform.script.toFilePath()));
final String repositoryRoot = p.dirname(p.dirname(baseDir));
final String relativePigeonPath = p.relative(baseDir, from: repositoryRoot);
print('Validating generated files:');
print(' Generating test output...');
final int generateExitCode = await generateTestPigeons(baseDir: baseDir);
if (generateExitCode != 0) {
print('Generation failed; see above for errors.');
exit(generateExitCode);
}
print(' Formatting output...');
final int formatExitCode =
await formatAllFiles(repositoryRoot: repositoryRoot);
if (formatExitCode != 0) {
print('Formatting failed; see above for errors.');
exit(formatExitCode);
}
print(' Checking for changes...');
final List<String> modifiedFiles = await _modifiedFiles(
repositoryRoot: repositoryRoot, relativePigeonPath: relativePigeonPath);
if (modifiedFiles.isEmpty) {
return;
}
print('The following files are not updated, or not formatted correctly:');
modifiedFiles.map((String line) => ' $line').forEach(print);
print('\nTo fix run "dart run tool/generate.dart --format" from the pigeon/ '
'directory, or apply the diff with the command below.\n');
final ProcessResult diffResult = await Process.run(
'git',
<String>['diff', relativePigeonPath],
workingDirectory: repositoryRoot,
);
if (diffResult.exitCode != 0) {
print('Unable to determine diff.');
exit(1);
}
print('patch -p1 <<DONE');
print(diffResult.stdout);
print('DONE');
exit(1);
}
Future<void> _validateGeneratedExampleFiles() async {
final String baseDir = p.dirname(p.dirname(Platform.script.toFilePath()));
final String repositoryRoot = p.dirname(p.dirname(baseDir));
final String relativePigeonPath = p.relative(baseDir, from: repositoryRoot);
print('Validating generated files:');
print(' Generating example output...');
final int generateExitCode = await generateExamplePigeons();
if (generateExitCode != 0) {
print('Generation failed; see above for errors.');
exit(generateExitCode);
}
print(' Formatting output...');
final int formatExitCode =
await formatAllFiles(repositoryRoot: repositoryRoot);
if (formatExitCode != 0) {
print('Formatting failed; see above for errors.');
exit(formatExitCode);
}
print(' Checking for changes...');
final List<String> modifiedFiles = await _modifiedFiles(
repositoryRoot: repositoryRoot, relativePigeonPath: relativePigeonPath);
if (modifiedFiles.isEmpty) {
return;
}
print(
'Either messages.dart and messages_test.dart have non-matching definitions or');
print('the following files are not updated, or not formatted correctly:');
modifiedFiles.map((String line) => ' $line').forEach(print);
print('\nTo fix run "dart run tool/generate.dart --format" from the pigeon/ '
'directory, or apply the diff with the command below.\n');
final ProcessResult diffResult = await Process.run(
'git',
<String>['diff', relativePigeonPath],
workingDirectory: repositoryRoot,
);
if (diffResult.exitCode != 0) {
print('Unable to determine diff.');
exit(1);
}
print('patch -p1 <<DONE');
print(diffResult.stdout);
print('DONE');
exit(1);
}
Future<List<String>> _modifiedFiles(
{required String repositoryRoot,
required String relativePigeonPath}) async {
final ProcessResult result = await Process.run(
'git',
<String>['ls-files', '--modified', relativePigeonPath],
workingDirectory: repositoryRoot,
);
if (result.exitCode != 0) {
print('Unable to determine changed files.');
print(result.stdout);
print(result.stderr);
exit(1);
}
return (result.stdout as String)
.split('\n')
.map((String line) => line.trim())
.where((String line) => line.isNotEmpty)
.toList();
}
Future<void> main(List<String> args) async {
// Run most tests on Linux, since Linux tends to be the easiest and cheapest.
const List<String> linuxHostTests = <String>[
dartUnitTests,
flutterUnitTests,
mockHandlerTests,
commandLineTests,
androidJavaUnitTests,
androidJavaLint,
androidKotlinUnitTests,
androidJavaIntegrationTests,
androidKotlinIntegrationTests,
];
const List<String> macOSHostTests = <String>[
iOSObjCUnitTests,
// Currently these are testing exactly the same thing as
// macOS*IntegrationTests, so we don't need to run both by default. This
// should be enabled if any iOS-only tests are added (e.g., for a feature
// not supported by macOS).
// iOSObjCIntegrationTests,
// iOSSwiftIntegrationTests,
iOSSwiftUnitTests,
macOSObjCIntegrationTests,
macOSSwiftUnitTests,
macOSSwiftIntegrationTests,
];
// Run Windows tests on Windows, since that's the only place they can run.
const List<String> windowsHostTests = <String>[
windowsUnitTests,
windowsIntegrationTests,
];
_validateTestCoverage(<List<String>>[
linuxHostTests,
macOSHostTests,
windowsHostTests,
// Tests that are deliberately not included in CI:
<String>[
// See comments in macOSHostTests:
iOSObjCIntegrationTests,
iOSSwiftIntegrationTests,
],
]);
// Ensure that all generated files are up to date. This is run only on Linux
// both to avoid duplication of work, and to avoid issues if different CI
// configurations have different setups (e.g., different clang-format versions
// or no clang-format at all).
if (Platform.isLinux) {
// Only run on master, since Dart format can change between versions.
// TODO(stuartmorgan): Make a more generic way to run this check only on
// master; this currently won't work for anything but Cirrus.
if (Platform.environment['CHANNEL'] == 'stable') {
print('Skipping generated file validation on stable.');
} else {
await _validateGeneratedTestFiles();
await _validateGeneratedExampleFiles();
}
}
final List<String> testsToRun;
if (Platform.isMacOS) {
testsToRun = macOSHostTests;
} else if (Platform.isWindows) {
testsToRun = windowsHostTests;
} else if (Platform.isLinux) {
testsToRun = linuxHostTests;
} else {
print('Unsupported host platform.');
exit(2);
}
await runTests(testsToRun);
}