@@ -40,6 +40,7 @@ class NativeTestCommand extends PackageLoopingCommand {
4040 argParser.addFlag (kPlatformAndroid, help: 'Runs Android tests' );
4141 argParser.addFlag (kPlatformIos, help: 'Runs iOS tests' );
4242 argParser.addFlag (kPlatformMacos, help: 'Runs macOS tests' );
43+ argParser.addFlag (kPlatformWindows, help: 'Runs Windows tests' );
4344
4445 // By default, both unit tests and integration tests are run, but provide
4546 // flags to disable one or the other.
@@ -80,6 +81,7 @@ this command.
8081 kPlatformAndroid: _PlatformDetails ('Android' , _testAndroid),
8182 kPlatformIos: _PlatformDetails ('iOS' , _testIos),
8283 kPlatformMacos: _PlatformDetails ('macOS' , _testMacOS),
84+ kPlatformWindows: _PlatformDetails ('Windows' , _testWindows),
8385 };
8486 _requestedPlatforms = _platforms.keys
8587 .where ((String platform) => getBoolArg (platform))
@@ -96,6 +98,11 @@ this command.
9698 throw ToolExit (exitInvalidArguments);
9799 }
98100
101+ if (getBoolArg (kPlatformWindows) && getBoolArg (_integrationTestFlag)) {
102+ logWarning ('This command currently only supports unit tests for Windows. '
103+ 'See https://github.com/flutter/flutter/issues/70233.' );
104+ }
105+
99106 // iOS-specific run-level state.
100107 if (_requestedPlatforms.contains ('ios' )) {
101108 String destination = getStringArg (_iosDestinationFlag);
@@ -119,16 +126,20 @@ this command.
119126 Future <PackageResult > runForPackage (RepositoryPackage package) async {
120127 final List <String > testPlatforms = < String > [];
121128 for (final String platform in _requestedPlatforms) {
122- if (pluginSupportsPlatform (platform, package,
129+ if (! pluginSupportsPlatform (platform, package,
123130 requiredMode: PlatformSupport .inline)) {
124- testPlatforms.add (platform);
125- } else {
126131 print ('No implementation for ${_platforms [platform ]!.label }.' );
132+ continue ;
127133 }
134+ if (! pluginHasNativeCodeForPlatform (platform, package)) {
135+ print ('No native code for ${_platforms [platform ]!.label }.' );
136+ continue ;
137+ }
138+ testPlatforms.add (platform);
128139 }
129140
130141 if (testPlatforms.isEmpty) {
131- return PackageResult .skip ('Not implemented for target platform(s).' );
142+ return PackageResult .skip ('Nothing to test for target platform(s).' );
132143 }
133144
134145 final _TestMode mode = _TestMode (
@@ -228,6 +239,8 @@ this command.
228239 final bool hasIntegrationTests =
229240 exampleHasNativeIntegrationTests (example);
230241
242+ // TODO(stuartmorgan): Make !hasUnitTests fatal. See
243+ // https://github.com/flutter/flutter/issues/85469
231244 if (mode.unit && ! hasUnitTests) {
232245 _printNoExampleTestsMessage (example, 'Android unit' );
233246 }
@@ -335,6 +348,9 @@ this command.
335348 for (final RepositoryPackage example in plugin.getExamples ()) {
336349 final String exampleName = example.displayName;
337350
351+ // TODO(stuartmorgan): Always check for RunnerTests, and make it fatal if
352+ // no examples have it. See
353+ // https://github.com/flutter/flutter/issues/85469
338354 if (testTarget != null ) {
339355 final Directory project = example.directory
340356 .childDirectory (platform.toLowerCase ())
@@ -387,6 +403,71 @@ this command.
387403 return _PlatformResult (overallResult);
388404 }
389405
406+ Future <_PlatformResult > _testWindows (
407+ RepositoryPackage plugin, _TestMode mode) async {
408+ if (mode.integrationOnly) {
409+ return _PlatformResult (RunState .skipped);
410+ }
411+
412+ bool isTestBinary (File file) {
413+ return file.basename.endsWith ('_test.exe' ) ||
414+ file.basename.endsWith ('_tests.exe' );
415+ }
416+
417+ return _runGoogleTestTests (plugin,
418+ buildDirectoryName: 'windows' , isTestBinary: isTestBinary);
419+ }
420+
421+ /// Finds every file in the [buildDirectoryName] subdirectory of [plugin] 's
422+ /// build directory for which [isTestBinary] is true, and runs all of them,
423+ /// returning the overall result.
424+ ///
425+ /// The binaries are assumed to be Google Test test binaries, thus returning
426+ /// zero for success and non-zero for failure.
427+ Future <_PlatformResult > _runGoogleTestTests (
428+ RepositoryPackage plugin, {
429+ required String buildDirectoryName,
430+ required bool Function (File ) isTestBinary,
431+ }) async {
432+ final List <File > testBinaries = < File > [];
433+ for (final RepositoryPackage example in plugin.getExamples ()) {
434+ final Directory buildDir = example.directory
435+ .childDirectory ('build' )
436+ .childDirectory (buildDirectoryName);
437+ if (! buildDir.existsSync ()) {
438+ continue ;
439+ }
440+ testBinaries.addAll (buildDir
441+ .listSync (recursive: true )
442+ .whereType <File >()
443+ .where (isTestBinary)
444+ .where ((File file) {
445+ // Only run the debug build of the unit tests, to avoid running the
446+ // same tests multiple times.
447+ final List <String > components = path.split (file.path);
448+ return components.contains ('debug' ) || components.contains ('Debug' );
449+ }));
450+ }
451+
452+ if (testBinaries.isEmpty) {
453+ final String binaryExtension = platform.isWindows ? '.exe' : '' ;
454+ printError (
455+ 'No test binaries found. At least one *_test(s)$binaryExtension '
456+ 'binary should be built by the example(s)' );
457+ return _PlatformResult (RunState .failed,
458+ error: 'No $buildDirectoryName unit tests found' );
459+ }
460+
461+ bool passing = true ;
462+ for (final File test in testBinaries) {
463+ print ('Running ${test .basename }...' );
464+ final int exitCode =
465+ await processRunner.runAndStream (test.path, < String > []);
466+ passing & = exitCode == 0 ;
467+ }
468+ return _PlatformResult (passing ? RunState .succeeded : RunState .failed);
469+ }
470+
390471 /// Prints a standard format message indicating that [platform] tests for
391472 /// [plugin] 's [example] are about to be run.
392473 void _printRunningExampleTestsMessage (
0 commit comments