@@ -40,6 +40,7 @@ class NativeTestCommand extends PackageLoopingCommand {
40
40
argParser.addFlag (kPlatformAndroid, help: 'Runs Android tests' );
41
41
argParser.addFlag (kPlatformIos, help: 'Runs iOS tests' );
42
42
argParser.addFlag (kPlatformMacos, help: 'Runs macOS tests' );
43
+ argParser.addFlag (kPlatformWindows, help: 'Runs Windows tests' );
43
44
44
45
// By default, both unit tests and integration tests are run, but provide
45
46
// flags to disable one or the other.
@@ -80,6 +81,7 @@ this command.
80
81
kPlatformAndroid: _PlatformDetails ('Android' , _testAndroid),
81
82
kPlatformIos: _PlatformDetails ('iOS' , _testIos),
82
83
kPlatformMacos: _PlatformDetails ('macOS' , _testMacOS),
84
+ kPlatformWindows: _PlatformDetails ('Windows' , _testWindows),
83
85
};
84
86
_requestedPlatforms = _platforms.keys
85
87
.where ((String platform) => getBoolArg (platform))
@@ -96,6 +98,11 @@ this command.
96
98
throw ToolExit (exitInvalidArguments);
97
99
}
98
100
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
+
99
106
// iOS-specific run-level state.
100
107
if (_requestedPlatforms.contains ('ios' )) {
101
108
String destination = getStringArg (_iosDestinationFlag);
@@ -119,16 +126,20 @@ this command.
119
126
Future <PackageResult > runForPackage (RepositoryPackage package) async {
120
127
final List <String > testPlatforms = < String > [];
121
128
for (final String platform in _requestedPlatforms) {
122
- if (pluginSupportsPlatform (platform, package,
129
+ if (! pluginSupportsPlatform (platform, package,
123
130
requiredMode: PlatformSupport .inline)) {
124
- testPlatforms.add (platform);
125
- } else {
126
131
print ('No implementation for ${_platforms [platform ]!.label }.' );
132
+ continue ;
127
133
}
134
+ if (! pluginHasNativeCodeForPlatform (platform, package)) {
135
+ print ('No native code for ${_platforms [platform ]!.label }.' );
136
+ continue ;
137
+ }
138
+ testPlatforms.add (platform);
128
139
}
129
140
130
141
if (testPlatforms.isEmpty) {
131
- return PackageResult .skip ('Not implemented for target platform(s).' );
142
+ return PackageResult .skip ('Nothing to test for target platform(s).' );
132
143
}
133
144
134
145
final _TestMode mode = _TestMode (
@@ -228,6 +239,8 @@ this command.
228
239
final bool hasIntegrationTests =
229
240
exampleHasNativeIntegrationTests (example);
230
241
242
+ // TODO(stuartmorgan): Make !hasUnitTests fatal. See
243
+ // https://github.com/flutter/flutter/issues/85469
231
244
if (mode.unit && ! hasUnitTests) {
232
245
_printNoExampleTestsMessage (example, 'Android unit' );
233
246
}
@@ -335,6 +348,9 @@ this command.
335
348
for (final RepositoryPackage example in plugin.getExamples ()) {
336
349
final String exampleName = example.displayName;
337
350
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
338
354
if (testTarget != null ) {
339
355
final Directory project = example.directory
340
356
.childDirectory (platform.toLowerCase ())
@@ -387,6 +403,71 @@ this command.
387
403
return _PlatformResult (overallResult);
388
404
}
389
405
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
+
390
471
/// Prints a standard format message indicating that [platform] tests for
391
472
/// [plugin] 's [example] are about to be run.
392
473
void _printRunningExampleTestsMessage (
0 commit comments