package com.google.android.apps.common.testing.ui.espresso;
import static com.google.android.apps.common.testing.ui.espresso.Espresso.onView;
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.click;
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.typeText;
import static com.google.android.apps.common.testing.ui.espresso.assertion.ViewAssertions.matches;
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.isAssignableFrom;
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withId;
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withText;
import com.google.android.apps.common.testing.ui.testapp.R;
import com.google.android.apps.common.testing.ui.testapp.SendActivity;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.LargeTest;
import android.view.View;
import org.hamcrest.Matcher;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Collection of some nasty edge cases.
*/
@LargeTest
public class EspressoEdgeCaseTest extends ActivityInstrumentationTestCase2<SendActivity> {
@SuppressWarnings("deprecation")
public EspressoEdgeCaseTest() {
// Supporting froyo.
super("com.google.android.apps.common.testing.ui.testapp", SendActivity.class);
}
private static final Callable<Void> NO_OP = new Callable<Void>() {
@Override
public Void call() {
return null;
}
};
private Handler mainHandler;
private OneShotResource oneShotResource;
@Override
public void setUp() throws Exception {
super.setUp();
getActivity();
mainHandler = new Handler(Looper.getMainLooper());
oneShotResource = new OneShotResource();
}
@Override
public void tearDown() throws Exception {
IdlingPolicies.setMasterPolicyTimeout(60, TimeUnit.SECONDS);
IdlingPolicies.setIdlingResourceTimeout(26, TimeUnit.SECONDS);
oneShotResource.setIdle(true);
super.tearDown();
}
@SuppressWarnings("unchecked")
public void testRecoveryFromExceptionOnMainThreadLoopMainThreadUntilIdle() throws Exception {
final RuntimeException poison = new RuntimeException("oops");
try {
onView(withId(R.id.enter_data_edit_text))
.perform(
new TestAction() {
@Override
public void perform(UiController controller, View view) {
mainHandler.post(new Runnable() {
@Override
public void run() {
throw poison;
}});
controller.loopMainThreadUntilIdle();
}
});
fail("should throw");
} catch (RuntimeException re) {
if (re == poison) {
// expected
} else {
// something else.
throw re;
}
}
// life should continue normally.
onView(withId(R.id.enter_data_edit_text))
.perform(typeText("Hello World111"));
onView(withId(R.id.enter_data_edit_text))
.check(matches(withText("Hello World111")));
}
@SuppressWarnings("unchecked")
public void testRecoveryFromExceptionOnMainThreadLoopMainThreadForAtLeast() throws Exception {
final RuntimeException poison = new RuntimeException("oops");
final FutureTask<Void> syncTask = new FutureTask<Void>(NO_OP);
try {
onView(withId(R.id.enter_data_edit_text))
.perform(
new TestAction() {
@Override
public void perform(UiController controller, View view) {
mainHandler.post(new Runnable() {
@Override
public void run() {
throw poison;
}});
// block test execution until loopMainThreadForAtLeast call
// would be satisified
mainHandler.postDelayed(syncTask, 2500);
controller.loopMainThreadForAtLeast(2000);
}
});
fail("should throw");
} catch (RuntimeException re) {
if (re == poison) {
// expected
} else {
// something else.
throw re;
}
}
syncTask.get();
// life should continue normally.
onView(withId(R.id.enter_data_edit_text))
.perform(typeText("baz bar"));
onView(withId(R.id.enter_data_edit_text))
.check(matches(withText("baz bar")));
}
@SuppressWarnings("unchecked")
public void testRecoveryFromTimeOutExceptionMaster() throws Exception {
IdlingPolicies.setMasterPolicyTimeout(2, TimeUnit.SECONDS);
final FutureTask<Void> syncTask = new FutureTask<Void>(NO_OP);
try {
onView(withId(R.id.enter_data_edit_text))
.perform(
new TestAction() {
@Override
public void perform(UiController controller, View view) {
mainHandler.post(new Runnable() {
@Override
public void run() {
SystemClock.sleep(TimeUnit.SECONDS.toMillis(8));
}
});
// block test execution until loopMainThreadForAtLeast call
// would be satisified
mainHandler.postDelayed(syncTask, 2500);
controller.loopMainThreadForAtLeast(1000);
}
});
fail("should throw");
} catch (RuntimeException re) {
if (re instanceof EspressoException) {
// expected
} else {
// something else.
throw re;
}
}
syncTask.get();
// life should continue normally.
onView(withId(R.id.enter_data_edit_text))
.perform(typeText("one two three"));
onView(withId(R.id.enter_data_edit_text))
.check(matches(withText("one two three")));
}
@SuppressWarnings("unchecked")
public void testRecoveryFromTimeOutExceptionDynamic() {
IdlingPolicies.setIdlingResourceTimeout(2, TimeUnit.SECONDS);
Espresso.registerIdlingResources(oneShotResource);
oneShotResource.setIdle(false);
try {
onView(withId(R.id.enter_data_edit_text))
.perform(click());
fail("should throw");
} catch (RuntimeException re) {
if (re instanceof EspressoException) {
// expected
} else {
// something else.
throw re;
}
}
oneShotResource.setIdle(true);
// life should continue normally.
onView(withId(R.id.enter_data_edit_text))
.perform(typeText("Doh"));
onView(withId(R.id.enter_data_edit_text))
.check(matches(withText("Doh")));
}
public void testRecoveryFromAsyncTaskTimeout() throws Exception {
IdlingPolicies.setMasterPolicyTimeout(2, TimeUnit.SECONDS);
try {
onView(withId(R.id.enter_data_edit_text))
.perform(new TestAction() {
@Override
public void perform(UiController controller, View view) {
new AsyncTask<Void, Void, Void>() {
@Override
public Void doInBackground(Void... params) {
SystemClock.sleep(TimeUnit.SECONDS.toMillis(8));
return null;
}
}.execute();
// block test execution until loopMainThreadForAtLeast call
// would be satisified
controller.loopMainThreadForAtLeast(1000);
}
});
fail("should throw");
} catch (RuntimeException re) {
if (re instanceof EspressoException) {
// expected
} else {
// something else.
throw re;
}
}
IdlingPolicies.setMasterPolicyTimeout(60, TimeUnit.SECONDS);
// life should continue normally.
onView(withId(R.id.enter_data_edit_text))
.perform(typeText("Har Har"));
onView(withId(R.id.enter_data_edit_text))
.check(matches(withText("Har Har")));
}
private abstract static class TestAction implements ViewAction {
@Override
public String getDescription() {
return "A random test action.";
}
@Override
public Matcher<View> getConstraints() {
return isAssignableFrom(View.class);
}
}
private static class OneShotResource implements IdlingResource {
private static AtomicInteger counter = new AtomicInteger(0);
private final int instance;
private volatile IdlingResource.ResourceCallback callback;
private volatile boolean isIdle = true;
private OneShotResource() {
instance = counter.incrementAndGet();
}
@Override
public String getName() {
return "TestOneShotResource_" + counter;
}
public void setIdle(boolean idle) {
isIdle = idle;
if (isIdle && callback != null) {
callback.onTransitionToIdle();
}
}
@Override
public boolean isIdleNow() {
return isIdle;
}
@Override
public void registerIdleTransitionCallback(IdlingResource.ResourceCallback callback) {
this.callback = callback;
}
}
}