zhangmengvv / android-test-kit

Automatically exported from code.google.com/p/android-test-kit
0 stars 0 forks source link

Test hangs and timeout on setUp: GoogleInstumentation (ActivityFinisher) leaks activities between tests #66

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
What steps will reproduce the problem?
1. Create Test1 which goes from Activity A to Activity B to Activity C
2. Create Test 2 which does the same
3. Check the Activity Stack when Test 1 ends

What is the expected output? What do you see instead?
Test2 will start and Activity B is still on the Stack

What version of the product are you using? On what operating system?
1.1

Please provide any additional information below.
This can result in test hanging between each other and failing with a timeout 
on startActivitySync.

A fix is proposed and pushed 
here:https://code.google.com/r/luigimassagallerano-googleinstr/source/detail?r=0
bd56035ad407e061064e6ba2b5dec3df33a1887

Original issue reported on code.google.com by luigimas...@gmail.com on 10 Apr 2014 at 3:30

GoogleCodeExporter commented 9 years ago
I've noticed that this is only a bug on the emulator but doesn't happen on a 
real device. This is what I see:
 - Test1 runs to completion successfully
 - Test2 fails to run and produces the following exception:
java.lang.RuntimeException: Could not launch intent Intent { 
act=android.intent.action.MAIN flg=0x10000000 
cmp=com.example.multiactivity/.MainActivity } within 45 seconds. Perhaps the 
main thread has not gone idle within a reasonable amount of time? There could 
be an animation or something constantly repainting the screen. Or the activity 
is doing network calls on creation? See the threaddump logs. For your reference 
the last time the event queue was idle before your activity launch request was 
1399508271868 and and now the last time the queue went idle was: 1399508277083. 
If these numbers are the same your activity might be hogging the event queue.
at 
com.google.android.apps.common.testing.testrunner.GoogleInstrumentation.startAct
ivitySync(GoogleInstrumentation.java:277)
at 
android.test.InstrumentationTestCase.launchActivityWithIntent(InstrumentationTes
tCase.java:119)
at 
android.test.InstrumentationTestCase.launchActivity(InstrumentationTestCase.java
:97)
at 
android.test.ActivityInstrumentationTestCase2.getActivity(ActivityInstrumentatio
nTestCase2.java:104)
at com.example.multiactivity.test.MultiTest.setUp(MultiTest.java:22)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:191)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:176)
at 
android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:55
4)
at 
com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunne
r.onStart(GoogleInstrumentationTestRunner.java:167)
at 
android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1701)

Original comment by jie...@xero.com on 8 May 2014 at 12:19

GoogleCodeExporter commented 9 years ago
We were seeing this issue also on real devices. Luigi's commit fixes it for us.

Original comment by sschuberth on 8 May 2014 at 1:58

GoogleCodeExporter commented 9 years ago
I'll attach a dummy project which reproduces the problem as soon as I can. 

Original comment by luigimas...@gmail.com on 8 May 2014 at 2:34

GoogleCodeExporter commented 9 years ago
I'm currently working around the issue by calling Espresso.pressBack() on 
tearDown(). This is obviously just a workaround as it closes the last activity 
before attempting to end each test.

Original comment by jie...@xero.com on 8 May 2014 at 8:35

GoogleCodeExporter commented 9 years ago
I too have seen this quite a bit. Our project currently contains a mix of 
Robotium-based tests and Espresso-based tests therefore we have Robotium as a 
test dependency in our project. As a hacky workaround for Espresso tests where 
this issue reproduces, we use Solo.finishOpenedActivities() from Robotium 
during the tearDown() and this resolves the issue for now.

Original comment by mhernan...@gmail.com on 25 Jun 2014 at 3:26

GoogleCodeExporter commented 9 years ago
What does it take to turn the status of this from "New" to "Accepted"?

Original comment by sschuberth on 26 Jun 2014 at 7:48

GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
pressBack solution does not worked for me but I found another:

    @Override
    protected void tearDown() throws Exception {
        closeAllActivities(getInstrumentation());

        super.tearDown();
    }

    public static void closeAllActivities(Instrumentation instrumentation) throws Exception {
        final int NUMBER_OF_RETRIES = 100;
        int i = 0;
        while (closeActivity(instrumentation)) {
            if (i++ > NUMBER_OF_RETRIES) {
                throw new AssertionError("Limit of retries excesses");
            }
            Thread.sleep(200);
        }
    }

    public static <X> X callOnMainSync(Instrumentation instrumentation, final Callable<X> callable) throws Exception {
        final AtomicReference<X> retAtomic = new AtomicReference<>();
        final AtomicReference<Throwable> exceptionAtomic = new AtomicReference<>();
        instrumentation.runOnMainSync(new Runnable() {
            @Override
            public void run() {
                try {
                    retAtomic.set(callable.call());
                } catch (Throwable e) {
                    exceptionAtomic.set(e);
                }
            }
        });
        final Throwable exception = exceptionAtomic.get();
        if (exception != null) {
            Throwables.propagateIfInstanceOf(exception, Exception.class);
            Throwables.propagate(exception);
        }
        return retAtomic.get();
    }

    public static Set<Activity> getActivitiesInStages(Stage... stages) {
        final Set<Activity> activities = Sets.newHashSet();
        final ActivityLifecycleMonitor instance = ActivityLifecycleMonitorRegistry.getInstance();
        for (Stage stage : stages) {
            final Collection<Activity> activitiesInStage = instance.getActivitiesInStage(stage);
            if (activitiesInStage != null) {
                activities.addAll(activitiesInStage);
            }
        }
        return activities;
    }

    private static boolean closeActivity(Instrumentation instrumentation) throws Exception {
        final Boolean activityClosed = callOnMainSync(instrumentation, new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                final Set<Activity> activities = getActivitiesInStages(Stage.RESUMED,
                        Stage.STARTED, Stage.PAUSED, Stage.STOPPED, Stage.CREATED);
                activities.removeAll(getActivitiesInStages(Stage.DESTROYED));
                if (activities.size() > 0) {
                    final Activity activity = activities.iterator().next();
                    activity.finish();
                    return true;
                } else {
                    return false;
                }
            }
        });
        if (activityClosed) {
            instrumentation.waitForIdleSync();
        }
        return activityClosed;
    }

It works for quite large UI Espresso tests set ~400

(post fixed.. I posted old solution)

Original comment by jacek.ma...@gmail.com on 31 Jul 2014 at 6:41

GoogleCodeExporter commented 9 years ago
Very helpful solution, Jacek. Thanks!

Original comment by wangenhe...@gmail.com on 15 Oct 2014 at 10:51