[tools,pigeon] Update tooling to handle Windows build output changes (#4826)

Updates the tooling that builds and runs Windows unit tests to handle the build output path changes in https://github.com/flutter/flutter/pull/131843
This commit is contained in:
stuartmorgan
2023-09-01 06:57:55 -07:00
committed by GitHub
parent cd94db1f08
commit b8b84b2304
4 changed files with 332 additions and 45 deletions

View File

@ -20,6 +20,7 @@ class CMakeProject {
required this.buildMode,
this.processRunner = const ProcessRunner(),
this.platform = const LocalPlatform(),
this.arch,
});
/// The directory of a Flutter project to run Gradle commands in.
@ -31,6 +32,11 @@ class CMakeProject {
/// The platform that commands are being run on.
final Platform platform;
/// The architecture subdirectory of the build.
// TODO(stuartmorgan): Make this non-nullable once Flutter 3.13 is no longer
// supported, since at that point there will always be a subdirectory.
final String? arch;
/// The build mode (e.g., Debug, Release).
///
/// This is a constructor paramater because on Linux many properties depend
@ -46,14 +52,13 @@ class CMakeProject {
Directory get buildDirectory {
Directory buildDir =
flutterProject.childDirectory('build').childDirectory(_platformDirName);
if (arch != null) {
buildDir = buildDir.childDirectory(arch!);
}
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());
// Linux uses a single-config generator, so the base build directory
// includes the configuration.
buildDir = buildDir.childDirectory(buildMode.toLowerCase());
}
return buildDir;
}

