package roboguice.util; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertThat; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.shadows.ShadowLooper; import android.os.Looper; import android.util.Log; @RunWith(RobolectricTestRunner.class) public class AndroidCallableTest { @Test public void shouldCallMethodsUsingProperThreads() throws Exception { final Thread fgThread = Thread.currentThread(); final Thread[] bgThread = {null}; final Thread[] answers = new Thread[5]; final ShadowLooper looper = Robolectric.shadowOf(Looper.getMainLooper()); Executors.newSingleThreadExecutor(new MyThreadFactory(bgThread)).submit(new StringAndroidCallable(answers,false)); // Run all the pending tasks on the ui thread while(answers[answers.length-1]==null) looper.runToEndOfTasks(); final Thread[] correctAnswer = new Thread[]{fgThread, bgThread[0], null, fgThread, fgThread }; assertThat(answers, equalTo(correctAnswer)); } @Test public void shouldCallMethodsUsingProperThreadsWithException() throws Exception { final Thread fgThread = Thread.currentThread(); final Thread[] bgThread = {null}; final Thread[] answers = new Thread[5]; final ShadowLooper looper = Robolectric.shadowOf(Looper.getMainLooper()); Executors.newSingleThreadExecutor(new MyThreadFactory(bgThread)).submit(new StringAndroidCallable(answers,true)); // Run all the pending tasks on the ui thread while(answers[answers.length-1]==null) looper.runToEndOfTasks(); final Thread[] correctAnswer = new Thread[]{fgThread, bgThread[0], fgThread, null, fgThread }; assertThat( answers, equalTo(correctAnswer)); } @Test public void shouldHaveCorrectStackTrace() { final Exception[] exception = {null}; final StackTraceElement[] here; final StackTraceElement[][] there = new StackTraceElement[][]{null}; final ShadowLooper looper = Robolectric.shadowOf(Looper.getMainLooper()); Ln.setLoggingLevel(Log.DEBUG); try { throw new UnsupportedOperationException(); } catch (UnsupportedOperationException e) { here = e.getStackTrace(); } Executors.newSingleThreadExecutor(new MyThreadFactory(new Thread[]{null})).submit(new AndroidCallable<String>() { @Override public String doInBackground() throws Exception { try { throw new NullPointerException(); } catch (NullPointerException e) { there[0] = e.getStackTrace(); throw e; } } @Override public void onException(Exception e) { exception[0] = e; } @Override public void onSuccess(String result) { } },false); // Run all the pending tasks on the ui thread while(exception[0]==null) looper.runToEndOfTasks(); assertThat(exception[0].getStackTrace().length, equalTo(here.length + there[0].length + 3)); int i=0; while (i<there[0].length) { assertThat( exception[0].getStackTrace()[i], equalTo(there[0][i])); ++i; } for( int j=i+3, k=0; k<here.length; ++k, ++j) // skip 3 frames due to differences in where we got our stacktrace from assertThat( exception[0].getStackTrace()[j].getFileName(), equalTo(here[k].getFileName())); // line numbers may be off } @Test(expected = RuntimeException.class ) public void shouldCrashAppForExceptionInOnSuccess() { final ShadowLooper looper = Robolectric.shadowOf(Looper.getMainLooper()); final boolean[] callRecord = new boolean[]{false}; Executors.newSingleThreadExecutor().submit( new AndroidCallable<String>() { @Override public String doInBackground() throws Exception { return ""; } @Override public void onSuccess(String result) { throw new RuntimeException(); } @Override public void onException(Exception e) { } @Override public void onFinally() { callRecord[0] = true; } }); // Run all the pending tasks on the ui thread while(!callRecord[callRecord.length-1]) looper.runToEndOfTasks(); } @Test(expected = RuntimeException.class ) public void shouldCrashAppForExceptionInOnException() { final ShadowLooper looper = Robolectric.shadowOf(Looper.getMainLooper()); final boolean[] callRecord = new boolean[]{false}; Executors.newSingleThreadExecutor().submit( new AndroidCallable<String>() { @Override public String doInBackground() throws Exception { throw new Exception(); } @Override public void onSuccess(String result) { } @Override public void onException(Exception e) { throw new RuntimeException(); } @Override public void onFinally() { callRecord[0] = true; } }); // Run all the pending tasks on the ui thread while(!callRecord[callRecord.length-1]) looper.runToEndOfTasks(); } @Test(expected = RuntimeException.class ) public void shouldCrashAppForExceptionInOnFinally() { final ShadowLooper looper = Robolectric.shadowOf(Looper.getMainLooper()); Executors.newSingleThreadExecutor().submit( new AndroidCallable<String>() { @Override public String doInBackground() throws Exception { return ""; } @Override public void onSuccess(String result) { throw new RuntimeException(); } @Override public void onException(Exception e) { } @Override public void onFinally() { throw new RuntimeException(); } }); // Run all the pending tasks on the ui thread //noinspection InfiniteLoopStatement while(true) looper.runToEndOfTasks(); } @Test public void shouldCallOnExceptionForExceptionInDoInBackground() { final ShadowLooper looper = Robolectric.shadowOf(Looper.getMainLooper()); final boolean[] callRecord = new boolean[]{false, false, false, false}; final boolean[] correctAnswer = new boolean[] { true, false, true, true }; Executors.newSingleThreadExecutor().submit( new AndroidCallable<String>() { @Override public void onPreCall() { callRecord[0] = true; } @Override public String doInBackground() throws Exception { throw new Exception(); } @Override public void onSuccess(String result) { callRecord[1] = true; } @Override public void onException(Exception e) { callRecord[2] = true; } @Override public void onFinally() { callRecord[3] = true; } }); // Run all the pending tasks on the ui thread while(!callRecord[callRecord.length-1]) looper.runToEndOfTasks(); assertThat( callRecord, equalTo(correctAnswer)); } @Test public void shouldCallOnExceptionForExceptionInOnPreExecute() { final ShadowLooper looper = Robolectric.shadowOf(Looper.getMainLooper()); final boolean[] callRecord = new boolean[]{false, false, false}; final boolean[] correctAnswer = new boolean[] { false, true, true }; Executors.newSingleThreadExecutor().submit( new AndroidCallable<String>() { @Override public void onPreCall() { throw new RuntimeException(); } @Override public String doInBackground() throws Exception { return ""; } @Override public void onSuccess(String result) { callRecord[0] = true; } @Override public void onException(Exception e) { callRecord[1] = true; } @Override public void onFinally() { callRecord[2] = true; } }); // Run all the pending tasks on the ui thread while(!callRecord[callRecord.length-1]) looper.runToEndOfTasks(); assertThat( callRecord, equalTo(correctAnswer)); } private static class StringAndroidCallable extends AndroidCallable<String> { Thread[] answers; boolean shouldThrowException; public StringAndroidCallable(Thread[] answers, boolean shouldThrowException ) { this.answers = answers; this.shouldThrowException = shouldThrowException; } @Override public void onPreCall() { answers[0] = Thread.currentThread(); } @Override public String doInBackground() throws Exception { answers[1] = Thread.currentThread(); if( shouldThrowException ) throw new UnsupportedOperationException(); return "12345"; } @Override public void onException(Exception e) { answers[2] = Thread.currentThread(); } @Override public void onSuccess(String result) { answers[3] = Thread.currentThread(); } @Override public void onFinally() { answers[4] = Thread.currentThread(); } } private static class MyThreadFactory implements ThreadFactory { private final Thread[] bgThread; public MyThreadFactory(Thread[] bgThread) { this.bgThread = bgThread; } @Override public Thread newThread(Runnable runnable) { bgThread[0] = new Thread(runnable); bgThread[0].setName("bgThread"); return bgThread[0]; } } }