From 6ef4c263ba16d41ac32ba88d21241ae0746e9ba8 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 31 Aug 2023 14:12:40 -0400 Subject: [PATCH 1/7] Add Windows arm64 support to native unit tests --- script/tool/lib/src/common/cmake.dart | 19 +- script/tool/lib/src/native_test_command.dart | 40 ++- .../tool/test/native_test_command_test.dart | 237 ++++++++++++++++-- 3 files changed, 256 insertions(+), 40 deletions(-) diff --git a/script/tool/lib/src/common/cmake.dart b/script/tool/lib/src/common/cmake.dart index 7d11039edd2..68533f6488f 100644 --- a/script/tool/lib/src/common/cmake.dart +++ b/script/tool/lib/src/common/cmake.dart @@ -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; } diff --git a/script/tool/lib/src/native_test_command.dart b/script/tool/lib/src/native_test_command.dart index e9fc3cc9664..79213ab427f 100644 --- a/script/tool/lib/src/native_test_command.dart +++ b/script/tool/lib/src/native_test_command.dart @@ -2,8 +2,11 @@ // 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'; +import 'package:path/path.dart'; import 'common/cmake.dart'; import 'common/core.dart'; @@ -41,7 +44,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 +68,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 _iOSDestinationFlags = []; @@ -548,9 +556,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 +572,30 @@ this command. final List testBinaries = []; bool hasMissingBuild = false; bool buildFailed = false; + String? arch; + if (platform.isWindows) { + arch = _abi == Abi.windowsX64 ? 'x64' : 'arm64'; + } 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 && !project.isConfigured()) { + // Check again without the arch subdirectory, since 3.13 doesn't + // have it yet. + // TODO(stuartmorgan): Remove this once Flutter 3.13 is no longer + // supported by the main repository CI. + 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 ' diff --git a/script/tool/test/native_test_command_test.dart b/script/tool/test/native_test_command_test.dart index a180c3cb09e..965118f0cc6 100644 --- a/script/tool/test/native_test_command_test.dart +++ b/script/tool/test/native_test_command_test.dart @@ -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 _kDeviceListMap = { }; 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: { platformLinux: const PlatformDetails(PlatformSupport.inline), }); - _createFakeCMakeCache(plugin, mockPlatform); + _createFakeCMakeCache(plugin, mockPlatform, _archDirX64); final File testBinary = childFileWithSubcomponents(plugin.directory, ['example', ...testBinaryRelativePath.split('/')]); @@ -1099,7 +1103,7 @@ public class FlutterActivityTest { ], platformSupport: { 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: { platformLinux: const PlatformDetails(PlatformSupport.inline), }); - _createFakeCMakeCache(plugin, mockPlatform); + _createFakeCMakeCache(plugin, mockPlatform, _archDirX64); Error? commandError; final List output = await runCapturingPrint(runner, [ @@ -1194,7 +1198,7 @@ public class FlutterActivityTest { ], platformSupport: { platformLinux: const PlatformDetails(PlatformSupport.inline), }); - _createFakeCMakeCache(plugin, mockPlatform); + _createFakeCMakeCache(plugin, mockPlatform, _archDirX64); final File testBinary = childFileWithSubcomponents(plugin.directory, ['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( - '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, [ '--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( + '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: [ + 'example/$x64TestBinaryRelativePath', + 'example/$arm64TestBinaryRelativePath', + ], platformSupport: { + platformWindows: const PlatformDetails(PlatformSupport.inline), + }); + _createFakeCMakeCache(plugin, mockPlatform, _archDirX64); + + final File testBinary = childFileWithSubcomponents(plugin.directory, + ['example', ...x64TestBinaryRelativePath.split('/')]); + + final List output = await runCapturingPrint(runner, [ + 'native-test', + '--windows', + '--no-integration', + ]); + + expect( + output, + containsAllInOrder([ + contains('Running plugin_test.exe...'), + contains('No issues found!'), + ]), + ); + + expect( + processRunner.recordedCalls, + orderedEquals([ + getWindowsBuildCall(plugin, _archDirX64), + ProcessCall(testBinary.path, const [], 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: { platformWindows: const PlatformDetails(PlatformSupport.inline), }); - _createFakeCMakeCache(plugin, mockPlatform); + _createFakeCMakeCache(plugin, mockPlatform, null); final File testBinary = childFileWithSubcomponents(plugin.directory, ['example', ...testBinaryRelativePath.split('/')]); @@ -1819,12 +1870,52 @@ public class FlutterActivityTest { expect( processRunner.recordedCalls, orderedEquals([ - getWindowsBuildCall(plugin), + getWindowsBuildCall(plugin, null), ProcessCall(testBinary.path, const [], null), ])); }); test('only runs debug unit tests', () async { + const String debugTestBinaryRelativePath = + 'build/windows/x64/Debug/bar/plugin_test.exe'; + const String releaseTestBinaryRelativePath = + 'build/windows/x64/Release/bar/plugin_test.exe'; + final RepositoryPackage plugin = + createFakePlugin('plugin', packagesDir, extraFiles: [ + 'example/$debugTestBinaryRelativePath', + 'example/$releaseTestBinaryRelativePath' + ], platformSupport: { + platformWindows: const PlatformDetails(PlatformSupport.inline), + }); + _createFakeCMakeCache(plugin, mockPlatform, _archDirX64); + + final File debugTestBinary = childFileWithSubcomponents( + plugin.directory, + ['example', ...debugTestBinaryRelativePath.split('/')]); + + final List output = await runCapturingPrint(runner, [ + 'native-test', + '--windows', + '--no-integration', + ]); + + expect( + output, + containsAllInOrder([ + contains('Running plugin_test.exe...'), + contains('No issues found!'), + ]), + ); + + expect( + processRunner.recordedCalls, + orderedEquals([ + getWindowsBuildCall(plugin, _archDirX64), + ProcessCall(debugTestBinary.path, const [], 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 = @@ -1836,7 +1927,7 @@ public class FlutterActivityTest { ], platformSupport: { platformWindows: const PlatformDetails(PlatformSupport.inline), }); - _createFakeCMakeCache(plugin, mockPlatform); + _createFakeCMakeCache(plugin, mockPlatform, null); final File debugTestBinary = childFileWithSubcomponents( plugin.directory, @@ -1859,7 +1950,7 @@ public class FlutterActivityTest { expect( processRunner.recordedCalls, orderedEquals([ - getWindowsBuildCall(plugin), + getWindowsBuildCall(plugin, null), ProcessCall(debugTestBinary.path, const [], null), ])); }); @@ -1896,7 +1987,7 @@ public class FlutterActivityTest { platformSupport: { platformWindows: const PlatformDetails(PlatformSupport.inline), }); - _createFakeCMakeCache(plugin, mockPlatform); + _createFakeCMakeCache(plugin, mockPlatform, _archDirX64); Error? commandError; final List output = await runCapturingPrint(runner, [ @@ -1918,20 +2009,20 @@ public class FlutterActivityTest { expect( processRunner.recordedCalls, orderedEquals([ - 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: [ 'example/$testBinaryRelativePath' ], platformSupport: { platformWindows: const PlatformDetails(PlatformSupport.inline), }); - _createFakeCMakeCache(plugin, mockPlatform); + _createFakeCMakeCache(plugin, mockPlatform, _archDirX64); final File testBinary = childFileWithSubcomponents(plugin.directory, ['example', ...testBinaryRelativePath.split('/')]); @@ -1961,10 +2052,102 @@ public class FlutterActivityTest { expect( processRunner.recordedCalls, orderedEquals([ - getWindowsBuildCall(plugin), + getWindowsBuildCall(plugin, _archDirX64), + ProcessCall(testBinary.path, const [], null), + ])); + }); + }); + + group('Windows arm64', () { + setUp(() { + final NativeTestCommand command = NativeTestCommand(packagesDir, + processRunner: processRunner, + platform: mockPlatform, + abi: Abi.windowsArm64); + + runner = CommandRunner( + '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: [ + 'example/$x64TestBinaryRelativePath', + 'example/$arm64TestBinaryRelativePath', + ], platformSupport: { + platformWindows: const PlatformDetails(PlatformSupport.inline), + }); + _createFakeCMakeCache(plugin, mockPlatform, _archDirArm64); + + final File testBinary = childFileWithSubcomponents(plugin.directory, + ['example', ...arm64TestBinaryRelativePath.split('/')]); + + final List output = await runCapturingPrint(runner, [ + 'native-test', + '--windows', + '--no-integration', + ]); + + expect( + output, + containsAllInOrder([ + contains('Running plugin_test.exe...'), + contains('No issues found!'), + ]), + ); + + expect( + processRunner.recordedCalls, + orderedEquals([ + getWindowsBuildCall(plugin, _archDirArm64), ProcessCall(testBinary.path, const [], 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: [ + 'example/$debugTestBinaryRelativePath', + 'example/$releaseTestBinaryRelativePath' + ], platformSupport: { + platformWindows: const PlatformDetails(PlatformSupport.inline), + }); + _createFakeCMakeCache(plugin, mockPlatform, _archDirArm64); + + final File debugTestBinary = childFileWithSubcomponents( + plugin.directory, + ['example', ...debugTestBinaryRelativePath.split('/')]); + + final List output = await runCapturingPrint(runner, [ + 'native-test', + '--windows', + '--no-integration', + ]); + + expect( + output, + containsAllInOrder([ + contains('Running plugin_test.exe...'), + contains('No issues found!'), + ]), + ); + + expect( + processRunner.recordedCalls, + orderedEquals([ + getWindowsBuildCall(plugin, _archDirArm64), + ProcessCall(debugTestBinary.path, const [], null), + ])); + }); }); }); } From 2c0be09c3702a75b8a50efa2ee02e71bb59f987c Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 31 Aug 2023 14:27:13 -0400 Subject: [PATCH 2/7] Add a roll for validation --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index 81b6a8cb426..2b0df52a91d 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -c175cf87a6a3d797fdc3f81cbd54419f639cd3d9 +792e26df9540cf2cf510ea0c11a53ba10a243424 From c538b0ead67c92d98c37b9edff4ea14b35b76ada Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 31 Aug 2023 14:57:38 -0400 Subject: [PATCH 3/7] Analysis fix --- script/tool/lib/src/native_test_command.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/script/tool/lib/src/native_test_command.dart b/script/tool/lib/src/native_test_command.dart index 79213ab427f..d5698769093 100644 --- a/script/tool/lib/src/native_test_command.dart +++ b/script/tool/lib/src/native_test_command.dart @@ -6,7 +6,6 @@ import 'dart:ffi'; import 'package:file/file.dart'; import 'package:meta/meta.dart'; -import 'package:path/path.dart'; import 'common/cmake.dart'; import 'common/core.dart'; From c96729139bfb4d6b14e97a4659702e75ac5cdd27 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 31 Aug 2023 15:06:38 -0400 Subject: [PATCH 4/7] Fix Pigeon test tooling --- packages/pigeon/tool/shared/test_suites.dart | 21 +++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/pigeon/tool/shared/test_suites.dart b/packages/pigeon/tool/shared/test_suites.dart index 1a5b853087c..376f01e2ec3 100644 --- a/packages/pigeon/tool/shared/test_suites.dart +++ b/packages/pigeon/tool/shared/test_suites.dart @@ -4,6 +4,7 @@ // ignore_for_file: avoid_print +import 'dart:ffi'; import 'dart:io' show File, Directory; import 'package:meta/meta.dart'; @@ -313,9 +314,23 @@ Future _runWindowsUnitTests() async { return compileCode; } - return runProcess( - '$examplePath/build/windows/plugins/test_plugin/Debug/test_plugin_test.exe', - []); + // Depending on the Flutter version, the build output path is different. + // Check for the new location first, and fall back to the old location, to + // support running against both stable and master. + // TODO(stuartmorgan): Remove all this when these tests no longer need to + // support Flutter 3.13, and just construct the version of the path with the + // architecture. + final String buildDirBase = '$examplePath/build/windows'; + const String buildRelativeBinaryPath = + 'plugins/test_plugin/Debug/test_plugin_test.exe'; + final String arch = Abi.current() == Abi.windowsArm64 ? 'arm64' : 'x64'; + final String newPath = '$buildDirBase/$arch/$buildRelativeBinaryPath'; + final String oldPath = '$buildDirBase/$buildRelativeBinaryPath'; + if (File(newPath).existsSync()) { + return runProcess(newPath, []); + } else { + return runProcess(oldPath, []); + } } Future _runWindowsIntegrationTests() async { From e724eaf629dafb809c4b1c5dae86ac54e2135173 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 31 Aug 2023 15:36:08 -0400 Subject: [PATCH 5/7] Analyzer fix --- packages/pigeon/tool/shared/test_suites.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pigeon/tool/shared/test_suites.dart b/packages/pigeon/tool/shared/test_suites.dart index 376f01e2ec3..73bb0b1ae02 100644 --- a/packages/pigeon/tool/shared/test_suites.dart +++ b/packages/pigeon/tool/shared/test_suites.dart @@ -320,12 +320,12 @@ Future _runWindowsUnitTests() async { // TODO(stuartmorgan): Remove all this when these tests no longer need to // support Flutter 3.13, and just construct the version of the path with the // architecture. - final String buildDirBase = '$examplePath/build/windows'; + const String buildDirBase = '$examplePath/build/windows'; const String buildRelativeBinaryPath = 'plugins/test_plugin/Debug/test_plugin_test.exe'; final String arch = Abi.current() == Abi.windowsArm64 ? 'arm64' : 'x64'; final String newPath = '$buildDirBase/$arch/$buildRelativeBinaryPath'; - final String oldPath = '$buildDirBase/$buildRelativeBinaryPath'; + const String oldPath = '$buildDirBase/$buildRelativeBinaryPath'; if (File(newPath).existsSync()) { return runProcess(newPath, []); } else { From d769cda489e6603c459f31e6c0a5df0805e0ad2d Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 31 Aug 2023 15:36:27 -0400 Subject: [PATCH 6/7] Undo roll --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index 2b0df52a91d..81b6a8cb426 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -792e26df9540cf2cf510ea0c11a53ba10a243424 +c175cf87a6a3d797fdc3f81cbd54419f639cd3d9 From acafb9d93dba8c2cd2e022211d9519657ca7196b Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 31 Aug 2023 19:17:32 -0400 Subject: [PATCH 7/7] Add x64 fallback on arm64 hosts --- packages/pigeon/tool/shared/test_suites.dart | 26 ++++++++------ script/tool/lib/src/native_test_command.dart | 35 ++++++++++++------ .../tool/test/native_test_command_test.dart | 36 +++++++++++++++++++ 3 files changed, 77 insertions(+), 20 deletions(-) diff --git a/packages/pigeon/tool/shared/test_suites.dart b/packages/pigeon/tool/shared/test_suites.dart index 73bb0b1ae02..39250f81653 100644 --- a/packages/pigeon/tool/shared/test_suites.dart +++ b/packages/pigeon/tool/shared/test_suites.dart @@ -4,7 +4,6 @@ // ignore_for_file: avoid_print -import 'dart:ffi'; import 'dart:io' show File, Directory; import 'package:meta/meta.dart'; @@ -314,20 +313,27 @@ Future _runWindowsUnitTests() async { return compileCode; } - // Depending on the Flutter version, the build output path is different. - // Check for the new location first, and fall back to the old location, to - // support running against both stable and master. + // Depending on the Flutter version, the build output path is different. To + // handle both master and stable, and to future-proof against the changes + // that will happen in https://github.com/flutter/flutter/issues/129807 + // - Try arm64, to future-proof against arm64 support. + // - Try x64, to cover pre-arm64 support on arm64 hosts, as well as x64 hosts + // running newer versions of Flutter. + // - Fall back to the pre-arch path, to support running against stable. // TODO(stuartmorgan): Remove all this when these tests no longer need to - // support Flutter 3.13, and just construct the version of the path with the - // architecture. + // support a version of Flutter without + // https://github.com/flutter/flutter/issues/129807, and just construct the + // version of the path with the current architecture. const String buildDirBase = '$examplePath/build/windows'; const String buildRelativeBinaryPath = 'plugins/test_plugin/Debug/test_plugin_test.exe'; - final String arch = Abi.current() == Abi.windowsArm64 ? 'arm64' : 'x64'; - final String newPath = '$buildDirBase/$arch/$buildRelativeBinaryPath'; + const String arm64Path = '$buildDirBase/arm64/$buildRelativeBinaryPath'; + const String x64Path = '$buildDirBase/x64/$buildRelativeBinaryPath'; const String oldPath = '$buildDirBase/$buildRelativeBinaryPath'; - if (File(newPath).existsSync()) { - return runProcess(newPath, []); + if (File(arm64Path).existsSync()) { + return runProcess(arm64Path, []); + } else if (File(x64Path).existsSync()) { + return runProcess(x64Path, []); } else { return runProcess(oldPath, []); } diff --git a/script/tool/lib/src/native_test_command.dart b/script/tool/lib/src/native_test_command.dart index d5698769093..7fc88944ba9 100644 --- a/script/tool/lib/src/native_test_command.dart +++ b/script/tool/lib/src/native_test_command.dart @@ -572,8 +572,10 @@ this command. bool hasMissingBuild = false; bool buildFailed = false; String? arch; + const String x64DirName = 'x64'; + const String arm64DirName = 'arm64'; if (platform.isWindows) { - arch = _abi == Abi.windowsX64 ? 'x64' : 'arm64'; + 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. @@ -585,15 +587,28 @@ this command. processRunner: processRunner, platform: platform, arch: arch); - if (platform.isWindows && !project.isConfigured()) { - // Check again without the arch subdirectory, since 3.13 doesn't - // have it yet. - // TODO(stuartmorgan): Remove this once Flutter 3.13 is no longer - // supported by the main repository CI. - project = CMakeProject(example.directory, - buildMode: buildMode, - processRunner: processRunner, - platform: platform); + 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}, ' diff --git a/script/tool/test/native_test_command_test.dart b/script/tool/test/native_test_command_test.dart index 965118f0cc6..993ee86d600 100644 --- a/script/tool/test/native_test_command_test.dart +++ b/script/tool/test/native_test_command_test.dart @@ -2109,6 +2109,42 @@ public class FlutterActivityTest { ])); }); + 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: [ + 'example/$x64TestBinaryRelativePath', + ], platformSupport: { + platformWindows: const PlatformDetails(PlatformSupport.inline), + }); + _createFakeCMakeCache(plugin, mockPlatform, _archDirX64); + + final File testBinary = childFileWithSubcomponents(plugin.directory, + ['example', ...x64TestBinaryRelativePath.split('/')]); + + final List output = await runCapturingPrint(runner, [ + 'native-test', + '--windows', + '--no-integration', + ]); + + expect( + output, + containsAllInOrder([ + contains('Running plugin_test.exe...'), + contains('No issues found!'), + ]), + ); + + expect( + processRunner.recordedCalls, + orderedEquals([ + getWindowsBuildCall(plugin, _archDirX64), + ProcessCall(testBinary.path, const [], null), + ])); + }); + test('only runs debug unit tests', () async { const String debugTestBinaryRelativePath = 'build/windows/arm64/Debug/bar/plugin_test.exe';