mirror of
https://github.com/flutter/packages.git
synced 2025-05-31 05:30:36 +08:00
[flutter_plugin_tools] Build gtest unit tests (#4492)
This commit is contained in:
118
script/tool/lib/src/common/cmake.dart
Normal file
118
script/tool/lib/src/common/cmake.dart
Normal file
@ -0,0 +1,118 @@
|
||||
// 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:file/file.dart';
|
||||
import 'package:flutter_plugin_tools/src/common/core.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
||||
import 'process_runner.dart';
|
||||
|
||||
const String _cacheCommandKey = 'CMAKE_COMMAND:INTERNAL';
|
||||
|
||||
/// A utility class for interacting with CMake projects.
|
||||
class CMakeProject {
|
||||
/// Creates an instance that runs commands for [project] with the given
|
||||
/// [processRunner].
|
||||
CMakeProject(
|
||||
this.flutterProject, {
|
||||
required this.buildMode,
|
||||
this.processRunner = const ProcessRunner(),
|
||||
this.platform = const LocalPlatform(),
|
||||
});
|
||||
|
||||
/// The directory of a Flutter project to run Gradle commands in.
|
||||
final Directory flutterProject;
|
||||
|
||||
/// The [ProcessRunner] used to run commands. Overridable for testing.
|
||||
final ProcessRunner processRunner;
|
||||
|
||||
/// The platform that commands are being run on.
|
||||
final Platform platform;
|
||||
|
||||
/// The build mode (e.g., Debug, Release).
|
||||
///
|
||||
/// This is a constructor paramater because on Linux many properties depend
|
||||
/// on the build mode since it uses a single-configuration generator.
|
||||
final String buildMode;
|
||||
|
||||
late final String _cmakeCommand = _determineCmakeCommand();
|
||||
|
||||
/// The project's platform directory name.
|
||||
String get _platformDirName => platform.isWindows ? 'windows' : 'linux';
|
||||
|
||||
/// The project's 'example' build directory for this instance's platform.
|
||||
Directory get buildDirectory {
|
||||
Directory buildDir =
|
||||
flutterProject.childDirectory('build').childDirectory(_platformDirName);
|
||||
if (platform.isLinux) {
|
||||
buildDir = buildDir
|
||||
// TODO(stuartmorgan): Support arm64 if that ever becomes a supported
|
||||
// CI configuration for the repository.
|
||||
.childDirectory('x64')
|
||||
// Linux uses a single-config generator, so the base build directory
|
||||
// includes the configuration.
|
||||
.childDirectory(buildMode.toLowerCase());
|
||||
}
|
||||
return buildDir;
|
||||
}
|
||||
|
||||
File get _cacheFile => buildDirectory.childFile('CMakeCache.txt');
|
||||
|
||||
/// Returns the CMake command to run build commands for this project.
|
||||
///
|
||||
/// Assumes the project has been built at least once, such that the CMake
|
||||
/// generation step has run.
|
||||
String getCmakeCommand() {
|
||||
return _cmakeCommand;
|
||||
}
|
||||
|
||||
/// Returns the CMake command to run build commands for this project. This is
|
||||
/// used to initialize _cmakeCommand, and should not be called directly.
|
||||
///
|
||||
/// Assumes the project has been built at least once, such that the CMake
|
||||
/// generation step has run.
|
||||
String _determineCmakeCommand() {
|
||||
// On Linux 'cmake' is expected to be in the path, so doesn't need to
|
||||
// be lookup up and cached.
|
||||
if (platform.isLinux) {
|
||||
return 'cmake';
|
||||
}
|
||||
final File cacheFile = _cacheFile;
|
||||
String? command;
|
||||
for (String line in cacheFile.readAsLinesSync()) {
|
||||
line = line.trim();
|
||||
if (line.startsWith(_cacheCommandKey)) {
|
||||
command = line.substring(line.indexOf('=') + 1).trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (command == null) {
|
||||
printError('Unable to find CMake command in ${cacheFile.path}');
|
||||
throw ToolExit(100);
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
/// Whether or not the project is ready to have CMake commands run on it
|
||||
/// (i.e., whether the `flutter` tool has generated the necessary files).
|
||||
bool isConfigured() => _cacheFile.existsSync();
|
||||
|
||||
/// Runs a `cmake` command with the given parameters.
|
||||
Future<int> runBuild(
|
||||
String target, {
|
||||
List<String> arguments = const <String>[],
|
||||
}) {
|
||||
return processRunner.runAndStream(
|
||||
getCmakeCommand(),
|
||||
<String>[
|
||||
'--build',
|
||||
buildDirectory.path,
|
||||
'--target',
|
||||
target,
|
||||
if (platform.isWindows) ...<String>['--config', buildMode],
|
||||
...arguments,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -14,9 +14,6 @@ const String _gradleWrapperNonWindows = 'gradlew';
|
||||
class GradleProject {
|
||||
/// Creates an instance that runs commands for [project] with the given
|
||||
/// [processRunner].
|
||||
///
|
||||
/// If [log] is true, commands run by this instance will long various status
|
||||
/// messages.
|
||||
GradleProject(
|
||||
this.flutterProject, {
|
||||
this.processRunner = const ProcessRunner(),
|
||||
|
@ -5,6 +5,7 @@
|
||||
import 'package:file/file.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
||||
import 'common/cmake.dart';
|
||||
import 'common/core.dart';
|
||||
import 'common/gradle.dart';
|
||||
import 'common/package_looping_command.dart';
|
||||
@ -456,8 +457,8 @@ this command.
|
||||
file.basename.endsWith('_tests.exe');
|
||||
}
|
||||
|
||||
return _runGoogleTestTests(plugin,
|
||||
buildDirectoryName: 'windows', isTestBinary: isTestBinary);
|
||||
return _runGoogleTestTests(plugin, 'Windows', 'Debug',
|
||||
isTestBinary: isTestBinary);
|
||||
}
|
||||
|
||||
Future<_PlatformResult> _testLinux(
|
||||
@ -471,8 +472,16 @@ this command.
|
||||
file.basename.endsWith('_tests');
|
||||
}
|
||||
|
||||
return _runGoogleTestTests(plugin,
|
||||
buildDirectoryName: 'linux', isTestBinary: isTestBinary);
|
||||
// Since Linux uses a single-config generator, building-examples only
|
||||
// generates the build files for release, so the tests have to be run in
|
||||
// release mode as well.
|
||||
//
|
||||
// TODO(stuartmorgan): Consider adding a command to `flutter` that would
|
||||
// generate build files without doing a build, and using that instead of
|
||||
// relying on running build-examples. See
|
||||
// https://github.com/flutter/flutter/issues/93407.
|
||||
return _runGoogleTestTests(plugin, 'Linux', 'Release',
|
||||
isTestBinary: isTestBinary);
|
||||
}
|
||||
|
||||
/// Finds every file in the [buildDirectoryName] subdirectory of [plugin]'s
|
||||
@ -482,38 +491,66 @@ this command.
|
||||
/// The binaries are assumed to be Google Test test binaries, thus returning
|
||||
/// zero for success and non-zero for failure.
|
||||
Future<_PlatformResult> _runGoogleTestTests(
|
||||
RepositoryPackage plugin, {
|
||||
required String buildDirectoryName,
|
||||
RepositoryPackage plugin,
|
||||
String platformName,
|
||||
String buildMode, {
|
||||
required bool Function(File) isTestBinary,
|
||||
}) async {
|
||||
final List<File> testBinaries = <File>[];
|
||||
bool hasMissingBuild = false;
|
||||
bool buildFailed = false;
|
||||
for (final RepositoryPackage example in plugin.getExamples()) {
|
||||
final Directory buildDir = example.directory
|
||||
.childDirectory('build')
|
||||
.childDirectory(buildDirectoryName);
|
||||
if (!buildDir.existsSync()) {
|
||||
final CMakeProject project = CMakeProject(example.directory,
|
||||
buildMode: buildMode,
|
||||
processRunner: processRunner,
|
||||
platform: platform);
|
||||
if (!project.isConfigured()) {
|
||||
printError('ERROR: Run "flutter build" on ${example.displayName}, '
|
||||
'or run this tool\'s "build-examples" command, for the target '
|
||||
'platform before executing tests.');
|
||||
hasMissingBuild = true;
|
||||
continue;
|
||||
}
|
||||
testBinaries.addAll(buildDir
|
||||
|
||||
// By repository convention, example projects create an aggregate target
|
||||
// called 'unit_tests' that builds all unit tests (usually just an alias
|
||||
// for a specific test target).
|
||||
final int exitCode = await project.runBuild('unit_tests');
|
||||
if (exitCode != 0) {
|
||||
printError('${example.displayName} unit tests failed to build.');
|
||||
buildFailed = true;
|
||||
}
|
||||
|
||||
testBinaries.addAll(project.buildDirectory
|
||||
.listSync(recursive: true)
|
||||
.whereType<File>()
|
||||
.where(isTestBinary)
|
||||
.where((File file) {
|
||||
// Only run the release build of the unit tests, to avoid running the
|
||||
// same tests multiple times. Release is used rather than debug since
|
||||
// `build-examples` builds release versions.
|
||||
// Only run the `buildMode` build of the unit tests, to avoid running
|
||||
// the same tests multiple times.
|
||||
final List<String> components = path.split(file.path);
|
||||
return components.contains('release') || components.contains('Release');
|
||||
return components.contains(buildMode) ||
|
||||
components.contains(buildMode.toLowerCase());
|
||||
}));
|
||||
}
|
||||
|
||||
if (hasMissingBuild) {
|
||||
return _PlatformResult(RunState.failed,
|
||||
error: 'Examples must be built before testing.');
|
||||
}
|
||||
|
||||
if (buildFailed) {
|
||||
return _PlatformResult(RunState.failed,
|
||||
error: 'Failed to build $platformName unit tests.');
|
||||
}
|
||||
|
||||
if (testBinaries.isEmpty) {
|
||||
final String binaryExtension = platform.isWindows ? '.exe' : '';
|
||||
printError(
|
||||
'No test binaries found. At least one *_test(s)$binaryExtension '
|
||||
'binary should be built by the example(s)');
|
||||
return _PlatformResult(RunState.failed,
|
||||
error: 'No $buildDirectoryName unit tests found');
|
||||
error: 'No $platformName unit tests found');
|
||||
}
|
||||
|
||||
bool passing = true;
|
||||
|
Reference in New Issue
Block a user