blob: ffcbe78887b08663bc779e5f819475077b266d4e [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.
////////////////////////////////////////////////////////////////////////////////
library;
import 'dart:io';
import 'package:collection/collection.dart';
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 {
await _validateGeneratedFiles(
(String baseDir) => generateTestPigeons(baseDir: baseDir),
generationMessage: 'Generating test output',
incorrectFilesMessage:
'The following files are not updated, or not formatted correctly:',
);
}
Future<void> _validateGeneratedExampleFiles() async {
await _validateGeneratedFiles(
(String _) => generateExamplePigeons(),
generationMessage: 'Generating example output',
incorrectFilesMessage:
'Either messages.dart and messages_test.dart have non-matching definitions or\n'
'the following files are not updated, or not formatted correctly:',
);
}
Future<void> _validateGeneratedFiles(
Future<int> Function(String baseDirectory) generator, {
required String generationMessage,
required String incorrectFilesMessage,
}) async {
// Generated file validation is split by platform, 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).
final Set<GeneratorLanguage> languagesToValidate;
if (Platform.isLinux) {
languagesToValidate = <GeneratorLanguage>{
GeneratorLanguage.cpp,
GeneratorLanguage.dart,
GeneratorLanguage.java,
GeneratorLanguage.kotlin,
GeneratorLanguage.objc,
};
} else if (Platform.isMacOS) {
languagesToValidate = <GeneratorLanguage>{
GeneratorLanguage.swift,
};
} else {
return;
}
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(' $generationMessage...');
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, languages: languagesToValidate);
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);
final Set<String> extensions = languagesToValidate
.map((GeneratorLanguage lang) => _extensionsForLanguage(lang))
.flattened
.toSet();
final Iterable<String> filteredFiles = modifiedFiles.where((String path) =>
extensions.contains(p.extension(path).replaceFirst('.', '')));
if (filteredFiles.isEmpty) {
return;
}
print(incorrectFilesMessage);
filteredFiles.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', ...filteredFiles],
workingDirectory: repositoryRoot,
);
if (diffResult.exitCode != 0) {
print('Unable to determine diff.');
exit(1);
}
print('patch -p1 <<DONE');
print(diffResult.stdout);
print('DONE');
exit(1);
}
Set<String> _extensionsForLanguage(GeneratorLanguage language) {
return switch (language) {
GeneratorLanguage.cpp => <String>{'cc', 'cpp', 'h'},
GeneratorLanguage.dart => <String>{'dart'},
GeneratorLanguage.java => <String>{'java'},
GeneratorLanguage.kotlin => <String>{'kt'},
GeneratorLanguage.swift => <String>{'swift'},
GeneratorLanguage.objc => <String>{'h', 'm', 'mm'},
};
}
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>[
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,
// These are Dart unit tests, which are already run by the normal
// test-dart repo tools command.
dartUnitTests,
flutterUnitTests,
],
]);
// Ensure that all generated files are up to date.
// Only run on master, since Dart format can change between versions.
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);
}