blob: 256f4e64c36fa1f3e99db86de549931f8fb63ec1 [file] [log] [blame]
Rui Qiu337cb242021-06-03 11:26:16 -07001/*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.car.telemetry.databroker;
18
19import static com.google.common.truth.Truth.assertThat;
Rui Qiu033041e2021-06-21 14:59:11 -070020import static com.google.common.truth.Truth.assertWithMessage;
Rui Qiu337cb242021-06-03 11:26:16 -070021
Rui Qiua86ab602021-07-02 10:40:36 -070022import static org.mockito.ArgumentMatchers.any;
23import static org.mockito.ArgumentMatchers.anyInt;
24import static org.mockito.ArgumentMatchers.anyString;
25import static org.mockito.Mockito.verify;
Zhomart Mukhamejanov44570cb2021-06-07 10:23:13 -070026import static org.mockito.Mockito.when;
Rui Qiu337cb242021-06-03 11:26:16 -070027
Rui Qiua86ab602021-07-02 10:40:36 -070028import android.annotation.Nullable;
Zhomart Mukhamejanov44570cb2021-06-07 10:23:13 -070029import android.car.hardware.CarPropertyConfig;
Rui Qiua86ab602021-07-02 10:40:36 -070030import android.content.Context;
31import android.content.ServiceConnection;
Rui Qiu033041e2021-06-21 14:59:11 -070032import android.os.Bundle;
33import android.os.Handler;
Rui Qiua86ab602021-07-02 10:40:36 -070034import android.os.IBinder;
35import android.os.RemoteException;
Rui Qiu033041e2021-06-21 14:59:11 -070036import android.os.SystemClock;
Zhomart Mukhamejanov44570cb2021-06-07 10:23:13 -070037
38import com.android.car.CarPropertyService;
Max Dashouk3b7e3502021-08-13 17:54:51 +000039import com.android.car.scriptexecutor.IScriptExecutor;
40import com.android.car.scriptexecutor.IScriptExecutorListener;
Zhomart Mukhamejanov44570cb2021-06-07 10:23:13 -070041import com.android.car.telemetry.TelemetryProto;
42import com.android.car.telemetry.publisher.PublisherFactory;
43
44import org.junit.Before;
Rui Qiu337cb242021-06-03 11:26:16 -070045import org.junit.Test;
46import org.junit.runner.RunWith;
Rui Qiua86ab602021-07-02 10:40:36 -070047import org.mockito.ArgumentCaptor;
48import org.mockito.Captor;
Zhomart Mukhamejanov44570cb2021-06-07 10:23:13 -070049import org.mockito.Mock;
Rui Qiu337cb242021-06-03 11:26:16 -070050import org.mockito.junit.MockitoJUnitRunner;
51
Zhomart Mukhamejanov44570cb2021-06-07 10:23:13 -070052import java.util.Collections;
Rui Qiu033041e2021-06-21 14:59:11 -070053import java.util.concurrent.PriorityBlockingQueue;
Zhomart Mukhamejanov44570cb2021-06-07 10:23:13 -070054
Rui Qiu337cb242021-06-03 11:26:16 -070055@RunWith(MockitoJUnitRunner.class)
56public class DataBrokerUnitTest {
Zhomart Mukhamejanov44570cb2021-06-07 10:23:13 -070057 private static final int PROP_ID = 100;
58 private static final int PROP_AREA = 200;
Rui Qiu033041e2021-06-21 14:59:11 -070059 private static final int PRIORITY_HIGH = 1;
Rui Qiud95f95d2021-08-17 12:29:33 -070060 private static final int PRIORITY_LOW = 100;
Rui Qiu033041e2021-06-21 14:59:11 -070061 private static final long TIMEOUT_MS = 5_000L;
Zhomart Mukhamejanov44570cb2021-06-07 10:23:13 -070062 private static final CarPropertyConfig<Integer> PROP_CONFIG =
63 CarPropertyConfig.newBuilder(Integer.class, PROP_ID, PROP_AREA).setAccess(
64 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ).build();
Rui Qiu033041e2021-06-21 14:59:11 -070065 private static final Bundle DATA = new Bundle();
Rui Qiu337cb242021-06-03 11:26:16 -070066 private static final TelemetryProto.VehiclePropertyPublisher
67 VEHICLE_PROPERTY_PUBLISHER_CONFIGURATION =
68 TelemetryProto.VehiclePropertyPublisher.newBuilder().setReadRate(
Zhomart Mukhamejanov44570cb2021-06-07 10:23:13 -070069 1).setVehiclePropertyId(PROP_ID).build();
Rui Qiu337cb242021-06-03 11:26:16 -070070 private static final TelemetryProto.Publisher PUBLISHER_CONFIGURATION =
71 TelemetryProto.Publisher.newBuilder().setVehicleProperty(
72 VEHICLE_PROPERTY_PUBLISHER_CONFIGURATION).build();
73 private static final TelemetryProto.Subscriber SUBSCRIBER_FOO =
74 TelemetryProto.Subscriber.newBuilder().setHandler("function_name_foo").setPublisher(
Rui Qiud95f95d2021-08-17 12:29:33 -070075 PUBLISHER_CONFIGURATION).setPriority(PRIORITY_HIGH).build();
Rui Qiu337cb242021-06-03 11:26:16 -070076 private static final TelemetryProto.MetricsConfig METRICS_CONFIG_FOO =
77 TelemetryProto.MetricsConfig.newBuilder().setName("Foo").setVersion(
78 1).addSubscribers(SUBSCRIBER_FOO).build();
79 private static final TelemetryProto.Subscriber SUBSCRIBER_BAR =
80 TelemetryProto.Subscriber.newBuilder().setHandler("function_name_bar").setPublisher(
Rui Qiud95f95d2021-08-17 12:29:33 -070081 PUBLISHER_CONFIGURATION).setPriority(PRIORITY_LOW).build();
Rui Qiu337cb242021-06-03 11:26:16 -070082 private static final TelemetryProto.MetricsConfig METRICS_CONFIG_BAR =
83 TelemetryProto.MetricsConfig.newBuilder().setName("Bar").setVersion(
84 1).addSubscribers(SUBSCRIBER_BAR).build();
85
Zhomart Mukhamejanov44570cb2021-06-07 10:23:13 -070086 private DataBrokerImpl mDataBroker;
Rui Qiua86ab602021-07-02 10:40:36 -070087 private FakeScriptExecutor mFakeScriptExecutor;
Rui Qiu033041e2021-06-21 14:59:11 -070088 private Handler mHandler;
89 private ScriptExecutionTask mHighPriorityTask;
90 private ScriptExecutionTask mLowPriorityTask;
Zhomart Mukhamejanov44570cb2021-06-07 10:23:13 -070091
Rui Qiua86ab602021-07-02 10:40:36 -070092 @Mock
93 private Context mMockContext;
94 @Mock
95 private CarPropertyService mMockCarPropertyService;
96 @Mock
97 private IBinder mMockScriptExecutorBinder;
98
99 @Captor
100 private ArgumentCaptor<ServiceConnection> mServiceConnectionCaptor;
101
Zhomart Mukhamejanov44570cb2021-06-07 10:23:13 -0700102 @Before
103 public void setUp() {
104 when(mMockCarPropertyService.getPropertyList())
105 .thenReturn(Collections.singletonList(PROP_CONFIG));
Rui Qiua86ab602021-07-02 10:40:36 -0700106 // bind service should return true, otherwise broker is disabled
107 when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
Zhomart Mukhamejanov44570cb2021-06-07 10:23:13 -0700108 PublisherFactory factory = new PublisherFactory(mMockCarPropertyService);
Rui Qiua86ab602021-07-02 10:40:36 -0700109 mDataBroker = new DataBrokerImpl(mMockContext, factory);
Rui Qiu033041e2021-06-21 14:59:11 -0700110 mHandler = mDataBroker.getWorkerHandler();
Rui Qiua86ab602021-07-02 10:40:36 -0700111
112 mFakeScriptExecutor = new FakeScriptExecutor();
113 when(mMockScriptExecutorBinder.queryLocalInterface(anyString()))
114 .thenReturn(mFakeScriptExecutor);
115 // capture ServiceConnection and connect to fake ScriptExecutor
116 verify(mMockContext).bindServiceAsUser(
117 any(), mServiceConnectionCaptor.capture(), anyInt(), any());
118 mServiceConnectionCaptor.getValue().onServiceConnected(
119 null, mMockScriptExecutorBinder);
120
Rui Qiu033041e2021-06-21 14:59:11 -0700121 mHighPriorityTask = new ScriptExecutionTask(
Rui Qiud95f95d2021-08-17 12:29:33 -0700122 new DataSubscriber(mDataBroker, METRICS_CONFIG_FOO, SUBSCRIBER_FOO),
Rui Qiu033041e2021-06-21 14:59:11 -0700123 DATA,
124 SystemClock.elapsedRealtime());
125 mLowPriorityTask = new ScriptExecutionTask(
Rui Qiud95f95d2021-08-17 12:29:33 -0700126 new DataSubscriber(mDataBroker, METRICS_CONFIG_BAR, SUBSCRIBER_BAR),
Rui Qiu033041e2021-06-21 14:59:11 -0700127 DATA,
128 SystemClock.elapsedRealtime());
129 }
130
131 @Test
Rui Qiua86ab602021-07-02 10:40:36 -0700132 public void testSetTaskExecutionPriority_whenNoTask_shouldNotInvokeScriptExecutor() {
Rui Qiu033041e2021-06-21 14:59:11 -0700133 mDataBroker.setTaskExecutionPriority(PRIORITY_HIGH);
134
Rui Qiua86ab602021-07-02 10:40:36 -0700135 waitForHandlerThreadToFinish();
136 assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(0);
Rui Qiu033041e2021-06-21 14:59:11 -0700137 }
138
139 @Test
Rui Qiua86ab602021-07-02 10:40:36 -0700140 public void testSetTaskExecutionPriority_whenNextTaskPriorityLow_shouldNotRunTask() {
Rui Qiu033041e2021-06-21 14:59:11 -0700141 mDataBroker.getTaskQueue().add(mLowPriorityTask);
142
143 mDataBroker.setTaskExecutionPriority(PRIORITY_HIGH);
144
145 waitForHandlerThreadToFinish();
146 // task is not polled
147 assertThat(mDataBroker.getTaskQueue().peek()).isEqualTo(mLowPriorityTask);
Rui Qiua86ab602021-07-02 10:40:36 -0700148 assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(0);
Rui Qiu033041e2021-06-21 14:59:11 -0700149 }
150
151 @Test
Rui Qiua86ab602021-07-02 10:40:36 -0700152 public void testSetTaskExecutionPriority_whenNextTaskPriorityHigh_shouldInvokeScriptExecutor() {
Rui Qiu033041e2021-06-21 14:59:11 -0700153 mDataBroker.getTaskQueue().add(mHighPriorityTask);
154
155 mDataBroker.setTaskExecutionPriority(PRIORITY_HIGH);
156
157 waitForHandlerThreadToFinish();
158 // task is polled and run
159 assertThat(mDataBroker.getTaskQueue().peek()).isNull();
Rui Qiua86ab602021-07-02 10:40:36 -0700160 assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(1);
Rui Qiu033041e2021-06-21 14:59:11 -0700161 }
162
163 @Test
Rui Qiua86ab602021-07-02 10:40:36 -0700164 public void testScheduleNextTask_whenNoTask_shouldNotInvokeScriptExecutor() {
Rui Qiu033041e2021-06-21 14:59:11 -0700165 mDataBroker.scheduleNextTask();
166
Rui Qiua86ab602021-07-02 10:40:36 -0700167 waitForHandlerThreadToFinish();
168 assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(0);
Rui Qiu033041e2021-06-21 14:59:11 -0700169 }
170
171 @Test
Rui Qiua86ab602021-07-02 10:40:36 -0700172 public void testScheduleNextTask_whenTaskInProgress_shouldNotInvokeScriptExecutorAgain() {
Rui Qiu033041e2021-06-21 14:59:11 -0700173 PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
174 taskQueue.add(mHighPriorityTask);
175 mDataBroker.scheduleNextTask(); // start a task
176 waitForHandlerThreadToFinish();
177 assertThat(taskQueue.peek()).isNull(); // assert that task is polled and running
178 taskQueue.add(mHighPriorityTask); // add another task into the queue
179
180 mDataBroker.scheduleNextTask(); // schedule next task while the last task is in progress
181
Rui Qiua86ab602021-07-02 10:40:36 -0700182 // verify task is not polled
183 assertThat(taskQueue.peek()).isEqualTo(mHighPriorityTask);
184 // expect one invocation for the task that is running
185 assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(1);
186 }
187
188 @Test
189 public void testScheduleNextTask_whenTaskCompletes_shouldAutomaticallyScheduleNextTask() {
190 PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
191 // add two tasks into the queue for execution
192 taskQueue.add(mHighPriorityTask);
193 taskQueue.add(mHighPriorityTask);
194
195 mDataBroker.scheduleNextTask(); // start a task
196 waitForHandlerThreadToFinish();
197 // end a task, should automatically schedule the next task
198 mFakeScriptExecutor.notifyScriptSuccess();
199
200 waitForHandlerThreadToFinish();
201 // verify queue is empty, both tasks are polled and executed
202 assertThat(taskQueue.peek()).isNull();
203 assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(2);
204 }
205
206 @Test
207 public void testScheduleNextTask_whenBindScriptExecutorFailed_shouldDisableBroker() {
208 mDataBroker.addMetricsConfiguration(METRICS_CONFIG_FOO);
209 PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
210 taskQueue.add(mHighPriorityTask);
211 // disconnect ScriptExecutor and fail all future attempts to bind to it
212 mServiceConnectionCaptor.getValue().onServiceDisconnected(null);
213 when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(false);
214
215 // will rebind to ScriptExecutor if it is null
216 mDataBroker.scheduleNextTask();
217
218 waitForHandlerThreadToFinish();
219 // all subscribers should have been removed
220 assertThat(mDataBroker.getSubscriptionMap()).hasSize(0);
221 assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(0);
222 }
223
224 @Test
225 public void testScheduleNextTask_whenScriptExecutorFails_shouldRequeueTask() {
226 PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
227 taskQueue.add(mHighPriorityTask);
228 mFakeScriptExecutor.failNextApiCalls(1); // fail the next invokeScript() call
229
230 mDataBroker.scheduleNextTask();
231
232 waitForHandlerThreadToFinish();
233 // expect invokeScript() to be called and failed, causing the same task to be re-queued
234 assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(1);
Rui Qiu033041e2021-06-21 14:59:11 -0700235 assertThat(taskQueue.peek()).isEqualTo(mHighPriorityTask);
236 }
237
238 @Test
Rui Qiua86ab602021-07-02 10:40:36 -0700239 public void testAddTaskToQueue_shouldInvokeScriptExecutor() {
Rui Qiu033041e2021-06-21 14:59:11 -0700240 mDataBroker.addTaskToQueue(mHighPriorityTask);
241
Rui Qiua86ab602021-07-02 10:40:36 -0700242 waitForHandlerThreadToFinish();
243 assertThat(mFakeScriptExecutor.getApiInvocationCount()).isEqualTo(1);
Zhomart Mukhamejanov44570cb2021-06-07 10:23:13 -0700244 }
245
Rui Qiu337cb242021-06-03 11:26:16 -0700246 @Test
247 public void testAddMetricsConfiguration_newMetricsConfig() {
Rui Qiuf14c6a22021-07-14 10:39:16 -0700248 mDataBroker.addMetricsConfiguration(METRICS_CONFIG_BAR);
249
250 waitForHandlerThreadToFinish();
251 assertThat(mDataBroker.getSubscriptionMap()).hasSize(1);
252 assertThat(mDataBroker.getSubscriptionMap()).containsKey(METRICS_CONFIG_BAR.getName());
253 // there should be one data subscriber in the subscription list of METRICS_CONFIG_BAR
254 assertThat(mDataBroker.getSubscriptionMap().get(METRICS_CONFIG_BAR.getName())).hasSize(1);
255 }
256
257
258 @Test
259 public void testAddMetricsConfiguration_duplicateMetricsConfig_shouldDoNothing() {
260 // this metrics config has already been added in setUp()
Rui Qiu337cb242021-06-03 11:26:16 -0700261 mDataBroker.addMetricsConfiguration(METRICS_CONFIG_FOO);
262
Rui Qiu033041e2021-06-21 14:59:11 -0700263 waitForHandlerThreadToFinish();
Rui Qiuf14c6a22021-07-14 10:39:16 -0700264 assertThat(mDataBroker.getSubscriptionMap()).hasSize(1);
Rui Qiu337cb242021-06-03 11:26:16 -0700265 assertThat(mDataBroker.getSubscriptionMap()).containsKey(METRICS_CONFIG_FOO.getName());
Rui Qiu337cb242021-06-03 11:26:16 -0700266 assertThat(mDataBroker.getSubscriptionMap().get(METRICS_CONFIG_FOO.getName())).hasSize(1);
267 }
268
269 @Test
Rui Qiuf14c6a22021-07-14 10:39:16 -0700270 public void testRemoveMetricsConfiguration_shouldRemoveAllAssociatedTasks() {
Rui Qiu337cb242021-06-03 11:26:16 -0700271 mDataBroker.addMetricsConfiguration(METRICS_CONFIG_FOO);
272 mDataBroker.addMetricsConfiguration(METRICS_CONFIG_BAR);
Rui Qiud95f95d2021-08-17 12:29:33 -0700273 ScriptExecutionTask taskWithMetricsConfigFoo = new ScriptExecutionTask(
274 new DataSubscriber(mDataBroker, METRICS_CONFIG_FOO, SUBSCRIBER_FOO),
275 DATA,
Rui Qiuf14c6a22021-07-14 10:39:16 -0700276 SystemClock.elapsedRealtime());
277 PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
278 taskQueue.add(mHighPriorityTask); // associated with METRICS_CONFIG_FOO
Rui Qiud95f95d2021-08-17 12:29:33 -0700279 taskQueue.add(mLowPriorityTask); // associated with METRICS_CONFIG_BAR
280 taskQueue.add(taskWithMetricsConfigFoo); // associated with METRICS_CONFIG_FOO
Rui Qiuf14c6a22021-07-14 10:39:16 -0700281 assertThat(taskQueue).hasSize(3);
282
283 mDataBroker.removeMetricsConfiguration(METRICS_CONFIG_FOO);
Rui Qiu337cb242021-06-03 11:26:16 -0700284
Rui Qiu033041e2021-06-21 14:59:11 -0700285 waitForHandlerThreadToFinish();
Rui Qiuf14c6a22021-07-14 10:39:16 -0700286 assertThat(taskQueue).hasSize(1);
Rui Qiud95f95d2021-08-17 12:29:33 -0700287 assertThat(taskQueue.poll()).isEqualTo(mLowPriorityTask);
Rui Qiuf14c6a22021-07-14 10:39:16 -0700288 }
289
290 @Test
291 public void testRemoveMetricsConfiguration_whenMetricsConfigNonExistent_shouldDoNothing() {
292 mDataBroker.removeMetricsConfiguration(METRICS_CONFIG_BAR);
293
294 waitForHandlerThreadToFinish();
295 assertThat(mDataBroker.getSubscriptionMap()).hasSize(0);
Rui Qiu337cb242021-06-03 11:26:16 -0700296 }
297
Rui Qiu033041e2021-06-21 14:59:11 -0700298 private void waitForHandlerThreadToFinish() {
299 assertWithMessage("handler not idle in %sms", TIMEOUT_MS)
300 .that(mHandler.runWithScissors(() -> {}, TIMEOUT_MS)).isTrue();
Rui Qiu337cb242021-06-03 11:26:16 -0700301 }
Rui Qiua86ab602021-07-02 10:40:36 -0700302
303 private static class FakeScriptExecutor implements IScriptExecutor {
304 private IScriptExecutorListener mListener;
305 private int mApiInvocationCount = 0;
306 private int mFailApi = 0;
307
308 @Override
309 public void invokeScript(String scriptBody, String functionName, Bundle publishedData,
310 @Nullable Bundle savedState, IScriptExecutorListener listener)
311 throws RemoteException {
312 mApiInvocationCount++;
313 mListener = listener;
314 if (mFailApi > 0) {
315 mFailApi--;
316 throw new RemoteException("Simulated failure");
317 }
318 }
319
320 @Override
321 public IBinder asBinder() {
322 return null;
323 }
324
325 /** Mocks script completion. */
326 public void notifyScriptSuccess() {
327 try {
328 mListener.onSuccess(new Bundle());
329 } catch (RemoteException e) {
330 // nothing to do
331 }
332 }
333
334 /** Fails the next N invokeScript() call. */
335 public void failNextApiCalls(int n) {
336 mFailApi = n;
337 }
338
339 /** Returns number of times the ScriptExecutor API was invoked. */
340 public int getApiInvocationCount() {
341 return mApiInvocationCount;
342 }
343 }
Rui Qiu337cb242021-06-03 11:26:16 -0700344}