3
3
// found in the LICENSE file.
4
4
5
5
import 'package:file/file.dart' ;
6
+ import 'package:meta/meta.dart' ;
6
7
import 'package:platform/platform.dart' ;
7
8
8
9
import 'common/cmake.dart' ;
@@ -21,6 +22,16 @@ const String _iOSDestinationFlag = 'ios-destination';
21
22
22
23
const int _exitNoIOSSimulators = 3 ;
23
24
25
+ /// The error message logged when a FlutterTestRunner test is not annotated with
26
+ /// @DartIntegrationTest.
27
+ @visibleForTesting
28
+ const String misconfiguredJavaIntegrationTestErrorExplanation =
29
+ 'The following files use @RunWith(FlutterTestRunner.class) '
30
+ 'but not @DartIntegrationTest, which will cause hangs when run with '
31
+ 'this command. See '
32
+ 'https://github.com/flutter/flutter/wiki/Plugin-Tests#enabling-android-ui-tests '
33
+ 'for instructions.' ;
34
+
24
35
/// The command to run native tests for plugins:
25
36
/// - iOS and macOS: XCTests (XCUnitTest and XCUITest)
26
37
/// - Android: JUnit tests
@@ -211,7 +222,17 @@ this command.
211
222
.existsSync ();
212
223
}
213
224
214
- bool exampleHasNativeIntegrationTests (RepositoryPackage example) {
225
+ _JavaTestInfo getJavaTestInfo (File testFile) {
226
+ final List <String > contents = testFile.readAsLinesSync ();
227
+ return _JavaTestInfo (
228
+ usesFlutterTestRunner: contents.any ((String line) =>
229
+ line.trimLeft ().startsWith ('@RunWith(FlutterTestRunner.class)' )),
230
+ hasDartIntegrationTestAnnotation: contents.any ((String line) =>
231
+ line.trimLeft ().startsWith ('@DartIntegrationTest' )));
232
+ }
233
+
234
+ Map <File , _JavaTestInfo > findIntegrationTestFiles (
235
+ RepositoryPackage example) {
215
236
final Directory integrationTestDirectory = example
216
237
.platformDirectory (FlutterPlatform .android)
217
238
.childDirectory ('app' )
@@ -220,25 +241,30 @@ this command.
220
241
// There are two types of integration tests that can be in the androidTest
221
242
// directory:
222
243
// - FlutterTestRunner.class tests, which bridge to Dart integration tests
223
- // - Purely native tests
244
+ // - Purely native integration tests
224
245
// Only the latter is supported by this command; the former will hang if
225
246
// run here because they will wait for a Dart call that will never come.
226
247
//
227
- // This repository uses a convention of putting the former in a
228
- // *ActivityTest.java file, so ignore that file when checking for tests.
229
- // Also ignore DartIntegrationTest.java, which defines the annotation used
230
- // below for filtering the former out when running tests.
248
+ // Find all test files, and determine which kind of test they are based on
249
+ // the annotations they use.
231
250
//
232
- // If those are the only files, then there are no tests to run here.
233
- return integrationTestDirectory.existsSync () &&
234
- integrationTestDirectory
235
- .listSync (recursive: true )
236
- .whereType <File >()
237
- .any ((File file) {
238
- final String basename = file.basename;
239
- return ! basename.endsWith ('ActivityTest.java' ) &&
240
- basename != 'DartIntegrationTest.java' ;
241
- });
251
+ // Ignore DartIntegrationTest.java, which defines the annotation used
252
+ // below for filtering the former out when running tests.
253
+ if (! integrationTestDirectory.existsSync ()) {
254
+ return < File , _JavaTestInfo > {};
255
+ }
256
+ final Iterable <File > integrationTestFiles = integrationTestDirectory
257
+ .listSync (recursive: true )
258
+ .whereType <File >()
259
+ .where ((File file) {
260
+ final String basename = file.basename;
261
+ return basename != 'DartIntegrationTest.java' &&
262
+ basename != 'DartIntegrationTest.kt' ;
263
+ });
264
+ return < File , _JavaTestInfo > {
265
+ for (final File file in integrationTestFiles)
266
+ file: getJavaTestInfo (file)
267
+ };
242
268
}
243
269
244
270
final Iterable <RepositoryPackage > examples = plugin.getExamples ();
@@ -247,10 +273,17 @@ this command.
247
273
bool ranAnyTests = false ;
248
274
bool failed = false ;
249
275
bool hasMissingBuild = false ;
276
+ bool hasMisconfiguredIntegrationTest = false ;
277
+ // Iterate through all examples (in the rare case that there is more than
278
+ // one example); running any tests found for each one. Requirements on what
279
+ // tests are present are enforced at the overall package level, not a per-
280
+ // example level. E.g., it's fine for only one example to have unit tests.
250
281
for (final RepositoryPackage example in examples) {
251
282
final bool hasUnitTests = exampleHasUnitTests (example);
252
- final bool hasIntegrationTests =
253
- exampleHasNativeIntegrationTests (example);
283
+ final Map <File , _JavaTestInfo > candidateIntegrationTestFiles =
284
+ findIntegrationTestFiles (example);
285
+ final bool hasIntegrationTests = candidateIntegrationTestFiles.values
286
+ .any ((_JavaTestInfo info) => ! info.hasDartIntegrationTestAnnotation);
254
287
255
288
if (mode.unit && ! hasUnitTests) {
256
289
_printNoExampleTestsMessage (example, 'Android unit' );
@@ -295,24 +328,41 @@ this command.
295
328
296
329
if (runIntegrationTests) {
297
330
// FlutterTestRunner-based tests will hang forever if run in a normal
298
- // app build, since they wait for a Dart call from integration_test that
299
- // will never come. Those tests have an extra annotation to allow
331
+ // app build, since they wait for a Dart call from integration_test
332
+ // that will never come. Those tests have an extra annotation to allow
300
333
// filtering them out.
301
- const String filter =
302
- 'notAnnotation=io.flutter.plugins.DartIntegrationTest' ;
303
-
304
- print ('Running integration tests...' );
305
- final int exitCode = await project.runCommand (
306
- 'app:connectedAndroidTest' ,
307
- arguments: < String > [
308
- '-Pandroid.testInstrumentationRunnerArguments.$filter ' ,
309
- ],
310
- );
311
- if (exitCode != 0 ) {
312
- printError ('$exampleName integration tests failed.' );
313
- failed = true ;
334
+ final List <File > misconfiguredTestFiles = candidateIntegrationTestFiles
335
+ .entries
336
+ .where ((MapEntry <File , _JavaTestInfo > entry) =>
337
+ entry.value.usesFlutterTestRunner &&
338
+ ! entry.value.hasDartIntegrationTestAnnotation)
339
+ .map ((MapEntry <File , _JavaTestInfo > entry) => entry.key)
340
+ .toList ();
341
+ if (misconfiguredTestFiles.isEmpty) {
342
+ // Ideally we would filter out @RunWith(FlutterTestRunner.class)
343
+ // tests directly, but there doesn't seem to be a way to filter based
344
+ // on annotation contents, so the DartIntegrationTest annotation was
345
+ // created as a proxy for that.
346
+ const String filter =
347
+ 'notAnnotation=io.flutter.plugins.DartIntegrationTest' ;
348
+
349
+ print ('Running integration tests...' );
350
+ final int exitCode = await project.runCommand (
351
+ 'app:connectedAndroidTest' ,
352
+ arguments: < String > [
353
+ '-Pandroid.testInstrumentationRunnerArguments.$filter ' ,
354
+ ],
355
+ );
356
+ if (exitCode != 0 ) {
357
+ printError ('$exampleName integration tests failed.' );
358
+ failed = true ;
359
+ }
360
+ ranAnyTests = true ;
361
+ } else {
362
+ hasMisconfiguredIntegrationTest = true ;
363
+ printError ('$misconfiguredJavaIntegrationTestErrorExplanation \n '
364
+ '${misconfiguredTestFiles .map ((File f ) => ' ${f .path }' ).join ('\n ' )}' );
314
365
}
315
- ranAnyTests = true ;
316
366
}
317
367
}
318
368
@@ -322,6 +372,10 @@ this command.
322
372
? 'Examples must be built before testing.'
323
373
: null );
324
374
}
375
+ if (hasMisconfiguredIntegrationTest) {
376
+ return _PlatformResult (RunState .failed,
377
+ error: 'Misconfigured integration test.' );
378
+ }
325
379
if (! mode.integrationOnly && ! ranUnitTests) {
326
380
printError ('No unit tests ran. Plugins are required to have unit tests.' );
327
381
return _PlatformResult (RunState .failed,
@@ -622,3 +676,16 @@ class _PlatformResult {
622
676
/// Ignored unless [state] is `failed` .
623
677
final String ? error;
624
678
}
679
+
680
+ /// The state of a .java test file.
681
+ class _JavaTestInfo {
682
+ const _JavaTestInfo (
683
+ {required this .usesFlutterTestRunner,
684
+ required this .hasDartIntegrationTestAnnotation});
685
+
686
+ /// Whether the test class uses the FlutterTestRunner.
687
+ final bool usesFlutterTestRunner;
688
+
689
+ /// Whether the test class has the @DartIntegrationTest annotation.
690
+ final bool hasDartIntegrationTestAnnotation;
691
+ }
0 commit comments