View File

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ffi';
import 'package:file/file.dart';
import 'package:meta/meta.dart';
@ -41,7 +43,9 @@ class NativeTestCommand extends PackageLoopingCommand {
super.packagesDir, {
super.processRunner,
super.platform,
}) : _xcode = Xcode(processRunner: processRunner, log: true) {
Abi? abi,
}) : _abi = abi ?? Abi.current(),
_xcode = Xcode(processRunner: processRunner, log: true) {
argParser.addOption(
_iOSDestinationFlag,
help: 'Specify the destination when running iOS tests.\n'
@ -63,6 +67,9 @@ class NativeTestCommand extends PackageLoopingCommand {
help: 'Runs native integration (UI) tests', defaultsTo: true);
}
// The ABI of the host.
final Abi _abi;
// The device destination flags for iOS tests.
List<String> _iOSDestinationFlags = <String>[];
@ -548,9 +555,10 @@ this command.
isTestBinary: isTestBinary);
}
/// Finds every file in the [buildDirectoryName] subdirectory of [plugin]'s
/// build directory for which [isTestBinary] is true, and runs all of them,
/// returning the overall result.
/// Finds every file in the relevant (based on [platformName], [buildMode],
/// and [arch]) subdirectory of [plugin]'s build directory for which
/// [isTestBinary] is true, and runs all of them, returning the overall
/// result.
///
/// The binaries are assumed to be Google Test test binaries, thus returning
/// zero for success and non-zero for failure.
@ -563,11 +571,45 @@ this command.
final List<File> testBinaries = <File>[];
bool hasMissingBuild = false;
bool buildFailed = false;
String? arch;
const String x64DirName = 'x64';
const String arm64DirName = 'arm64';
if (platform.isWindows) {
arch = _abi == Abi.windowsX64 ? x64DirName : arm64DirName;
} else if (platform.isLinux) {
// TODO(stuartmorgan): Support arm64 if that ever becomes a supported
// CI configuration for the repository.
arch = 'x64';
}
for (final RepositoryPackage example in plugin.getExamples()) {
final CMakeProject project = CMakeProject(example.directory,
CMakeProject project = CMakeProject(example.directory,
buildMode: buildMode,
processRunner: processRunner,
platform: platform);
platform: platform,
arch: arch);
if (platform.isWindows) {
if (arch == arm64DirName && !project.isConfigured()) {
// Check for x64, to handle builds newer than 3.13, but that don't yet
// have https://github.com/flutter/flutter/issues/129807.
// TODO(stuartmorgan): Remove this when CI no longer supports a
// version of Flutter without the issue above fixed.
project = CMakeProject(example.directory,
buildMode: buildMode,
processRunner: processRunner,
platform: platform,
arch: x64DirName);
}
if (!project.isConfigured()) {
// Check again without the arch subdirectory, since 3.13 doesn't
// have it yet.
// TODO(stuartmorgan): Remove this when CI no longer supports Flutter
// 3.13.
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 '

View File

@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:convert';
import 'dart:ffi';
import 'package:args/command_runner.dart';
import 'package:file/file.dart';
@ -56,10 +57,13 @@ final Map<String, dynamic> _kDeviceListMap = <String, dynamic>{
};
const String _fakeCmakeCommand = 'path/to/cmake';
const String _archDirX64 = 'x64';
const String _archDirArm64 = 'arm64';
void _createFakeCMakeCache(RepositoryPackage plugin, Platform platform) {
void _createFakeCMakeCache(
RepositoryPackage plugin, Platform platform, String? archDir) {
final CMakeProject project = CMakeProject(getExampleDir(plugin),
platform: platform, buildMode: 'Release');
platform: platform, buildMode: 'Release', arch: archDir);
final File cache = project.buildDirectory.childFile('CMakeCache.txt');
cache.createSync(recursive: true);
cache.writeAsStringSync('CMAKE_COMMAND:INTERNAL=$_fakeCmakeCommand');
@ -1060,7 +1064,7 @@ public class FlutterActivityTest {
], platformSupport: <String, PlatformDetails>{
platformLinux: const PlatformDetails(PlatformSupport.inline),
});
_createFakeCMakeCache(plugin, mockPlatform);
_createFakeCMakeCache(plugin, mockPlatform, _archDirX64);
final File testBinary = childFileWithSubcomponents(plugin.directory,
<String>['example', ...testBinaryRelativePath.split('/')]);
@ -1099,7 +1103,7 @@ public class FlutterActivityTest {
], platformSupport: <String, PlatformDetails>{
platformLinux: const PlatformDetails(PlatformSupport.inline),
});
_createFakeCMakeCache(plugin, mockPlatform);
_createFakeCMakeCache(plugin, mockPlatform, _archDirX64);
final File releaseTestBinary = childFileWithSubcomponents(
plugin.directory,
@ -1159,7 +1163,7 @@ public class FlutterActivityTest {
platformSupport: <String, PlatformDetails>{
platformLinux: const PlatformDetails(PlatformSupport.inline),
});
_createFakeCMakeCache(plugin, mockPlatform);
_createFakeCMakeCache(plugin, mockPlatform, _archDirX64);
Error? commandError;
final List<String> output = await runCapturingPrint(runner, <String>[
@ -1194,7 +1198,7 @@ public class FlutterActivityTest {
], platformSupport: <String, PlatformDetails>{
platformLinux: const PlatformDetails(PlatformSupport.inline),
});
_createFakeCMakeCache(plugin, mockPlatform);
_createFakeCMakeCache(plugin, mockPlatform, _archDirX64);
final File testBinary = childFileWithSubcomponents(plugin.directory,
<String>['example', ...testBinaryRelativePath.split('/')]);
@ -1760,25 +1764,22 @@ public class FlutterActivityTest {
mockPlatform = MockPlatform(isWindows: true);
packagesDir = createPackagesDirectory(fileSystem: fileSystem);
processRunner = RecordingProcessRunner();
final NativeTestCommand command = NativeTestCommand(packagesDir,
processRunner: processRunner, platform: mockPlatform);
runner = CommandRunner<void>(
'native_test_command', 'Test for native_test_command');
runner.addCommand(command);
});
// Returns the ProcessCall to expect for build the Windows unit tests for
// the given plugin.
ProcessCall getWindowsBuildCall(RepositoryPackage plugin) {
ProcessCall getWindowsBuildCall(RepositoryPackage plugin, String? arch) {
Directory projectDir = getExampleDir(plugin)
.childDirectory('build')
.childDirectory('windows');
if (arch != null) {
projectDir = projectDir.childDirectory(arch);
}
return ProcessCall(
_fakeCmakeCommand,
<String>[
'--build',
getExampleDir(plugin)
.childDirectory('build')
.childDirectory('windows')
.path,
projectDir.path,
'--target',
'unit_tests',
'--config',
@ -1787,8 +1788,58 @@ public class FlutterActivityTest {
null);
}
group('Windows', () {
group('Windows x64', () {
setUp(() {
final NativeTestCommand command = NativeTestCommand(packagesDir,
processRunner: processRunner,
platform: mockPlatform,
abi: Abi.windowsX64);
runner = CommandRunner<void>(
'native_test_command', 'Test for native_test_command');
runner.addCommand(command);
});
test('runs unit tests', () async {
const String x64TestBinaryRelativePath =
'build/windows/x64/Debug/bar/plugin_test.exe';
const String arm64TestBinaryRelativePath =
'build/windows/arm64/Debug/bar/plugin_test.exe';
final RepositoryPackage plugin =
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/$x64TestBinaryRelativePath',
'example/$arm64TestBinaryRelativePath',
], platformSupport: <String, PlatformDetails>{
platformWindows: const PlatformDetails(PlatformSupport.inline),
});
_createFakeCMakeCache(plugin, mockPlatform, _archDirX64);
final File testBinary = childFileWithSubcomponents(plugin.directory,
<String>['example', ...x64TestBinaryRelativePath.split('/')]);
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--windows',
'--no-integration',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running plugin_test.exe...'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getWindowsBuildCall(plugin, _archDirX64),
ProcessCall(testBinary.path, const <String>[], null),
]));
});
test('runs unit tests with legacy build output', () async {
const String testBinaryRelativePath =
'build/windows/Debug/bar/plugin_test.exe';
final RepositoryPackage plugin =
@ -1797,7 +1848,7 @@ public class FlutterActivityTest {
], platformSupport: <String, PlatformDetails>{
platformWindows: const PlatformDetails(PlatformSupport.inline),
});
_createFakeCMakeCache(plugin, mockPlatform);
_createFakeCMakeCache(plugin, mockPlatform, null);
final File testBinary = childFileWithSubcomponents(plugin.directory,
<String>['example', ...testBinaryRelativePath.split('/')]);
@ -1819,16 +1870,16 @@ public class FlutterActivityTest {
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getWindowsBuildCall(plugin),
getWindowsBuildCall(plugin, null),
ProcessCall(testBinary.path, const <String>[], null),
]));
});
test('only runs debug unit tests', () async {
const String debugTestBinaryRelativePath =
'build/windows/Debug/bar/plugin_test.exe';
'build/windows/x64/Debug/bar/plugin_test.exe';
const String releaseTestBinaryRelativePath =
'build/windows/Release/bar/plugin_test.exe';
'build/windows/x64/Release/bar/plugin_test.exe';
final RepositoryPackage plugin =
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/$debugTestBinaryRelativePath',
@ -1836,7 +1887,7 @@ public class FlutterActivityTest {
], platformSupport: <String, PlatformDetails>{
platformWindows: const PlatformDetails(PlatformSupport.inline),
});
_createFakeCMakeCache(plugin, mockPlatform);
_createFakeCMakeCache(plugin, mockPlatform, _archDirX64);
final File debugTestBinary = childFileWithSubcomponents(
plugin.directory,
@ -1859,7 +1910,47 @@ public class FlutterActivityTest {
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getWindowsBuildCall(plugin),
getWindowsBuildCall(plugin, _archDirX64),
ProcessCall(debugTestBinary.path, const <String>[], null),
]));
});
test('only runs debug unit tests with legacy build output', () async {
const String debugTestBinaryRelativePath =
'build/windows/Debug/bar/plugin_test.exe';
const String releaseTestBinaryRelativePath =
'build/windows/Release/bar/plugin_test.exe';
final RepositoryPackage plugin =
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/$debugTestBinaryRelativePath',
'example/$releaseTestBinaryRelativePath'
], platformSupport: <String, PlatformDetails>{
platformWindows: const PlatformDetails(PlatformSupport.inline),
});
_createFakeCMakeCache(plugin, mockPlatform, null);
final File debugTestBinary = childFileWithSubcomponents(
plugin.directory,
<String>['example', ...debugTestBinaryRelativePath.split('/')]);
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--windows',
'--no-integration',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running plugin_test.exe...'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getWindowsBuildCall(plugin, null),
ProcessCall(debugTestBinary.path, const <String>[], null),
]));
});
@ -1896,7 +1987,7 @@ public class FlutterActivityTest {
platformSupport: <String, PlatformDetails>{
platformWindows: const PlatformDetails(PlatformSupport.inline),
});
_createFakeCMakeCache(plugin, mockPlatform);
_createFakeCMakeCache(plugin, mockPlatform, _archDirX64);
Error? commandError;
final List<String> output = await runCapturingPrint(runner, <String>[
@ -1918,20 +2009,20 @@ public class FlutterActivityTest {
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getWindowsBuildCall(plugin),
getWindowsBuildCall(plugin, _archDirX64),
]));
});
test('fails if a unit test fails', () async {
const String testBinaryRelativePath =
'build/windows/Debug/bar/plugin_test.exe';
'build/windows/x64/Debug/bar/plugin_test.exe';
final RepositoryPackage plugin =
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/$testBinaryRelativePath'
], platformSupport: <String, PlatformDetails>{
platformWindows: const PlatformDetails(PlatformSupport.inline),
});
_createFakeCMakeCache(plugin, mockPlatform);
_createFakeCMakeCache(plugin, mockPlatform, _archDirX64);
final File testBinary = childFileWithSubcomponents(plugin.directory,
<String>['example', ...testBinaryRelativePath.split('/')]);
@ -1961,10 +2052,138 @@ public class FlutterActivityTest {
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getWindowsBuildCall(plugin),
getWindowsBuildCall(plugin, _archDirX64),
ProcessCall(testBinary.path, const <String>[], null),
]));
});
});
group('Windows arm64', () {
setUp(() {
final NativeTestCommand command = NativeTestCommand(packagesDir,
processRunner: processRunner,
platform: mockPlatform,
abi: Abi.windowsArm64);
runner = CommandRunner<void>(
'native_test_command', 'Test for native_test_command');
runner.addCommand(command);
});
test('runs unit tests', () async {
const String x64TestBinaryRelativePath =
'build/windows/x64/Debug/bar/plugin_test.exe';
const String arm64TestBinaryRelativePath =
'build/windows/arm64/Debug/bar/plugin_test.exe';
final RepositoryPackage plugin =
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/$x64TestBinaryRelativePath',
'example/$arm64TestBinaryRelativePath',
], platformSupport: <String, PlatformDetails>{
platformWindows: const PlatformDetails(PlatformSupport.inline),
});
_createFakeCMakeCache(plugin, mockPlatform, _archDirArm64);
final File testBinary = childFileWithSubcomponents(plugin.directory,
<String>['example', ...arm64TestBinaryRelativePath.split('/')]);
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--windows',
'--no-integration',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running plugin_test.exe...'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getWindowsBuildCall(plugin, _archDirArm64),
ProcessCall(testBinary.path, const <String>[], null),
]));
});
test('falls back to x64 unit tests if arm64 is not built', () async {
const String x64TestBinaryRelativePath =
'build/windows/x64/Debug/bar/plugin_test.exe';
final RepositoryPackage plugin =
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/$x64TestBinaryRelativePath',
], platformSupport: <String, PlatformDetails>{
platformWindows: const PlatformDetails(PlatformSupport.inline),
});
_createFakeCMakeCache(plugin, mockPlatform, _archDirX64);
final File testBinary = childFileWithSubcomponents(plugin.directory,
<String>['example', ...x64TestBinaryRelativePath.split('/')]);
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--windows',
'--no-integration',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running plugin_test.exe...'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getWindowsBuildCall(plugin, _archDirX64),
ProcessCall(testBinary.path, const <String>[], null),
]));
});
test('only runs debug unit tests', () async {
const String debugTestBinaryRelativePath =
'build/windows/arm64/Debug/bar/plugin_test.exe';
const String releaseTestBinaryRelativePath =
'build/windows/arm64/Release/bar/plugin_test.exe';
final RepositoryPackage plugin =
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/$debugTestBinaryRelativePath',
'example/$releaseTestBinaryRelativePath'
], platformSupport: <String, PlatformDetails>{
platformWindows: const PlatformDetails(PlatformSupport.inline),
});
_createFakeCMakeCache(plugin, mockPlatform, _archDirArm64);
final File debugTestBinary = childFileWithSubcomponents(
plugin.directory,
<String>['example', ...debugTestBinaryRelativePath.split('/')]);
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--windows',
'--no-integration',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running plugin_test.exe...'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getWindowsBuildCall(plugin, _archDirArm64),
ProcessCall(debugTestBinary.path, const <String>[], null),
]));
});
});
});
}