Update camera Robolectric tests for @LooperMode(PAUSED)
Robolectric 4.4 now uses @LooperMode(PAUSED) by default, this requires
a few changes to the way robolectric tests are driven.
For more information, see:
http://robolectric.org/blog/2019/06/04/paused-looper/
Bug: 162018163
Test: ./gradlew test
Change-Id: I67d336b5c50cfb2bf14dbac90e3923df01e21304
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/TorchControlTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/TorchControlTest.java
index 5d2a7be..b60734c 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/TorchControlTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/TorchControlTest.java
@@ -16,6 +16,8 @@
package androidx.camera.camera2.internal;
+import static android.os.Looper.getMainLooper;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
@@ -23,6 +25,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.internal.verification.VerificationModeFactory.times;
+import static org.robolectric.Shadows.shadowOf;
import android.content.Context;
import android.hardware.camera2.CameraAccessException;
@@ -123,6 +126,7 @@
public void enableTorch_whenNoFlashUnit() throws InterruptedException {
Throwable cause = null;
try {
+ // Without a flash unit, this future will complete immediately. No need to idle.
mNoFlashUnitTorchControl.enableTorch(true).get();
} catch (ExecutionException e) {
// The real cause is wrapped in ExecutionException, retrieve it and check.
@@ -133,16 +137,22 @@
@Test
public void getTorchState_whenNoFlashUnit() {
- int torchState = mNoFlashUnitTorchControl.getTorchState().getValue();
+ int torchState =
+ Objects.requireNonNull(mNoFlashUnitTorchControl.getTorchState().getValue());
assertThat(torchState).isEqualTo(TorchState.OFF);
}
@Test
public void enableTorch_whenInactive() throws InterruptedException {
mTorchControl.setActive(false);
+ ListenableFuture<Void> listenableFuture = mTorchControl.enableTorch(true);
+ // enableTorch can be called from any thread and posts to executor, so idle our executor.
+ shadowOf(getMainLooper()).idle();
+
+ assertThat(listenableFuture.isDone()).isTrue();
Throwable cause = null;
try {
- mTorchControl.enableTorch(true).get();
+ listenableFuture.get();
} catch (ExecutionException e) {
// The real cause is wrapped in ExecutionException, retrieve it and check.
cause = e.getCause();
@@ -154,7 +164,8 @@
@Test
public void getTorchState_whenInactive() {
mTorchControl.setActive(false);
- int torchState = mTorchControl.getTorchState().getValue();
+ // LiveData is updated synchronously. No need to idle.
+ int torchState = Objects.requireNonNull(mTorchControl.getTorchState().getValue());
assertThat(torchState).isEqualTo(TorchState.OFF);
}
@@ -162,7 +173,8 @@
@Test
public void enableTorch_torchStateOn() {
mTorchControl.enableTorch(true);
- int torchState = mTorchControl.getTorchState().getValue();
+ // LiveData is updated synchronously. No need to idle.
+ int torchState = Objects.requireNonNull(mTorchControl.getTorchState().getValue());
assertThat(torchState).isEqualTo(TorchState.ON);
}
@@ -170,26 +182,34 @@
@Test
public void disableTorch_TorchStateOff() {
mTorchControl.enableTorch(true);
- int torchState = mTorchControl.getTorchState().getValue();
-
- assertThat(torchState).isEqualTo(TorchState.ON);
+ // LiveData is updated synchronously. No need to idle.
+ int firstTorchState = Objects.requireNonNull(mTorchControl.getTorchState().getValue());
mTorchControl.enableTorch(false);
- torchState = mTorchControl.getTorchState().getValue();
+ // LiveData is updated synchronously. No need to idle.
+ int secondTorchState = mTorchControl.getTorchState().getValue();
- assertThat(torchState).isEqualTo(TorchState.OFF);
+ assertThat(firstTorchState).isEqualTo(TorchState.ON);
+ assertThat(secondTorchState).isEqualTo(TorchState.OFF);
}
- @Test(timeout = 5000L)
+ @Test
public void enableDisableTorch_futureWillCompleteSuccessfully()
throws ExecutionException, InterruptedException {
ListenableFuture<Void> future = mTorchControl.enableTorch(true);
+ // enableTorch can be called from any thread and posts to executor, so idle our executor.
+ shadowOf(getMainLooper()).idle();
+
+ // Calling onCaptureResult directly from executor thread (main thread). No need to idle.
mCaptureResultListener.onCaptureResult(
mockFlashCaptureResult(CaptureResult.FLASH_MODE_TORCH));
// Future should return with no exception
future.get();
future = mTorchControl.enableTorch(false);
+ // enableTorch can be called from any thread and posts to executor, so idle our executor.
+ shadowOf(getMainLooper()).idle();
+ // Calling onCaptureResult directly from executor thread (main thread). No need to idle.
mCaptureResultListener.onCaptureResult(
mockFlashCaptureResult(CaptureResult.FLASH_MODE_OFF));
// Future should return with no exception
@@ -200,6 +220,8 @@
public void enableTorchTwice_cancelPreviousFuture() throws InterruptedException {
ListenableFuture<Void> future = mTorchControl.enableTorch(true);
mTorchControl.enableTorch(true);
+ // enableTorch can be called from any thread and posts to executor, so idle our executor.
+ shadowOf(getMainLooper()).idle();
Throwable cause = null;
try {
future.get();
@@ -214,6 +236,9 @@
@Test
public void setInActive_cancelPreviousFuture() throws InterruptedException {
ListenableFuture<Void> future = mTorchControl.enableTorch(true);
+ // enableTorch can be called from any thread and posts to executor, so idle our executor.
+ shadowOf(getMainLooper()).idle();
+ // setActive() is called from executor thread (main thread in this case). No need to idle.
mTorchControl.setActive(false);
Throwable cause = null;
try {
@@ -229,20 +254,24 @@
@Test
public void setInActiveWhenTorchOn_changeToTorchOff() {
mTorchControl.enableTorch(true);
- int torchState = mTorchControl.getTorchState().getValue();
+ // enableTorch can be called from any thread and posts to executor, so idle our executor.
+ shadowOf(getMainLooper()).idle();
+ int initialTorchState = Objects.requireNonNull(mTorchControl.getTorchState().getValue());
- assertThat(torchState).isEqualTo(TorchState.ON);
-
+ // setActive() is called from executor thread (main thread in this case). No need to idle.
mTorchControl.setActive(false);
- torchState = mTorchControl.getTorchState().getValue();
+ int torchStateAfterInactive = mTorchControl.getTorchState().getValue();
- assertThat(torchState).isEqualTo(TorchState.OFF);
+ assertThat(initialTorchState).isEqualTo(TorchState.ON);
+ assertThat(torchStateAfterInactive).isEqualTo(TorchState.OFF);
}
@Test
public void enableDisableTorch_observeTorchStateLiveData() {
+ @SuppressWarnings("unchecked")
Observer<Integer> observer = mock(Observer.class);
LiveData<Integer> torchStateLiveData = mTorchControl.getTorchState();
+ // Adding observer from main thread should synchronously be notified of initial state
torchStateLiveData.observe(mLifecycleOwner, new Observer<Integer>() {
private Integer mValue;
@Override
@@ -254,7 +283,12 @@
});
mTorchControl.enableTorch(true);
+ // Idle the main thread to receive first update
+ shadowOf(getMainLooper()).idle();
+
mTorchControl.enableTorch(false);
+ // Idle the main thread to receive second update
+ shadowOf(getMainLooper()).idle();
ArgumentCaptor<Integer> torchStateCaptor = ArgumentCaptor.forClass(Integer.class);
verify(observer, times(3)).onChanged(torchStateCaptor.capture());
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/ZoomControlTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/ZoomControlTest.java
index 94ae05c..f6cce84 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/ZoomControlTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/ZoomControlTest.java
@@ -16,14 +16,15 @@
package androidx.camera.camera2.internal;
+import static android.os.Looper.getMainLooper;
+
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
import android.content.Context;
import android.graphics.Rect;
@@ -35,12 +36,9 @@
import android.os.Build;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.camera.core.CameraControl;
import androidx.camera.core.impl.CameraControlInternal;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
-import androidx.camera.core.impl.utils.futures.FutureCallback;
-import androidx.camera.core.impl.utils.futures.Futures;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
@@ -57,9 +55,8 @@
import org.robolectric.shadows.ShadowCameraCharacteristics;
import org.robolectric.shadows.ShadowCameraManager;
-import java.util.concurrent.CountDownLatch;
+import java.util.Objects;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
@SmallTest
@RunWith(RobolectricTestRunner.class)
@@ -76,6 +73,10 @@
private ZoomControl mZoomControl;
private Camera2CameraControl.CaptureResultListener mCaptureResultListener;
+ private static Rect getCropRectByRatio(float ratio) {
+ return ZoomControl.getCropRectByRatio(SENSOR_RECT, ratio);
+ }
+
@Before
public void setUp() throws CameraAccessException {
initShadowCameraManager();
@@ -133,32 +134,20 @@
.addCamera(CAMERA1_ID, characteristics1);
}
- private static Rect getCropRectByRatio(float ratio) {
- return ZoomControl.getCropRectByRatio(SENSOR_RECT, ratio);
- }
-
@Test
public void setZoomRatio1_whenResultCropRegionIsAlive_ListenableFutureSucceeded()
- throws InterruptedException {
- CountDownLatch latch = new CountDownLatch(1);
+ throws InterruptedException, ExecutionException {
ListenableFuture<Void> listenableFuture = mZoomControl.setZoomRatio(1.0f);
+ // setZoomRatio can be called from any thread and posts to executor, so idle our executor.
+ shadowOf(getMainLooper()).idle();
TotalCaptureResult result = mockCaptureResult(getCropRectByRatio(1.0f));
+ // Calling onCaptureResult directly from executor thread (main thread). No need to idle.
mCaptureResultListener.onCaptureResult(result);
- Futures.addCallback(listenableFuture, new FutureCallback<Void>() {
- @Override
- public void onSuccess(@Nullable Void result) {
- latch.countDown();
- }
-
- @Override
- public void onFailure(Throwable t) {
-
- }
- }, CameraXExecutors.mainThreadExecutor());
-
- assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
+ assertThat(listenableFuture.isDone()).isTrue();
+ // Future should have succeeded. Should not throw.
+ listenableFuture.get();
}
@NonNull
@@ -172,295 +161,227 @@
@Test
public void setZoomRatioOtherThan1_whenResultCropRegionIsAlive_ListenableFutureSucceeded()
- throws InterruptedException {
- CountDownLatch latch = new CountDownLatch(1);
+ throws InterruptedException, ExecutionException {
ListenableFuture<Void> listenableFuture = mZoomControl.setZoomRatio(2.0f);
+ // setZoomRatio can be called from any thread and posts to executor, so idle our executor.
+ shadowOf(getMainLooper()).idle();
TotalCaptureResult result2 = mockCaptureResult(getCropRectByRatio(2.0f));
+ // Calling onCaptureResult directly from executor thread (main thread). No need to idle.
mCaptureResultListener.onCaptureResult(result2);
- Futures.addCallback(listenableFuture, new FutureCallback<Void>() {
- @Override
- public void onSuccess(@Nullable Void result) {
- latch.countDown();
- }
-
- @Override
- public void onFailure(Throwable t) {
-
- }
- }, CameraXExecutors.mainThreadExecutor());
-
- assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
+ assertThat(listenableFuture.isDone()).isTrue();
+ // Future should have succeeded. Should not throw.
+ listenableFuture.get();
}
@Test
public void setLinearZoom_valueIsAlive_ListenableFutureSucceeded()
- throws InterruptedException {
- CountDownLatch latch = new CountDownLatch(1);
+ throws ExecutionException, InterruptedException {
ListenableFuture<Void> listenableFuture = mZoomControl.setLinearZoom(0.1f);
+ // setLinearZoom can be called from any thread and posts to executor, so idle our executor.
+ shadowOf(getMainLooper()).idle();
- float targetRatio = mZoomControl.getZoomState().getValue().getZoomRatio();
+ float targetRatio = Objects.requireNonNull(
+ mZoomControl.getZoomState().getValue()).getZoomRatio();
TotalCaptureResult result = mockCaptureResult(getCropRectByRatio(targetRatio));
+ // Calling onCaptureResult directly from executor thread (main thread). No need to idle.
mCaptureResultListener.onCaptureResult(result);
- Futures.addCallback(listenableFuture, new FutureCallback<Void>() {
- @Override
- public void onSuccess(@Nullable Void result) {
- latch.countDown();
- }
-
- @Override
- public void onFailure(Throwable t) {
-
- }
- }, CameraXExecutors.mainThreadExecutor());
-
- assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
+ assertThat(listenableFuture.isDone()).isTrue();
+ // Future should have succeeded. Should not throw.
+ listenableFuture.get();
}
@Test
- public void setZoomRatio_newRatioIsSet_operationCanceled() throws InterruptedException {
- CountDownLatch latchForOp1Canceled = new CountDownLatch(1);
- CountDownLatch latchForOp2Canceled = new CountDownLatch(1);
- CountDownLatch latchForOp3Succeeded = new CountDownLatch(1);
+ public void setZoomRatio_newRatioIsSet_operationCanceled()
+ throws InterruptedException, ExecutionException {
ListenableFuture<Void> listenableFuture = mZoomControl.setZoomRatio(2.0f);
ListenableFuture<Void> listenableFuture2 = mZoomControl.setZoomRatio(3.0f);
ListenableFuture<Void> listenableFuture3 = mZoomControl.setZoomRatio(4.0f);
+ // setZoomRatio can be called from any thread and posts to executor, so idle our executor.
+ // Since multiple calls to setZoomRatio are posted in order, we only need to idle once to
+ // run all of them.
+ shadowOf(getMainLooper()).idle();
TotalCaptureResult result = mockCaptureResult(getCropRectByRatio(4.0f));
+ // Calling onCaptureResult directly from executor thread (main thread). No need to idle.
mCaptureResultListener.onCaptureResult(result);
- Futures.addCallback(listenableFuture, new FutureCallback<Void>() {
- @Override
- public void onSuccess(@Nullable Void result) {
- }
+ assertThat(listenableFuture.isDone()).isTrue();
+ assertThat(listenableFuture2.isDone()).isTrue();
+ assertThat(listenableFuture3.isDone()).isTrue();
+ // Futures 1 and 2 should have failed.
+ Throwable t = null;
+ try {
+ listenableFuture.get();
+ } catch (ExecutionException e) {
+ t = e.getCause();
+ }
+ assertThat(t).isInstanceOf(CameraControl.OperationCanceledException.class);
- @Override
- public void onFailure(Throwable t) {
- if (t instanceof CameraControl.OperationCanceledException) {
- latchForOp1Canceled.countDown();
- }
- }
- }, CameraXExecutors.mainThreadExecutor());
+ t = null;
+ try {
+ listenableFuture2.get();
+ } catch (ExecutionException e) {
+ t = e.getCause();
+ }
+ assertThat(t).isInstanceOf(CameraControl.OperationCanceledException.class);
- Futures.addCallback(listenableFuture2, new FutureCallback<Void>() {
- @Override
- public void onSuccess(@Nullable Void result) {
- }
-
- @Override
- public void onFailure(Throwable t) {
- if (t instanceof CameraControl.OperationCanceledException) {
- latchForOp2Canceled.countDown();
- }
- }
- }, CameraXExecutors.mainThreadExecutor());
-
- Futures.addCallback(listenableFuture3, new FutureCallback<Void>() {
- @Override
- public void onSuccess(@Nullable Void result) {
- latchForOp3Succeeded.countDown();
- }
-
- @Override
- public void onFailure(Throwable t) {
- }
- }, CameraXExecutors.mainThreadExecutor());
-
- assertTrue(latchForOp1Canceled.await(500, TimeUnit.MILLISECONDS));
- assertTrue(latchForOp2Canceled.await(500, TimeUnit.MILLISECONDS));
- assertTrue(latchForOp3Succeeded.await(500, TimeUnit.MILLISECONDS));
+ // Future 3 should have succeeded. Should not throw.
+ listenableFuture3.get();
}
@Test
public void setLinearZoom_newPercentageIsSet_operationCanceled()
- throws InterruptedException {
- CountDownLatch latchForOp1Canceled = new CountDownLatch(1);
- CountDownLatch latchForOp2Canceled = new CountDownLatch(1);
- CountDownLatch latchForOp3Succeeded = new CountDownLatch(1);
+ throws InterruptedException, ExecutionException {
ListenableFuture<Void> listenableFuture = mZoomControl.setLinearZoom(0.1f);
ListenableFuture<Void> listenableFuture2 = mZoomControl.setLinearZoom(0.2f);
ListenableFuture<Void> listenableFuture3 = mZoomControl.setLinearZoom(0.3f);
- float ratioForPercentage = mZoomControl.getZoomState().getValue().getZoomRatio();
+ // setZoomRatio can be called from any thread and posts to executor, so idle our executor.
+ // Since multiple calls to setZoomRatio are posted in order, we only need to idle once to
+ // run all of them.
+ shadowOf(getMainLooper()).idle();
+ float ratioForPercentage = Objects.requireNonNull(
+ mZoomControl.getZoomState().getValue()).getZoomRatio();
TotalCaptureResult result = mockCaptureResult(getCropRectByRatio(ratioForPercentage));
+ // Calling onCaptureResult directly from executor thread (main thread). No need to idle.
mCaptureResultListener.onCaptureResult(result);
- Futures.addCallback(listenableFuture, new FutureCallback<Void>() {
- @Override
- public void onSuccess(@Nullable Void result) {
- }
+ assertThat(listenableFuture.isDone()).isTrue();
+ assertThat(listenableFuture2.isDone()).isTrue();
+ assertThat(listenableFuture3.isDone()).isTrue();
+ // Futures 1 and 2 should have failed.
+ Throwable t = null;
+ try {
+ listenableFuture.get();
+ } catch (ExecutionException e) {
+ t = e.getCause();
+ }
+ assertThat(t).isInstanceOf(CameraControl.OperationCanceledException.class);
- @Override
- public void onFailure(Throwable t) {
- if (t instanceof CameraControl.OperationCanceledException) {
- latchForOp1Canceled.countDown();
- }
- }
- }, CameraXExecutors.mainThreadExecutor());
+ t = null;
+ try {
+ listenableFuture2.get();
+ } catch (ExecutionException e) {
+ t = e.getCause();
+ }
+ assertThat(t).isInstanceOf(CameraControl.OperationCanceledException.class);
- Futures.addCallback(listenableFuture2, new FutureCallback<Void>() {
- @Override
- public void onSuccess(@Nullable Void result) {
- }
-
- @Override
- public void onFailure(Throwable t) {
- if (t instanceof CameraControl.OperationCanceledException) {
- latchForOp2Canceled.countDown();
- }
- }
- }, CameraXExecutors.mainThreadExecutor());
-
- Futures.addCallback(listenableFuture3, new FutureCallback<Void>() {
- @Override
- public void onSuccess(@Nullable Void result) {
- latchForOp3Succeeded.countDown();
- }
-
- @Override
- public void onFailure(Throwable t) {
- }
- }, CameraXExecutors.mainThreadExecutor());
-
- assertTrue(latchForOp1Canceled.await(500, TimeUnit.MILLISECONDS));
- assertTrue(latchForOp2Canceled.await(500, TimeUnit.MILLISECONDS));
- assertTrue(latchForOp3Succeeded.await(500, TimeUnit.MILLISECONDS));
+ // Future 3 should have succeeded. Should not throw.
+ listenableFuture3.get();
}
@Test
- public void setZoomRatioAndPercentage_mixedOperation() throws InterruptedException {
- CountDownLatch latchForOp1Canceled = new CountDownLatch(1);
- CountDownLatch latchForOp2Canceled = new CountDownLatch(1);
- CountDownLatch latchForOp3Succeeded = new CountDownLatch(1);
+ public void setZoomRatioAndPercentage_mixedOperation()
+ throws InterruptedException, ExecutionException {
ListenableFuture<Void> listenableFuture = mZoomControl.setZoomRatio(2f);
ListenableFuture<Void> listenableFuture2 = mZoomControl.setLinearZoom(0.1f);
ListenableFuture<Void> listenableFuture3 = mZoomControl.setZoomRatio(4f);
+ // setZoomRatio/setLinearZoom can be called from any thread and posts to executor, so idle
+ // our executor. Since multiple calls to setZoomRatio/setLinearZoom are posted in order,
+ // we only need to idle once to run all of them.
+ shadowOf(getMainLooper()).idle();
TotalCaptureResult result = mockCaptureResult(getCropRectByRatio(4.0f));
+ // Calling onCaptureResult directly from executor thread (main thread). No need to idle.
mCaptureResultListener.onCaptureResult(result);
- Futures.addCallback(listenableFuture, new FutureCallback<Void>() {
- @Override
- public void onSuccess(@Nullable Void result) {
- }
+ assertThat(listenableFuture.isDone()).isTrue();
+ assertThat(listenableFuture2.isDone()).isTrue();
+ assertThat(listenableFuture3.isDone()).isTrue();
+ // Futures 1 and 2 should have failed.
+ Throwable t = null;
+ try {
+ listenableFuture.get();
+ } catch (ExecutionException e) {
+ t = e.getCause();
+ }
+ assertThat(t).isInstanceOf(CameraControl.OperationCanceledException.class);
- @Override
- public void onFailure(Throwable t) {
- if (t instanceof CameraControl.OperationCanceledException) {
- latchForOp1Canceled.countDown();
- }
- }
- }, CameraXExecutors.mainThreadExecutor());
+ t = null;
+ try {
+ listenableFuture2.get();
+ } catch (ExecutionException e) {
+ t = e.getCause();
+ }
+ assertThat(t).isInstanceOf(CameraControl.OperationCanceledException.class);
- Futures.addCallback(listenableFuture2, new FutureCallback<Void>() {
- @Override
- public void onSuccess(@Nullable Void result) {
- }
-
- @Override
- public void onFailure(Throwable t) {
- if (t instanceof CameraControl.OperationCanceledException) {
- latchForOp2Canceled.countDown();
- }
- }
- }, CameraXExecutors.mainThreadExecutor());
-
- Futures.addCallback(listenableFuture3, new FutureCallback<Void>() {
- @Override
- public void onSuccess(@Nullable Void result) {
- latchForOp3Succeeded.countDown();
- }
-
- @Override
- public void onFailure(Throwable t) {
- }
- }, CameraXExecutors.mainThreadExecutor());
-
- assertTrue(latchForOp1Canceled.await(500, TimeUnit.MILLISECONDS));
- assertTrue(latchForOp2Canceled.await(500, TimeUnit.MILLISECONDS));
- assertTrue(latchForOp3Succeeded.await(500, TimeUnit.MILLISECONDS));
+ // Future 3 should have succeeded. Should not throw.
+ listenableFuture3.get();
}
@Test
- public void setZoomRatio_whenInActive_operationCanceled() {
+ public void setZoomRatio_whenInActive_operationCanceled() throws InterruptedException {
+ // setActive() is called from executor thread (main thread in this case). No need to idle.
mZoomControl.setActive(false);
ListenableFuture<Void> listenableFuture = mZoomControl.setZoomRatio(1.0f);
+ // setZoomRatio can be called from any thread and posts to executor, so idle our executor.
+ shadowOf(getMainLooper()).idle();
+ assertThat(listenableFuture.isDone()).isTrue();
+ Throwable t = null;
try {
- listenableFuture.get(1000, TimeUnit.MILLISECONDS);
+ listenableFuture.get();
} catch (ExecutionException e) {
- if (e.getCause() instanceof CameraControl.OperationCanceledException) {
- assertTrue(true);
- return;
- }
- } catch (Exception e) {
+ t = e.getCause();
}
-
- fail();
+ assertThat(t).isInstanceOf(CameraControl.OperationCanceledException.class);
}
@Test
- public void setLinearZoom_whenInActive_operationCanceled() {
+ public void setLinearZoom_whenInActive_operationCanceled() throws InterruptedException {
+ // setActive() is called from executor thread (main thread in this case). No need to idle.
mZoomControl.setActive(false);
ListenableFuture<Void> listenableFuture = mZoomControl.setLinearZoom(0f);
+ // setZoomRatio can be called from any thread and posts to executor, so idle our executor.
+ shadowOf(getMainLooper()).idle();
+ assertThat(listenableFuture.isDone()).isTrue();
+ Throwable t = null;
try {
- listenableFuture.get(1000, TimeUnit.MILLISECONDS);
+ listenableFuture.get();
} catch (ExecutionException e) {
- if (e.getCause() instanceof CameraControl.OperationCanceledException) {
- assertTrue(true);
- return;
- }
- } catch (Exception e) {
+ t = e.getCause();
}
-
- fail();
+ assertThat(t).isInstanceOf(CameraControl.OperationCanceledException.class);
}
@Test
public void setZoomRatio_afterInActive_operationCanceled() throws InterruptedException {
- CountDownLatch latch = new CountDownLatch(1);
ListenableFuture<Void> listenableFuture = mZoomControl.setZoomRatio(2.0f);
- Futures.addCallback(listenableFuture, new FutureCallback<Void>() {
- @Override
- public void onSuccess(@Nullable Void result) {
- }
-
- @Override
- public void onFailure(Throwable t) {
- if (t instanceof CameraControl.OperationCanceledException) {
- latch.countDown();
- }
- }
- }, CameraXExecutors.mainThreadExecutor());
-
+ // setZoomRatio can be called from any thread and posts to executor, so idle our executor.
+ shadowOf(getMainLooper()).idle();
+ // setActive() is called from executor thread (main thread in this case). No need to idle.
mZoomControl.setActive(false);
- assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
+ assertThat(listenableFuture.isDone()).isTrue();
+ Throwable t = null;
+ try {
+ listenableFuture.get();
+ } catch (ExecutionException e) {
+ t = e.getCause();
+ }
+ assertThat(t).isInstanceOf(CameraControl.OperationCanceledException.class);
}
@Test
public void setLinearZoom_afterInActive_operationCanceled() throws InterruptedException {
- CountDownLatch latch = new CountDownLatch(1);
ListenableFuture<Void> listenableFuture = mZoomControl.setLinearZoom(0.3f);
- Futures.addCallback(listenableFuture, new FutureCallback<Void>() {
- @Override
- public void onSuccess(@Nullable Void result) {
- }
-
- @Override
- public void onFailure(Throwable t) {
- if (t instanceof CameraControl.OperationCanceledException) {
- latch.countDown();
- }
- }
- }, CameraXExecutors.mainThreadExecutor());
-
+ // setZoomRatio can be called from any thread and posts to executor, so idle our executor.
+ shadowOf(getMainLooper()).idle();
+ // setActive() is called from executor thread (main thread in this case). No need to idle.
mZoomControl.setActive(false);
- assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
+ assertThat(listenableFuture.isDone()).isTrue();
+ Throwable t = null;
+ try {
+ listenableFuture.get();
+ } catch (ExecutionException e) {
+ t = e.getCause();
+ }
+ assertThat(t).isInstanceOf(CameraControl.OperationCanceledException.class);
}
@Test
@@ -478,10 +399,13 @@
mock(CameraControlInternal.ControlUpdateCallback.class));
ZoomControl zoomControl = new ZoomControl(mCamera2CameraControl, cameraCharacteristics,
CameraXExecutors.mainThreadExecutor());
+ // setActive() is called from executor thread (main thread in this case). No need to idle.
zoomControl.setActive(true);
+ // LiveData is updated synchronously. No need to idle.
zoomControl.setZoomRatio(3.0f);
- assertThat(zoomControl.getZoomState().getValue().getZoomRatio()).isEqualTo(1.0f);
+ assertThat(Objects.requireNonNull(
+ zoomControl.getZoomState().getValue()).getZoomRatio()).isEqualTo(1.0f);
assertThat(zoomControl.getZoomState().getValue().getLinearZoom()).isEqualTo(0.0f);
}
@@ -500,10 +424,13 @@
mock(CameraControlInternal.ControlUpdateCallback.class));
ZoomControl zoomControl = new ZoomControl(mCamera2CameraControl, cameraCharacteristics,
CameraXExecutors.mainThreadExecutor());
+ // setActive() is called from executor thread (main thread in this case). No need to idle.
zoomControl.setActive(true);
+ // LiveData is updated synchronously. No need to idle.
zoomControl.setZoomRatio(0.2f);
- assertThat(zoomControl.getZoomState().getValue().getZoomRatio()).isEqualTo(1.0f);
+ assertThat(Objects.requireNonNull(
+ zoomControl.getZoomState().getValue()).getZoomRatio()).isEqualTo(1.0f);
assertThat(zoomControl.getZoomState().getValue().getLinearZoom()).isEqualTo(0.0f);
}
@@ -523,10 +450,13 @@
mock(CameraControlInternal.ControlUpdateCallback.class));
ZoomControl zoomControl = new ZoomControl(mCamera2CameraControl, cameraCharacteristics,
CameraXExecutors.mainThreadExecutor());
+ // setActive() is called from executor thread (main thread in this case). No need to idle.
zoomControl.setActive(true);
+ // LiveData is updated synchronously. No need to idle.
zoomControl.setLinearZoom(0.4f);
- assertThat(zoomControl.getZoomState().getValue().getZoomRatio()).isEqualTo(1.0f);
+ assertThat(Objects.requireNonNull(
+ zoomControl.getZoomState().getValue()).getZoomRatio()).isEqualTo(1.0f);
// percentage is updated correctly but the zoomRatio is always 1.0f if zoom not supported.
assertThat(zoomControl.getZoomState().getValue().getLinearZoom()).isEqualTo(0.4f);
}
@@ -546,11 +476,14 @@
mock(CameraControlInternal.ControlUpdateCallback.class));
ZoomControl zoomControl = new ZoomControl(mCamera2CameraControl, cameraCharacteristics,
CameraXExecutors.mainThreadExecutor());
+ // setActive() is called from executor thread (main thread in this case). No need to idle.
zoomControl.setActive(true);
+ // LiveData is updated synchronously. No need to idle.
zoomControl.setLinearZoom(0.3f);
zoomControl.setLinearZoom(-0.2f);
- assertThat(zoomControl.getZoomState().getValue().getZoomRatio()).isEqualTo(1.0f);
+ assertThat(Objects.requireNonNull(
+ zoomControl.getZoomState().getValue()).getZoomRatio()).isEqualTo(1.0f);
// percentage not changed but the zoomRatio is always 1.0f if zoom not supported.
assertThat(zoomControl.getZoomState().getValue().getLinearZoom()).isEqualTo(0.3f);
}
@@ -570,11 +503,14 @@
mock(CameraControlInternal.ControlUpdateCallback.class));
ZoomControl zoomControl = new ZoomControl(mCamera2CameraControl, cameraCharacteristics,
CameraXExecutors.mainThreadExecutor());
+ // setActive() is called from executor thread (main thread in this case). No need to idle.
zoomControl.setActive(true);
+ // LiveData is updated synchronously. No need to idle.
zoomControl.setLinearZoom(0.3f);
zoomControl.setLinearZoom(1.2f);
- assertThat(zoomControl.getZoomState().getValue().getZoomRatio()).isEqualTo(1.0f);
+ assertThat(Objects.requireNonNull(
+ zoomControl.getZoomState().getValue()).getZoomRatio()).isEqualTo(1.0f);
// percentage not changed but the zoomRatio is always 1.0f if zoom not supported.
assertThat(zoomControl.getZoomState().getValue().getLinearZoom()).isEqualTo(0.3f);
}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisNonBlockingAnalyzerTest.java b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisNonBlockingAnalyzerTest.java
index 42ba496..ecd8e5e 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisNonBlockingAnalyzerTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisNonBlockingAnalyzerTest.java
@@ -16,6 +16,8 @@
package androidx.camera.core;
+import static android.os.Looper.getMainLooper;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -26,6 +28,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
import android.os.Build;
@@ -112,6 +115,8 @@
mImageAnalysisNonBlockingAnalyzer.onImageAvailable(mImageReaderProxy);
+ shadowOf(getMainLooper()).idle();
+
ArgumentCaptor<ImageProxy> imageProxyArgumentCaptor =
ArgumentCaptor.forClass(ImageProxy.class);
verify(mAnalyzer, times(1)).analyze(
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
index 1ca30c1..90fc0b2 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
@@ -18,6 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.robolectric.Shadows.shadowOf;
+
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
@@ -48,8 +50,6 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.internal.DoNotInstrument;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.ShadowLooper;
import java.util.ArrayList;
import java.util.Collections;
@@ -88,10 +88,14 @@
public void setUp() throws ExecutionException, InterruptedException {
mCallbackThread = new HandlerThread("Callback");
mCallbackThread.start();
+ // Explicitly pause callback thread since we will control execution manually in tests
+ shadowOf(mCallbackThread.getLooper()).pause();
mCallbackHandler = new Handler(mCallbackThread.getLooper());
mBackgroundThread = new HandlerThread("Background");
mBackgroundThread.start();
+ // Explicitly pause background thread since we will control execution manually in tests
+ shadowOf(mBackgroundThread.getLooper()).pause();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
mBackgroundExecutor = CameraXExecutors.newHandlerExecutor(mBackgroundHandler);
@@ -327,6 +331,6 @@
* @param handler the {@link Handler} to flush.
*/
private static void flushHandler(Handler handler) {
- ((ShadowLooper) Shadow.extract(handler.getLooper())).idle();
+ shadowOf(handler.getLooper()).idle();
}
}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
index 960f00f..f559c0c1 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
@@ -22,6 +22,7 @@
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
+import android.os.Looper.getMainLooper
import android.util.Pair
import android.util.Rational
import android.view.Surface
@@ -46,7 +47,6 @@
import androidx.concurrent.futures.ResolvableFuture
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.MediumTest
-import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
@@ -60,6 +60,7 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.robolectric.RobolectricTestRunner
+import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
import org.robolectric.annotation.internal.DoNotInstrument
import org.robolectric.shadow.api.Shadow
@@ -115,6 +116,8 @@
CameraX.initialize(context, cameraXConfig).get()
callbackThread = HandlerThread("Callback")
callbackThread.start()
+ // Explicitly pause callback thread since we will control execution manually in tests
+ shadowOf(callbackThread.looper).pause()
callbackHandler = Handler(callbackThread.looper)
executor = CameraXExecutors.newHandlerExecutor(callbackHandler)
}
@@ -138,6 +141,7 @@
imageCapture.takePicture(executor, onImageCapturedCallback)
// Send fake image.
fakeImageReaderProxy?.triggerImageAvailable(TagBundle.create(Pair("TagBundleKey", 0)), 0)
+ shadowOf(getMainLooper()).idle()
flushHandler(callbackHandler)
// Assert.
@@ -182,11 +186,12 @@
imageCapture.takePicture(executor, onImageCapturedCallback)
// Send fake image.
fakeImageReaderProxy?.triggerImageAvailable(TagBundle.create(Pair("TagBundleKey", 0)), 0)
+ shadowOf(getMainLooper()).idle()
flushHandler(callbackHandler)
// Assert.
- Truth.assertThat(capturedImage!!.width).isEqualTo(fakeImageReaderProxy?.width)
- Truth.assertThat(capturedImage!!.height).isEqualTo(fakeImageReaderProxy?.height)
+ assertThat(capturedImage!!.width).isEqualTo(fakeImageReaderProxy?.width)
+ assertThat(capturedImage!!.height).isEqualTo(fakeImageReaderProxy?.height)
}
@Test
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
index 273f27b..b478e33 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.graphics.Rect
import android.os.Build
+import android.os.Looper.getMainLooper
import android.util.Rational
import android.util.Size
import android.view.Surface
@@ -40,6 +41,7 @@
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.Shadows.shadowOf
import java.util.Collections
import java.util.concurrent.ExecutionException
@@ -126,18 +128,20 @@
.getApplicationContext<Context>(), CameraSelector.DEFAULT_BACK_CAMERA
)
cameraUseCaseAdapter.addUseCases(Collections.singleton<UseCase>(preview))
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+
// Unsent pending SurfaceRequest created by pipeline.
val pendingSurfaceRequest = preview.mPendingSurfaceRequest
var receivedSurfaceRequest: SurfaceRequest? = null
// Act: set a SurfaceProvider after attachment.
preview.setSurfaceProvider { receivedSurfaceRequest = it }
+ shadowOf(getMainLooper()).idle()
// Assert: received a SurfaceRequest.
assertThat(receivedSurfaceRequest).isSameInstanceAs(pendingSurfaceRequest)
// Act: set a different SurfaceProvider.
preview.setSurfaceProvider { receivedSurfaceRequest = it }
+ shadowOf(getMainLooper()).idle()
// Assert: received a different SurfaceRequest.
assertThat(receivedSurfaceRequest).isNotSameInstanceAs(pendingSurfaceRequest)
}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ProcessingImageReaderTest.java b/camera/camera-core/src/test/java/androidx/camera/core/ProcessingImageReaderTest.java
index d27dccf..c40e6d8 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ProcessingImageReaderTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ProcessingImageReaderTest.java
@@ -16,11 +16,14 @@
package androidx.camera.core;
+import static android.os.Looper.getMainLooper;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.robolectric.Shadows.shadowOf;
import android.graphics.ImageFormat;
import android.os.AsyncTask;
@@ -41,15 +44,18 @@
import androidx.camera.testing.fakes.FakeImageReaderProxy;
import androidx.test.filters.SmallTest;
+import org.junit.After;
+import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.android.util.concurrent.PausedExecutorService;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.internal.DoNotInstrument;
-import org.robolectric.shadows.ShadowLooper;
import java.util.HashMap;
import java.util.Map;
@@ -58,6 +64,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+@SuppressWarnings("UnstableApiUsage") // Needed because PausedExecutorService is marked @Beta
@SmallTest
@RunWith(RobolectricTestRunner.class)
@DoNotInstrument
@@ -87,7 +94,7 @@
}
};
-
+ private static PausedExecutorService sPausedExecutor;
private final CaptureStage mCaptureStage0 = new FakeCaptureStage(CAPTURE_ID_0, null);
private final CaptureStage mCaptureStage1 = new FakeCaptureStage(CAPTURE_ID_1, null);
private final CaptureStage mCaptureStage2 = new FakeCaptureStage(CAPTURE_ID_2, null);
@@ -97,6 +104,16 @@
private CaptureBundle mCaptureBundle;
private String mTagBundleKey;
+ @BeforeClass
+ public static void setUpClass() {
+ sPausedExecutor = new PausedExecutorService();
+ }
+
+ @AfterClass
+ public static void tearDownClass() {
+ sPausedExecutor.shutdown();
+ }
+
@Before
public void setUp() {
mCaptureBundle = CaptureBundles.createCaptureBundle(mCaptureStage0, mCaptureStage1);
@@ -104,13 +121,19 @@
mMetadataImageReader = new MetadataImageReader(mImageReaderProxy);
}
+ @After
+ public void cleanUp() {
+ // Ensure the PausedExecutorService is drained
+ sPausedExecutor.runAll();
+ }
+
@Test
public void canSetFuturesInSettableImageProxyBundle()
throws InterruptedException, TimeoutException, ExecutionException {
// Sets the callback from ProcessingImageReader to start processing
CaptureProcessor captureProcessor = mock(CaptureProcessor.class);
ProcessingImageReader processingImageReader = new ProcessingImageReader(
- mMetadataImageReader, AsyncTask.THREAD_POOL_EXECUTOR, mCaptureBundle,
+ mMetadataImageReader, sPausedExecutor, mCaptureBundle,
captureProcessor);
processingImageReader.setOnImageAvailableListener(mock(
ImageReaderProxy.OnImageAvailableListener.class),
@@ -145,12 +168,15 @@
triggerImageAvailable(id, captureIdToTime.get(id));
}
- // Ensure all posted tasks finish running
- ShadowLooper.runUiThreadTasks();
+ // Ensure tasks are posted to the processing executor
+ shadowOf(getMainLooper()).idle();
+
+ // Run processing
+ sPausedExecutor.runAll();
ArgumentCaptor<ImageProxyBundle> imageProxyBundleCaptor =
ArgumentCaptor.forClass(ImageProxyBundle.class);
- verify(captureProcessor, timeout(3000).times(1)).process(imageProxyBundleCaptor.capture());
+ verify(captureProcessor, times(1)).process(imageProxyBundleCaptor.capture());
assertThat(imageProxyBundleCaptor.getValue()).isNotNull();
// CaptureProcessor.process should be called once all ImageProxies on the
@@ -187,6 +213,9 @@
triggerImageAvailable(idTimestamp.getKey(), idTimestamp.getValue());
}
+ // Ensure tasks are posted to the processing executor
+ shadowOf(getMainLooper()).idle();
+
// Wait for CaptureProcessor.process() to start so that it is in the middle of processing
assertThat(waitingCaptureProcessor.waitForProcessingToStart(3000)).isTrue();
@@ -194,7 +223,7 @@
// Allow the CaptureProcessor to continue processing. Calling finishProcessing() will
// cause the CaptureProcessor to start accessing the ImageProxy. If the ImageProxy has
- // already been closed then
+ // already been closed then we will time out at waitForProcessingToComplete().
waitingCaptureProcessor.finishProcessing();
// The processing will only complete if no exception was thrown during the processing
@@ -202,11 +231,13 @@
assertThat(waitingCaptureProcessor.waitForProcessingToComplete(3000)).isTrue();
}
+ // Tests that a ProcessingImageReader can be closed while in the process of receiving
+ // ImageProxies for an ImageProxyBundle.
@Test
public void closeImageHalfway() throws InterruptedException {
// Sets the callback from ProcessingImageReader to start processing
ProcessingImageReader processingImageReader = new ProcessingImageReader(
- mMetadataImageReader, AsyncTask.THREAD_POOL_EXECUTOR, mCaptureBundle,
+ mMetadataImageReader, sPausedExecutor, mCaptureBundle,
NOOP_PROCESSOR);
processingImageReader.setOnImageAvailableListener(mock(
ImageReaderProxy.OnImageAvailableListener.class),
@@ -216,9 +247,12 @@
mTagBundleKey = processingImageReader.getTagBundleKey();
triggerImageAvailable(CAPTURE_ID_0, TIMESTAMP_0);
- processingImageReader.close();
+ // Ensure the first image is received by the ProcessingImageReader
+ shadowOf(getMainLooper()).idle();
- triggerImageAvailable(CAPTURE_ID_1, TIMESTAMP_1);
+ // The ProcessingImageReader is closed after receiving the first image, but before
+ // receiving enough images for the entire ImageProxyBundle.
+ processingImageReader.close();
assertThat(mImageReaderProxy.isClosed()).isTrue();
}
@@ -263,15 +297,15 @@
private static class WaitingCaptureProcessor implements CaptureProcessor {
// Block processing so that the ProcessingImageReader can be closed before the
// CaptureProcessor has finished accessing the ImageProxy and ImageProxyBundle
- private CountDownLatch mProcessingLatch = new CountDownLatch(1);
+ private final CountDownLatch mProcessingLatch = new CountDownLatch(1);
// To wait for processing to start. This makes sure that the ProcessingImageReader can be
// closed after processing has started
- private CountDownLatch mProcessingStartLatch = new CountDownLatch(1);
+ private final CountDownLatch mProcessingStartLatch = new CountDownLatch(1);
// Block processing from completing. This ensures that the CaptureProcessor has finished
// accessing the ImageProxy and ImageProxyBundle successfully.
- private CountDownLatch mProcessingComplete = new CountDownLatch(1);
+ private final CountDownLatch mProcessingComplete = new CountDownLatch(1);
WaitingCaptureProcessor() {
}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/impl/CameraRepositoryTest.java b/camera/camera-core/src/test/java/androidx/camera/core/impl/CameraRepositoryTest.java
index 701319c..7a6b809 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/impl/CameraRepositoryTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/impl/CameraRepositoryTest.java
@@ -16,8 +16,12 @@
package androidx.camera.core.impl;
+import static android.os.Looper.getMainLooper;
+
import static com.google.common.truth.Truth.assertThat;
+import static org.robolectric.Shadows.shadowOf;
+
import android.os.Build;
import androidx.camera.core.CameraSelector;
@@ -35,7 +39,6 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.internal.DoNotInstrument;
-import org.robolectric.shadows.ShadowLooper;
import java.util.ArrayList;
import java.util.List;
@@ -118,12 +121,15 @@
ListenableFuture<Void> deinitFuture = mCameraRepository.deinit();
// Needed since FakeCamera uses LiveDataObservable
- ShadowLooper.runUiThreadTasks();
+ shadowOf(getMainLooper()).idle();
assertThat(deinitFuture.isDone()).isTrue();
for (CameraInternal cameraInternal : cameraInternals) {
- assertThat(cameraInternal.getCameraState().fetchData().get()).isEqualTo(
- CameraInternal.State.RELEASED);
+ ListenableFuture<CameraInternal.State> stateFuture =
+ cameraInternal.getCameraState().fetchData();
+ // Needed since FakeCamera uses LiveDataObservable
+ shadowOf(getMainLooper()).idle();
+ assertThat(stateFuture.get()).isEqualTo(CameraInternal.State.RELEASED);
}
}
}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/impl/utils/ExifTest.java b/camera/camera-core/src/test/java/androidx/camera/core/impl/utils/ExifTest.java
index 7a65d76..8fa4dc5 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/impl/utils/ExifTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/impl/utils/ExifTest.java
@@ -20,6 +20,7 @@
import android.location.Location;
import android.os.Build;
+import android.os.SystemClock;
import androidx.test.filters.SmallTest;
@@ -34,6 +35,7 @@
import java.io.IOException;
import java.io.InputStream;
+import java.time.Duration;
@SmallTest
@RunWith(RobolectricTestRunner.class)
@@ -156,13 +158,12 @@
@Test
public void attachedTimestampUsesSystemWallTime() {
- long beforeTimestamp = System.currentTimeMillis();
+ long beforeTimestamp = SystemClock.uptimeMillis();
+ ShadowSystemClock.advanceBy(Duration.ofMillis(100));
- // The Exif class is instrumented since it's in the androidx.* namespace.
- // Set the ShadowSystemClock to match the real system clock.
- ShadowSystemClock.setNanoTime(System.currentTimeMillis() * 1000 * 1000);
mExif.attachTimestamp();
- long afterTimestamp = System.currentTimeMillis();
+ ShadowSystemClock.advanceBy(Duration.ofMillis(100));
+ long afterTimestamp = SystemClock.uptimeMillis();
// Check that the attached timestamp is in the closed range [beforeTimestamp,
// afterTimestamp].
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/impl/utils/MainThreadAsyncHandlerTest.java b/camera/camera-core/src/test/java/androidx/camera/core/impl/utils/MainThreadAsyncHandlerTest.java
index 3917a8d..f8e2ad4 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/impl/utils/MainThreadAsyncHandlerTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/impl/utils/MainThreadAsyncHandlerTest.java
@@ -24,8 +24,6 @@
import androidx.test.filters.SmallTest;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@@ -41,16 +39,6 @@
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
public class MainThreadAsyncHandlerTest {
- @Before
- public void setUp() {
- ShadowLooper.pauseMainLooper();
- }
-
- @After
- public void tearDown() {
- ShadowLooper.idleMainLooperConstantly(true);
- }
-
@Test
public void canPostTaskToMainLooper() {
Handler handler = MainThreadAsyncHandler.getInstance();
diff --git a/camera/camera-extensions/build.gradle b/camera/camera-extensions/build.gradle
index 0633ff5..c6415d4 100644
--- a/camera/camera-extensions/build.gradle
+++ b/camera/camera-extensions/build.gradle
@@ -39,7 +39,7 @@
testImplementation(JUNIT)
testImplementation(MOCKITO_CORE)
testImplementation(ROBOLECTRIC)
-
+ testImplementation(TRUTH)
testImplementation project(":camera:camera-testing")
testImplementation(project(":camera:camera-extensions-stub"))
// To use the extensions-stub for testing directly.
diff --git a/camera/camera-extensions/src/test/java/androidx/camera/extensions/VersionTest.java b/camera/camera-extensions/src/test/java/androidx/camera/extensions/VersionTest.java
index fb6859e..31df428 100644
--- a/camera/camera-extensions/src/test/java/androidx/camera/extensions/VersionTest.java
+++ b/camera/camera-extensions/src/test/java/androidx/camera/extensions/VersionTest.java
@@ -16,15 +16,7 @@
package androidx.camera.extensions;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.greaterThan;
-import static org.hamcrest.Matchers.lessThan;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import org.junit.Test;
@@ -39,60 +31,60 @@
Version version2 = Version.create(2, 0, 0, "test");
- assertTrue(version1.equals(version1_description));
- assertFalse(version1.equals(version1_patch));
- assertFalse(version1.equals(version1_minor));
- assertFalse(version1.equals(version2));
+ assertThat(version1.equals(version1_description)).isTrue();
+ assertThat(version1.equals(version1_patch)).isFalse();
+ assertThat(version1.equals(version1_minor)).isFalse();
+ assertThat(version1.equals(version2)).isFalse();
- assertThat(version1.compareTo(version1_patch), lessThan(0));
- assertThat(version1.compareTo(version1_description), equalTo(0));
- assertThat(version1.compareTo(version1_minor), lessThan(0));
- assertThat(version1.compareTo(version2), lessThan(0));
+ assertThat(version1.compareTo(version1_patch)).isLessThan(0);
+ assertThat(version1.compareTo(version1_description)).isEqualTo(0);
+ assertThat(version1.compareTo(version1_minor)).isLessThan(0);
+ assertThat(version1.compareTo(version2)).isLessThan(0);
- assertThat(version1.compareTo(1), equalTo(0));
- assertThat(version1.compareTo(2), lessThan(0));
- assertThat(version1.compareTo(0), greaterThan(0));
+ assertThat(version1.compareTo(1)).isEqualTo(0);
+ assertThat(version1.compareTo(2)).isLessThan(0);
+ assertThat(version1.compareTo(0)).isGreaterThan(0);
- assertThat(version1.compareTo(1, 0), equalTo(0));
- assertThat(version1.compareTo(1, 1), lessThan(0));
- assertThat(version1_minor.compareTo(1, 0), greaterThan(0));
+ assertThat(version1.compareTo(1, 0)).isEqualTo(0);
+ assertThat(version1.compareTo(1, 1)).isLessThan(0);
+ assertThat(version1_minor.compareTo(1, 0)).isGreaterThan(0);
- assertThat(version1.compareTo(2, 0), lessThan(0));
+ assertThat(version1.compareTo(2, 0)).isLessThan(0);
}
@Test
public void testParseStringVersion() {
Version version1 = Version.parse("1.2.3-description");
- assertNotNull(version1);
- assertEquals(version1.getMajor(), 1);
- assertEquals(version1.getMinor(), 2);
- assertEquals(version1.getPatch(), 3);
- assertEquals(version1.getDescription(), "description");
+ assertThat(version1).isNotNull();
+ assertThat(version1.getMajor()).isEqualTo(1);
+ assertThat(version1.getMinor()).isEqualTo(2);
+ assertThat(version1.getPatch()).isEqualTo(3);
+ assertThat(version1.getDescription()).isEqualTo("description");
Version version2 = Version.parse("4.5.6");
- assertNotNull(version2);
- assertEquals(version2.getDescription(), "");
+ assertThat(version2).isNotNull();
+ assertThat(version2.getDescription()).isEqualTo("");
Version version3 = Version.parse("01.002.0003");
- assertNotNull(version3);
- assertEquals(version3.getMajor(), 1);
- assertEquals(version3.getMinor(), 2);
- assertEquals(version3.getPatch(), 3);
+ assertThat(version3).isNotNull();
+ assertThat(version3.getMajor()).isEqualTo(1);
+ assertThat(version3.getMinor()).isEqualTo(2);
+ assertThat(version3.getPatch()).isEqualTo(3);
// Test invalid input version string.
- assertNull(Version.parse("1.0"));
- assertNull(Version.parse("1. 0.0"));
- assertNull(Version.parse("1..0"));
- assertNull(Version.parse("1.0.a"));
- assertNull(Version.parse("1.0.0."));
- assertNull(Version.parse("1.0.0.description"));
+ assertThat(Version.parse("1.0")).isNull();
+ assertThat(Version.parse("1. 0.0")).isNull();
+ assertThat(Version.parse("1..0")).isNull();
+ assertThat(Version.parse("1.0.a")).isNull();
+ assertThat(Version.parse("1.0.0.")).isNull();
+ assertThat(Version.parse("1.0.0.description")).isNull();
- assertNull(Version.parse("1.0.0.0"));
- assertNull(Version.parse("1.0.-0"));
- assertNull(Version.parse("1.0.-0"));
- assertNull(Version.parse("(1.0.0)"));
- assertNull(Version.parse(" 1.0.0 "));
+ assertThat(Version.parse("1.0.0.0")).isNull();
+ assertThat(Version.parse("1.0.-0")).isNull();
+ assertThat(Version.parse("1.0.-0")).isNull();
+ assertThat(Version.parse("(1.0.0)")).isNull();
+ assertThat(Version.parse(" 1.0.0 ")).isNull();
}
}