Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 8d0382e

Browse files
[flutter_plugin_tools] Build gtest unit tests (#4492)
1 parent d90b64f commit 8d0382e

File tree

9 files changed

+333
-44
lines changed

9 files changed

+333
-44
lines changed

packages/url_launcher/url_launcher/example/linux/flutter/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ add_custom_command(
7878
COMMAND ${CMAKE_COMMAND} -E env
7979
${FLUTTER_TOOL_ENVIRONMENT}
8080
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
81-
linux-x64 ${CMAKE_BUILD_TYPE}
81+
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
82+
VERBATIM
8283
)
8384
add_custom_target(flutter_assemble DEPENDS
8485
"${FLUTTER_LIBRARY}"

packages/url_launcher/url_launcher/example/windows/flutter/generated_plugin_registrant.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
#include "generated_plugin_registrant.h"
88

9-
#include <url_launcher_windows/url_launcher_plugin.h>
9+
#include <url_launcher_windows/url_launcher_windows.h>
1010

1111
void RegisterPlugins(flutter::PluginRegistry* registry) {
12-
UrlLauncherPluginRegisterWithRegistrar(
13-
registry->GetRegistrarForPlugin("UrlLauncherPlugin"));
12+
UrlLauncherWindowsRegisterWithRegistrar(
13+
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
1414
}

packages/url_launcher/url_launcher_linux/example/linux/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ add_dependencies(${BINARY_NAME} flutter_assemble)
4545

4646
# Enable the test target.
4747
set(include_url_launcher_linux_tests TRUE)
48+
# Provide an alias for the test target using the name expected by repo tooling.
49+
add_custom_target(unit_tests DEPENDS url_launcher_linux_test)
4850

4951
# Generated plugin build rules, which manage building the plugins and adding
5052
# them to the application.

packages/url_launcher/url_launcher_windows/example/windows/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ add_subdirectory("runner")
4848

4949
# Enable the test target.
5050
set(include_url_launcher_windows_tests TRUE)
51+
# Provide an alias for the test target using the name expected by repo tooling.
52+
add_custom_target(unit_tests DEPENDS url_launcher_windows_test)
5153

5254
# Generated plugin build rules, which manage building the plugins and adding
5355
# them to the application.

script/tool/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## NEXT
22

3+
- `native-test` now builds unit tests before running them on Windows and Linux,
4+
matching the behavior of other platforms.
35
- Added `--log-timing` to add timing information to package headers in looping
46
commands.
57

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:file/file.dart';
6+
import 'package:flutter_plugin_tools/src/common/core.dart';
7+
import 'package:platform/platform.dart';
8+
9+
import 'process_runner.dart';
10+
11+
const String _cacheCommandKey = 'CMAKE_COMMAND:INTERNAL';
12+
13+
/// A utility class for interacting with CMake projects.
14+
class CMakeProject {
15+
/// Creates an instance that runs commands for [project] with the given
16+
/// [processRunner].
17+
CMakeProject(
18+
this.flutterProject, {
19+
required this.buildMode,
20+
this.processRunner = const ProcessRunner(),
21+
this.platform = const LocalPlatform(),
22+
});
23+
24+
/// The directory of a Flutter project to run Gradle commands in.
25+
final Directory flutterProject;
26+
27+
/// The [ProcessRunner] used to run commands. Overridable for testing.
28+
final ProcessRunner processRunner;
29+
30+
/// The platform that commands are being run on.
31+
final Platform platform;
32+
33+
/// The build mode (e.g., Debug, Release).
34+
///
35+
/// This is a constructor paramater because on Linux many properties depend
36+
/// on the build mode since it uses a single-configuration generator.
37+
final String buildMode;
38+
39+
late final String _cmakeCommand = _determineCmakeCommand();
40+
41+
/// The project's platform directory name.
42+
String get _platformDirName => platform.isWindows ? 'windows' : 'linux';
43+
44+
/// The project's 'example' build directory for this instance's platform.
45+
Directory get buildDirectory {
46+
Directory buildDir =
47+
flutterProject.childDirectory('build').childDirectory(_platformDirName);
48+
if (platform.isLinux) {
49+
buildDir = buildDir
50+
// TODO(stuartmorgan): Support arm64 if that ever becomes a supported
51+
// CI configuration for the repository.
52+
.childDirectory('x64')
53+
// Linux uses a single-config generator, so the base build directory
54+
// includes the configuration.
55+
.childDirectory(buildMode.toLowerCase());
56+
}
57+
return buildDir;
58+
}
59+
60+
File get _cacheFile => buildDirectory.childFile('CMakeCache.txt');
61+
62+
/// Returns the CMake command to run build commands for this project.
63+
///
64+
/// Assumes the project has been built at least once, such that the CMake
65+
/// generation step has run.
66+
String getCmakeCommand() {
67+
return _cmakeCommand;
68+
}
69+
70+
/// Returns the CMake command to run build commands for this project. This is
71+
/// used to initialize _cmakeCommand, and should not be called directly.
72+
///
73+
/// Assumes the project has been built at least once, such that the CMake
74+
/// generation step has run.
75+
String _determineCmakeCommand() {
76+
// On Linux 'cmake' is expected to be in the path, so doesn't need to
77+
// be lookup up and cached.
78+
if (platform.isLinux) {
79+
return 'cmake';
80+
}
81+
final File cacheFile = _cacheFile;
82+
String? command;
83+
for (String line in cacheFile.readAsLinesSync()) {
84+
line = line.trim();
85+
if (line.startsWith(_cacheCommandKey)) {
86+
command = line.substring(line.indexOf('=') + 1).trim();
87+
break;
88+
}
89+
}
90+
if (command == null) {
91+
printError('Unable to find CMake command in ${cacheFile.path}');
92+
throw ToolExit(100);
93+
}
94+
return command;
95+
}
96+
97+
/// Whether or not the project is ready to have CMake commands run on it
98+
/// (i.e., whether the `flutter` tool has generated the necessary files).
99+
bool isConfigured() => _cacheFile.existsSync();
100+
101+
/// Runs a `cmake` command with the given parameters.
102+
Future<int> runBuild(
103+
String target, {
104+
List<String> arguments = const <String>[],
105+
}) {
106+
return processRunner.runAndStream(
107+
getCmakeCommand(),
108+
<String>[
109+
'--build',
110+
buildDirectory.path,
111+
'--target',
112+
target,
113+
if (platform.isWindows) ...<String>['--config', buildMode],
114+
...arguments,
115+
],
116+
);
117+
}
118+
}

script/tool/lib/src/common/gradle.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ const String _gradleWrapperNonWindows = 'gradlew';
1414
class GradleProject {
1515
/// Creates an instance that runs commands for [project] with the given
1616
/// [processRunner].
17-
///
18-
/// If [log] is true, commands run by this instance will long various status
19-
/// messages.
2017
GradleProject(
2118
this.flutterProject, {
2219
this.processRunner = const ProcessRunner(),

script/tool/lib/src/native_test_command.dart

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'package:file/file.dart';
66
import 'package:platform/platform.dart';
77

8+
import 'common/cmake.dart';
89
import 'common/core.dart';
910
import 'common/gradle.dart';
1011
import 'common/package_looping_command.dart';
@@ -456,8 +457,8 @@ this command.
456457
file.basename.endsWith('_tests.exe');
457458
}
458459

459-
return _runGoogleTestTests(plugin,
460-
buildDirectoryName: 'windows', isTestBinary: isTestBinary);
460+
return _runGoogleTestTests(plugin, 'Windows', 'Debug',
461+
isTestBinary: isTestBinary);
461462
}
462463

463464
Future<_PlatformResult> _testLinux(
@@ -471,8 +472,16 @@ this command.
471472
file.basename.endsWith('_tests');
472473
}
473474

474-
return _runGoogleTestTests(plugin,
475-
buildDirectoryName: 'linux', isTestBinary: isTestBinary);
475+
// Since Linux uses a single-config generator, building-examples only
476+
// generates the build files for release, so the tests have to be run in
477+
// release mode as well.
478+
//
479+
// TODO(stuartmorgan): Consider adding a command to `flutter` that would
480+
// generate build files without doing a build, and using that instead of
481+
// relying on running build-examples. See
482+
// https://github.com/flutter/flutter/issues/93407.
483+
return _runGoogleTestTests(plugin, 'Linux', 'Release',
484+
isTestBinary: isTestBinary);
476485
}
477486

478487
/// Finds every file in the [buildDirectoryName] subdirectory of [plugin]'s
@@ -482,38 +491,66 @@ this command.
482491
/// The binaries are assumed to be Google Test test binaries, thus returning
483492
/// zero for success and non-zero for failure.
484493
Future<_PlatformResult> _runGoogleTestTests(
485-
RepositoryPackage plugin, {
486-
required String buildDirectoryName,
494+
RepositoryPackage plugin,
495+
String platformName,
496+
String buildMode, {
487497
required bool Function(File) isTestBinary,
488498
}) async {
489499
final List<File> testBinaries = <File>[];
500+
bool hasMissingBuild = false;
501+
bool buildFailed = false;
490502
for (final RepositoryPackage example in plugin.getExamples()) {
491-
final Directory buildDir = example.directory
492-
.childDirectory('build')
493-
.childDirectory(buildDirectoryName);
494-
if (!buildDir.existsSync()) {
503+
final CMakeProject project = CMakeProject(example.directory,
504+
buildMode: buildMode,
505+
processRunner: processRunner,
506+
platform: platform);
507+
if (!project.isConfigured()) {
508+
printError('ERROR: Run "flutter build" on ${example.displayName}, '
509+
'or run this tool\'s "build-examples" command, for the target '
510+
'platform before executing tests.');
511+
hasMissingBuild = true;
495512
continue;
496513
}
497-
testBinaries.addAll(buildDir
514+
515+
// By repository convention, example projects create an aggregate target
516+
// called 'unit_tests' that builds all unit tests (usually just an alias
517+
// for a specific test target).
518+
final int exitCode = await project.runBuild('unit_tests');
519+
if (exitCode != 0) {
520+
printError('${example.displayName} unit tests failed to build.');
521+
buildFailed = true;
522+
}
523+
524+
testBinaries.addAll(project.buildDirectory
498525
.listSync(recursive: true)
499526
.whereType<File>()
500527
.where(isTestBinary)
501528
.where((File file) {
502-
// Only run the release build of the unit tests, to avoid running the
503-
// same tests multiple times. Release is used rather than debug since
504-
// `build-examples` builds release versions.
529+
// Only run the `buildMode` build of the unit tests, to avoid running
530+
// the same tests multiple times.
505531
final List<String> components = path.split(file.path);
506-
return components.contains('release') || components.contains('Release');
532+
return components.contains(buildMode) ||
533+
components.contains(buildMode.toLowerCase());
507534
}));
508535
}
509536

537+
if (hasMissingBuild) {
538+
return _PlatformResult(RunState.failed,
539+
error: 'Examples must be built before testing.');
540+
}
541+
542+
if (buildFailed) {
543+
return _PlatformResult(RunState.failed,
544+
error: 'Failed to build $platformName unit tests.');
545+
}
546+
510547
if (testBinaries.isEmpty) {
511548
final String binaryExtension = platform.isWindows ? '.exe' : '';
512549
printError(
513550
'No test binaries found. At least one *_test(s)$binaryExtension '
514551
'binary should be built by the example(s)');
515552
return _PlatformResult(RunState.failed,
516-
error: 'No $buildDirectoryName unit tests found');
553+
error: 'No $platformName unit tests found');
517554
}
518555

519556
bool passing = true;

0 commit comments

Comments
 (0)