• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 package com.google.android.chre.test.chqts;
17 
18 import android.hardware.location.ContextHubClient;
19 import android.hardware.location.ContextHubClientCallback;
20 import android.hardware.location.ContextHubInfo;
21 import android.hardware.location.ContextHubManager;
22 import android.hardware.location.ContextHubTransaction;
23 import android.hardware.location.NanoAppBinary;
24 import android.hardware.location.NanoAppMessage;
25 import android.util.Log;
26 
27 import com.google.android.utils.chre.ChreTestUtil;
28 
29 import org.junit.Assert;
30 
31 import java.nio.ByteBuffer;
32 import java.nio.ByteOrder;
33 import java.nio.charset.Charset;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.HashSet;
37 import java.util.List;
38 import java.util.Set;
39 import java.util.concurrent.CountDownLatch;
40 import java.util.concurrent.TimeUnit;
41 import java.util.concurrent.atomic.AtomicReference;
42 
43 /**
44  * A class that can execute the CHQTS "general" tests. Nanoapps using this "general" test framework
45  * have the name "general_test".
46  *
47  * A test successfully passes in one of two ways:
48  * - MessageType.SUCCESS received from the Nanoapp by this infrastructure.
49  * - A call to ContextHubGeneralTestExecutor.pass() by the test code.
50  *
51  * NOTE: A test must extend this class and define the handleNanoappMessage() function to handle
52  * specific messages for the test.
53  *
54  * TODO: Refactor this class to be able to be invoked for < P builds.
55  */
56 public abstract class ContextHubGeneralTestExecutor extends ContextHubClientCallback {
57     public static final String TAG = "ContextHubGeneralTestExecutor";
58 
59     private final List<GeneralTestNanoApp> mGeneralTestNanoAppList;
60 
61     private final Set<Long> mNanoAppIdSet;
62 
63     private ContextHubClient mContextHubClient;
64 
65     private final ContextHubManager mContextHubManager;
66 
67     private final ContextHubInfo mContextHubInfo;
68 
69     private CountDownLatch mCountDownLatch;
70 
71     private boolean mInitialized = false;
72 
73     private AtomicReference<String> mErrorString = new AtomicReference<>(null);
74 
75     private long mThreadId;
76 
77     /**
78      * A container class to describe a general_test nanoapp.
79      */
80     public static class GeneralTestNanoApp {
81         private final NanoAppBinary mNanoAppBinary;
82         private final ContextHubTestConstants.TestNames mTestName;
83 
84         // Set to false if the nanoapp should not be loaded at init. An example of why this may be
85         // needed are for nanoapps that are loaded in the middle of the test execution, but still
86         // needs to be included in this test executor (e.g. deliver messages from it).
87         private final boolean mLoadAtInit;
88 
89         // Set to false if the nanoapp should not send a start message at init. An example of where
90         // this is not needed is for test nanoapps that use the general_test protocol, but do not
91         // require a start message (e.g. starts on load like the busy_startup nanoapp).
92         private final boolean mSendStartMessage;
93 
GeneralTestNanoApp(NanoAppBinary nanoAppBinary, ContextHubTestConstants.TestNames testName)94         public GeneralTestNanoApp(NanoAppBinary nanoAppBinary,
95                 ContextHubTestConstants.TestNames testName) {
96             mTestName = testName;
97             mNanoAppBinary = nanoAppBinary;
98             mLoadAtInit = true;
99             mSendStartMessage = true;
100         }
101 
GeneralTestNanoApp(NanoAppBinary nanoAppBinary, ContextHubTestConstants.TestNames testName, boolean loadAtInit)102         public GeneralTestNanoApp(NanoAppBinary nanoAppBinary,
103                 ContextHubTestConstants.TestNames testName, boolean loadAtInit) {
104             mTestName = testName;
105             mNanoAppBinary = nanoAppBinary;
106             mLoadAtInit = loadAtInit;
107             mSendStartMessage = true;
108         }
109 
GeneralTestNanoApp(NanoAppBinary nanoAppBinary, ContextHubTestConstants.TestNames testName, boolean loadAtInit, boolean sendStartMessage)110         public GeneralTestNanoApp(NanoAppBinary nanoAppBinary,
111                 ContextHubTestConstants.TestNames testName,
112                 boolean loadAtInit, boolean sendStartMessage) {
113             mTestName = testName;
114             mNanoAppBinary = nanoAppBinary;
115             mLoadAtInit = loadAtInit;
116             mSendStartMessage = sendStartMessage;
117         }
118 
getNanoAppBinary()119         public NanoAppBinary getNanoAppBinary() {
120             return mNanoAppBinary;
121         }
122 
getTestName()123         public ContextHubTestConstants.TestNames getTestName() {
124             return mTestName;
125         }
126 
loadAtInit()127         public boolean loadAtInit() {
128             return mLoadAtInit;
129         }
130 
sendStartMessage()131         public boolean sendStartMessage() {
132             return mSendStartMessage;
133         }
134     }
135 
136     /**
137      * Note that this constructor accepts multiple general_test nanoapps to test.
138      */
ContextHubGeneralTestExecutor(ContextHubManager manager, ContextHubInfo info, GeneralTestNanoApp... tests)139     public ContextHubGeneralTestExecutor(ContextHubManager manager, ContextHubInfo info,
140             GeneralTestNanoApp... tests) {
141         mContextHubManager = manager;
142         mContextHubInfo = info;
143         mGeneralTestNanoAppList = new ArrayList<>(Arrays.asList(tests));
144         mNanoAppIdSet = new HashSet<>();
145         for (GeneralTestNanoApp test : mGeneralTestNanoAppList) {
146             mNanoAppIdSet.add(test.getNanoAppBinary().getNanoAppId());
147         }
148     }
149 
150     @Override
onMessageFromNanoApp(ContextHubClient client, NanoAppMessage message)151     public void onMessageFromNanoApp(ContextHubClient client, NanoAppMessage message) {
152         if (mNanoAppIdSet.contains(message.getNanoAppId())) {
153             NanoAppMessage realMessage = hackMessageFromNanoApp(message);
154 
155             int messageType = realMessage.getMessageType();
156             ContextHubTestConstants.MessageType messageEnum =
157                     ContextHubTestConstants.MessageType.fromInt(messageType, "");
158             byte[] data = realMessage.getMessageBody();
159 
160             switch (messageEnum) {
161                 case INVALID_MESSAGE_TYPE:  // fall-through
162                 case FAILURE:  // fall-through
163                 case INTERNAL_FAILURE:
164                     // These are univeral failure conditions for all tests.
165                     // If they have data, it's expected to be an ASCII string.
166                     String errorString = new String(data, Charset.forName("US-ASCII"));
167                     fail(errorString);
168                     break;
169 
170                 case SKIPPED:
171                     // TODO: Use junit Assume
172                     String reason = new String(data, Charset.forName("US-ASCII"));
173                     Log.w(TAG, "SKIPPED " + ":" + reason);
174                     pass();
175                     break;
176 
177                 case SUCCESS:
178                     // This is a universal success for the test.  We ignore
179                     // 'data'.
180                     pass();
181                     break;
182 
183                 default:
184                     handleMessageFromNanoApp(message.getNanoAppId(), messageEnum, data);
185             }
186         }
187     }
188 
189     /**
190      * Should be invoked before run() is invoked to set up the test, e.g. in a @Before method.
191      */
init()192     public void init() {
193         Assert.assertFalse("init() must not be invoked when already initialized", mInitialized);
194 
195         mInitialized = true;
196 
197         // Initialize the CountDownLatch before run() since some nanoapps will start on load.
198         mCountDownLatch = new CountDownLatch(1);
199 
200         mContextHubClient = mContextHubManager.createClient(mContextHubInfo, this);
201 
202         for (GeneralTestNanoApp test : mGeneralTestNanoAppList) {
203             if (test.loadAtInit()) {
204                 ChreTestUtil.loadNanoAppAssertSuccess(mContextHubManager, mContextHubInfo,
205                         test.getNanoAppBinary());
206             }
207         }
208 
209         mErrorString.set(null);
210     }
211 
212     /**
213      * Run the test.
214      */
run(long timeoutSeconds)215     public void run(long timeoutSeconds) {
216         mThreadId = Thread.currentThread().getId();
217 
218         for (GeneralTestNanoApp test : mGeneralTestNanoAppList) {
219             if (test.loadAtInit() && test.sendStartMessage()) {
220                 sendMessageToNanoAppOrFail(test.getNanoAppBinary().getNanoAppId(),
221                         test.getTestName().asInt(), new byte[0] /* data */);
222             }
223         }
224 
225         boolean success = false;
226         try {
227             success = mCountDownLatch.await(timeoutSeconds, TimeUnit.SECONDS);
228         } catch (InterruptedException e) {
229             Assert.fail(e.getMessage());
230         }
231 
232         Assert.assertTrue("Test timed out", success);
233     }
234 
235     /**
236      * Invoke to indicate that the test has passed.
237      */
pass()238     public void pass() {
239         mCountDownLatch.countDown();
240     }
241 
242     /**
243      * Cleans up the test, should be invoked in e.g. @After method.
244      */
deinit()245     public void deinit() {
246         Assert.assertTrue("deinit() must be invoked after init()", mInitialized);
247 
248         // TODO: If the nanoapp aborted (i.e. test failed), wait for CHRE reset or nanoapp abort
249         // callback, and otherwise assert unload success.
250         for (GeneralTestNanoApp test : mGeneralTestNanoAppList) {
251             ChreTestUtil.unloadNanoApp(mContextHubManager, mContextHubInfo,
252                     test.getNanoAppBinary().getNanoAppId());
253         }
254 
255         mContextHubClient.close();
256         mContextHubClient = null;
257 
258         mInitialized = false;
259 
260         if (mErrorString.get() != null) {
261             Assert.fail(mErrorString.get());
262         }
263     }
264 
265     /**
266      * Sends a message to the test nanoapp.
267      *
268      * @param nanoAppId The 64-bit ID of the nanoapp to send the message to.
269      * @param type      The message type.
270      * @param data      The message payload.
271      */
sendMessageToNanoAppOrFail(long nanoAppId, int type, byte[] data)272     protected void sendMessageToNanoAppOrFail(long nanoAppId, int type, byte[] data) {
273         NanoAppMessage message = NanoAppMessage.createMessageToNanoApp(
274                 nanoAppId, type, data);
275 
276         int result = mContextHubClient.sendMessageToNanoApp(hackMessageToNanoApp(message));
277         if (result != ContextHubTransaction.RESULT_SUCCESS) {
278             fail("Failed to send message: result = " + result);
279         }
280     }
281 
282     /**
283      * @param errorMessage The error message to display
284      */
fail(String errorMessage)285     protected void fail(String errorMessage) {
286         assertTrue(errorMessage, false /* condition */);
287     }
288 
289     /**
290      * Semantics the same as Assert.assertEquals.
291      */
assertEquals(String errorMessage, T expected, T actual)292     protected <T> void assertEquals(String errorMessage, T expected, T actual) {
293         if (Thread.currentThread().getId() == mThreadId) {
294             Assert.assertEquals(errorMessage, expected, actual);
295         } else if ((expected == null && actual != null) || (expected != null && !expected.equals(
296                 actual))) {
297             mErrorString.set(errorMessage + ": " + expected + " != " + actual);
298             mCountDownLatch.countDown();
299         }
300     }
301 
302     /**
303      * Semantics the same as Assert.assertTrue.
304      */
assertTrue(String errorMessage, boolean condition)305     protected void assertTrue(String errorMessage, boolean condition) {
306         if (Thread.currentThread().getId() == mThreadId) {
307             Assert.assertTrue(errorMessage, condition);
308         } else if (!condition) {
309             mErrorString.set(errorMessage);
310             mCountDownLatch.countDown();
311         }
312     }
313 
314     /**
315      * Semantics are the same as Assert.assertFalse.
316      */
assertFalse(String errorMessage, boolean condition)317     protected void assertFalse(String errorMessage, boolean condition) {
318         assertTrue(errorMessage, !condition);
319     }
320 
getContextHubManager()321     protected ContextHubManager getContextHubManager() {
322         return mContextHubManager;
323     }
324 
getContextHubInfo()325     protected ContextHubInfo getContextHubInfo() {
326         return mContextHubInfo;
327     }
328 
329     /**
330      * Handles a message specific for a test.
331      *
332      * @param nanoAppId The 64-bit ID of the nanoapp sending the message.
333      * @param type      The message type.
334      * @param data      The message body.
335      */
handleMessageFromNanoApp( long nanoAppId, ContextHubTestConstants.MessageType type, byte[] data)336     protected abstract void handleMessageFromNanoApp(
337             long nanoAppId, ContextHubTestConstants.MessageType type, byte[] data);
338 
339     // TODO: Remove this hack
hackMessageToNanoApp(NanoAppMessage message)340     protected NanoAppMessage hackMessageToNanoApp(NanoAppMessage message) {
341         // For NYC, we are not able to assume that the messageType correctly
342         // makes it to the nanoapp.  So we put it, in little endian, as the
343         // first four bytes of the message.
344         byte[] origData = message.getMessageBody();
345         ByteBuffer newData = ByteBuffer.allocate(4 + origData.length);
346         newData.order(ByteOrder.LITTLE_ENDIAN);
347         newData.putInt(message.getMessageType());
348         newData.put(origData);
349         return NanoAppMessage.createMessageToNanoApp(
350                 message.getNanoAppId(), message.getMessageType(), newData.array());
351     }
352 
353     // TODO: Remove this hack
hackMessageFromNanoApp(NanoAppMessage message)354     protected NanoAppMessage hackMessageFromNanoApp(NanoAppMessage message) {
355         // For now, our nanohub HAL and JNI code end up not sending across the
356         // message type of the user correctly.  So our testing protocol hacks
357         // around this by putting the message type in the first four bytes of
358         // the data payload, in little endian.
359         ByteBuffer origData = ByteBuffer.wrap(message.getMessageBody());
360         origData.order(ByteOrder.LITTLE_ENDIAN);
361         int newMessageType = origData.getInt();
362         // The new data is the remainder of this array (which could be empty).
363         byte[] newData = new byte[origData.remaining()];
364         origData.get(newData);
365         return NanoAppMessage.createMessageFromNanoApp(
366                 message.getNanoAppId(), newMessageType, newData,
367                 message.isBroadcastMessage());
368     }
369 }
370