33// found in the LICENSE file.
44
55import 'package:file/file.dart' ;
6+ import 'package:meta/meta.dart' ;
67import 'package:platform/platform.dart' ;
78
89import 'common/cmake.dart' ;
@@ -21,6 +22,16 @@ const String _iOSDestinationFlag = 'ios-destination';
2122
2223const int _exitNoIOSSimulators = 3 ;
2324
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+
2435/// The command to run native tests for plugins:
2536/// - iOS and macOS: XCTests (XCUnitTest and XCUITest)
2637/// - Android: JUnit tests
@@ -211,7 +222,17 @@ this command.
211222 .existsSync ();
212223 }
213224
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) {
215236 final Directory integrationTestDirectory = example
216237 .platformDirectory (FlutterPlatform .android)
217238 .childDirectory ('app' )
@@ -220,25 +241,30 @@ this command.
220241 // There are two types of integration tests that can be in the androidTest
221242 // directory:
222243 // - FlutterTestRunner.class tests, which bridge to Dart integration tests
223- // - Purely native tests
244+ // - Purely native integration tests
224245 // Only the latter is supported by this command; the former will hang if
225246 // run here because they will wait for a Dart call that will never come.
226247 //
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.
231250 //
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+ };
242268 }
243269
244270 final Iterable <RepositoryPackage > examples = plugin.getExamples ();
@@ -247,10 +273,17 @@ this command.
247273 bool ranAnyTests = false ;
248274 bool failed = false ;
249275 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.
250281 for (final RepositoryPackage example in examples) {
251282 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);
254287
255288 if (mode.unit && ! hasUnitTests) {
256289 _printNoExampleTestsMessage (example, 'Android unit' );
@@ -295,24 +328,41 @@ this command.
295328
296329 if (runIntegrationTests) {
297330 // 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
300333 // 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 ' )}' );
314365 }
315- ranAnyTests = true ;
316366 }
317367 }
318368
@@ -322,6 +372,10 @@ this command.
322372 ? 'Examples must be built before testing.'
323373 : null );
324374 }
375+ if (hasMisconfiguredIntegrationTest) {
376+ return _PlatformResult (RunState .failed,
377+ error: 'Misconfigured integration test.' );
378+ }
325379 if (! mode.integrationOnly && ! ranUnitTests) {
326380 printError ('No unit tests ran. Plugins are required to have unit tests.' );
327381 return _PlatformResult (RunState .failed,
@@ -622,3 +676,16 @@ class _PlatformResult {
622676 /// Ignored unless [state] is `failed` .
623677 final String ? error;
624678}
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