stablekernel / RxEspresso

Filling the gap between RxJava and Espresso
Apache License 2.0
79 stars 12 forks source link

Doesn't work with RxBiding #4

Closed ciprig closed 7 years ago

ciprig commented 8 years ago

RxBiding use hot observables which are never unsubscribe. A solution will be to only count for observables that don't execute on main thread, that might be complicated.

realdadfish commented 8 years ago

This implementation does not work with RxLifecycle either and, and as Dan Lew pointed out:

I don't agree with how RxIdlingResource works: a subscription isn't an indicator of background activity! There are plenty of other cases, for example if you're using a subscription detect changes in an EditText, or a subscription for when a button is clicked, or..., or... that's the point of Rx.

Source: https://github.com/trello/RxLifecycle/issues/65

So I tried to tackle the problem from a different angle - instead of looking at the individual observables / subscriptions I look at the schedulers and came up with this:

public class RxSchedulersHook extends RxJavaSchedulersHook implements IdlingResource {
    private static final String TAG = RxSchedulersHook.class.getSimpleName();

    private static RxSchedulersHook sInstance;

    private final AtomicInteger mScheduledActions = new AtomicInteger(0);

    private ResourceCallback mResourceCallback;

    private RxSchedulersHook() {
    }

    public static RxSchedulersHook get() {
        if (sInstance == null) {
            sInstance = new RxSchedulersHook();
            Espresso.registerIdlingResources(sInstance);
        }
        return sInstance;
    }

    @Override
    public Action0 onSchedule(final Action0 action) {
        // This is a dirty hack: Since our implementation does not know from where it was scheduled, it also does not
        // know whether or not our scheduled action will return immediately or block / recursively schedule itself again.
        //
        // The only way to kind-of getting this information is by looking at the current stack trace and check whether
        // we have been scheduledPeriodically; in this case we skip schedule counting and just return the action undecorated.
        if (scheduledPeriodically()) {
            return action;
        }

        int current = mScheduledActions.incrementAndGet();
        Timber.tag(TAG).d("scheduling action %s (%d)", action, current);
        return () -> {
            try {
                action.call();
            } finally {
                int remaining = mScheduledActions.decrementAndGet();
                Timbe.tag(TAG).d("action %s executed (%d)", action, remaining);
                if (remaining == 0) {
                    mResourceCallback.onTransitionToIdle();
                }
            }
        };
    }

    private boolean scheduledPeriodically() {
        for (StackTraceElement el : Thread.currentThread().getStackTrace()) {
            if (el.getMethodName().equals("schedulePeriodically")) {
                return true;
            }
        }
        return false;
    }

    @Override
    public String getName() {
        return TAG;
    }

    @Override
    public boolean isIdleNow() {
        return mScheduledActions.get() == 0;
    }

    @Override
    public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
        mResourceCallback = resourceCallback;
    }
}

Unfortunately this does not work well under all circumstances, e.g. I have found that ScheduledUnsubscribe (a private inner worker of the public OperatorObserveOn) might dead-lock itself somehow while it is unscheduling itself and as such prevents the IdlingResource from getting idle. I'm still on it...

rosshambrick commented 8 years ago

This is of course not ideal and is a current limitation of the library (as stated in the readme). I haven't had the time to dig in to find other (better) solutions to this but I do welcome contributions or alternative libraries that handle this better. This does however work quite well if your usage of RxJava is limited to one-shot async calls as we typically use them.

msgitter commented 8 years ago

@tommyd3mdi Thx for sharing your try of tracking the actions. Have you found a solution for the ScheduledUnsubscribe dead-lock problem?

tir38 commented 7 years ago

Now that we are manually counting by increment/decrement, this should work with RxBinding