[DataBroker] Hand over data to ScriptExecutor
The unit test uses a fake script executor which tests the logic to pass
data from DataBroker to ScriptExecutor
Bug: 191993518
Test: atest CarServiceUnitTest:DataBrokerUnitTest
Change-Id: I7e93171f7759bef851504bab7d90c9c9a69348f1
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerUnitTest.java
index f4cde37..d1e0f2e 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerUnitTest.java
@@ -16,16 +16,25 @@
package com.android.car.telemetry.databroker;
-import static com.android.car.telemetry.databroker.DataBrokerImpl.MSG_HANDLE_TASK;
-
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.Nullable;
import android.car.hardware.CarPropertyConfig;
+import android.car.telemetry.IScriptExecutor;
+import android.car.telemetry.IScriptExecutorListener;
+import android.content.Context;
+import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
import android.os.SystemClock;
import com.android.car.CarPropertyService;
@@ -35,6 +44,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@@ -72,21 +83,41 @@
TelemetryProto.MetricsConfig.newBuilder().setName("Bar").setVersion(
1).addSubscribers(SUBSCRIBER_BAR).build();
- @Mock
- private CarPropertyService mMockCarPropertyService;
-
private DataBrokerImpl mDataBroker;
+ private FakeScriptExecutor mFakeScriptExecutor;
private Handler mHandler;
private ScriptExecutionTask mHighPriorityTask;
private ScriptExecutionTask mLowPriorityTask;
+ @Mock
+ private Context mMockContext;
+ @Mock
+ private CarPropertyService mMockCarPropertyService;
+ @Mock
+ private IBinder mMockScriptExecutorBinder;
+
+ @Captor
+ private ArgumentCaptor<ServiceConnection> mServiceConnectionCaptor;
+
@Before
public void setUp() {
when(mMockCarPropertyService.getPropertyList())
.thenReturn(Collections.singletonList(PROP_CONFIG));
+ // bind service should return true, otherwise broker is disabled
+ when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
PublisherFactory factory = new PublisherFactory(mMockCarPropertyService);
- mDataBroker = new DataBrokerImpl(factory);
+ mDataBroker = new DataBrokerImpl(mMockContext, factory);
mHandler = mDataBroker.getWorkerHandler();
+
+ mFakeScriptExecutor = new FakeScriptExecutor();
+ when(mMockScriptExecutorBinder.queryLocalInterface(anyString()))
+ .thenReturn(mFakeScriptExecutor);
+ // capture ServiceConnection and connect to fake ScriptExecutor
+ verify(mMockContext).bindServiceAsUser(
+ any(), mServiceConnectionCaptor.capture(), anyInt(), any());
+ mServiceConnectionCaptor.getValue().onServiceConnected(
+ null, mMockScriptExecutorBinder);
+
mHighPriorityTask = new ScriptExecutionTask(
new DataSubscriber(mDataBroker, METRICS_CONFIG_FOO, SUBSCRIBER_FOO, PRIORITY_HIGH),
DATA,
@@ -98,14 +129,15 @@
}
@Test
- public void testSetTaskExecutionPriority_whenNoTask_shouldNotScheduleTask() {
+ public void testSetTaskExecutionPriority_whenNoTask_shouldNotInvokeScriptExecutor() {
mDataBroker.setTaskExecutionPriority(PRIORITY_HIGH);
- assertThat(mHandler.hasMessages(MSG_HANDLE_TASK)).isFalse();
+ waitForHandlerThreadToFinish();
+ assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(0);
}
@Test
- public void testSetTaskExecutionPriority_whenNextTaskPriorityLow_shouldNotPollTask() {
+ public void testSetTaskExecutionPriority_whenNextTaskPriorityLow_shouldNotRunTask() {
mDataBroker.getTaskQueue().add(mLowPriorityTask);
mDataBroker.setTaskExecutionPriority(PRIORITY_HIGH);
@@ -113,10 +145,11 @@
waitForHandlerThreadToFinish();
// task is not polled
assertThat(mDataBroker.getTaskQueue().peek()).isEqualTo(mLowPriorityTask);
+ assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(0);
}
@Test
- public void testSetTaskExecutionPriority_whenNextTaskPriorityHigh_shouldPollTask() {
+ public void testSetTaskExecutionPriority_whenNextTaskPriorityHigh_shouldInvokeScriptExecutor() {
mDataBroker.getTaskQueue().add(mHighPriorityTask);
mDataBroker.setTaskExecutionPriority(PRIORITY_HIGH);
@@ -124,17 +157,19 @@
waitForHandlerThreadToFinish();
// task is polled and run
assertThat(mDataBroker.getTaskQueue().peek()).isNull();
+ assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(1);
}
@Test
- public void testScheduleNextTask_whenNoTask_shouldNotSendMessageToHandler() {
+ public void testScheduleNextTask_whenNoTask_shouldNotInvokeScriptExecutor() {
mDataBroker.scheduleNextTask();
- assertThat(mHandler.hasMessages(MSG_HANDLE_TASK)).isFalse();
+ waitForHandlerThreadToFinish();
+ assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(0);
}
@Test
- public void testScheduleNextTask_whenTaskInProgress_shouldNotSendMessageToHandler() {
+ public void testScheduleNextTask_whenTaskInProgress_shouldNotInvokeScriptExecutorAgain() {
PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
taskQueue.add(mHighPriorityTask);
mDataBroker.scheduleNextTask(); // start a task
@@ -144,16 +179,68 @@
mDataBroker.scheduleNextTask(); // schedule next task while the last task is in progress
- // verify no message is sent to handler and no task is polled
- assertThat(mHandler.hasMessages(MSG_HANDLE_TASK)).isFalse();
+ // verify task is not polled
+ assertThat(taskQueue.peek()).isEqualTo(mHighPriorityTask);
+ // expect one invocation for the task that is running
+ assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void testScheduleNextTask_whenTaskCompletes_shouldAutomaticallyScheduleNextTask() {
+ PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
+ // add two tasks into the queue for execution
+ taskQueue.add(mHighPriorityTask);
+ taskQueue.add(mHighPriorityTask);
+
+ mDataBroker.scheduleNextTask(); // start a task
+ waitForHandlerThreadToFinish();
+ // end a task, should automatically schedule the next task
+ mFakeScriptExecutor.notifyScriptSuccess();
+
+ waitForHandlerThreadToFinish();
+ // verify queue is empty, both tasks are polled and executed
+ assertThat(taskQueue.peek()).isNull();
+ assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(2);
+ }
+
+ @Test
+ public void testScheduleNextTask_whenBindScriptExecutorFailed_shouldDisableBroker() {
+ mDataBroker.addMetricsConfiguration(METRICS_CONFIG_FOO);
+ PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
+ taskQueue.add(mHighPriorityTask);
+ // disconnect ScriptExecutor and fail all future attempts to bind to it
+ mServiceConnectionCaptor.getValue().onServiceDisconnected(null);
+ when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(false);
+
+ // will rebind to ScriptExecutor if it is null
+ mDataBroker.scheduleNextTask();
+
+ waitForHandlerThreadToFinish();
+ // all subscribers should have been removed
+ assertThat(mDataBroker.getSubscriptionMap()).hasSize(0);
+ assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void testScheduleNextTask_whenScriptExecutorFails_shouldRequeueTask() {
+ PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
+ taskQueue.add(mHighPriorityTask);
+ mFakeScriptExecutor.failNextApiCalls(1); // fail the next invokeScript() call
+
+ mDataBroker.scheduleNextTask();
+
+ waitForHandlerThreadToFinish();
+ // expect invokeScript() to be called and failed, causing the same task to be re-queued
+ assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(1);
assertThat(taskQueue.peek()).isEqualTo(mHighPriorityTask);
}
@Test
- public void testAddTaskToQueue_shouldScheduleNextTask() {
+ public void testAddTaskToQueue_shouldInvokeScriptExecutor() {
mDataBroker.addTaskToQueue(mHighPriorityTask);
- assertThat(mHandler.hasMessages(MSG_HANDLE_TASK)).isTrue();
+ waitForHandlerThreadToFinish();
+ assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(1);
}
@Test
@@ -212,4 +299,46 @@
assertWithMessage("handler not idle in %sms", TIMEOUT_MS)
.that(mHandler.runWithScissors(() -> {}, TIMEOUT_MS)).isTrue();
}
+
+ private static class FakeScriptExecutor implements IScriptExecutor {
+ private IScriptExecutorListener mListener;
+ private int mApiInvocationCount = 0;
+ private int mFailApi = 0;
+
+ @Override
+ public void invokeScript(String scriptBody, String functionName, Bundle publishedData,
+ @Nullable Bundle savedState, IScriptExecutorListener listener)
+ throws RemoteException {
+ mApiInvocationCount++;
+ mListener = listener;
+ if (mFailApi > 0) {
+ mFailApi--;
+ throw new RemoteException("Simulated failure");
+ }
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+
+ /** Mocks script completion. */
+ public void notifyScriptSuccess() {
+ try {
+ mListener.onSuccess(new Bundle());
+ } catch (RemoteException e) {
+ // nothing to do
+ }
+ }
+
+ /** Fails the next N invokeScript() call. */
+ public void failNextApiCalls(int n) {
+ mFailApi = n;
+ }
+
+ /** Returns number of times the ScriptExecutor API was invoked. */
+ public int getApiInvocationCount() {
+ return mApiInvocationCount;
+ }
+ }
}