xref: /aosp_15_r20/system/chre/java/test/utils/src/com/google/android/utils/chre/ChreTestUtil.java (revision 84e339476a462649f82315436d70fd732297a399)
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 
17 package com.google.android.utils.chre;
18 
19 import static com.google.common.truth.Truth.assertWithMessage;
20 
21 import android.app.Instrumentation;
22 import android.content.Context;
23 import android.hardware.location.ContextHubInfo;
24 import android.hardware.location.ContextHubManager;
25 import android.hardware.location.ContextHubTransaction;
26 import android.hardware.location.NanoAppBinary;
27 import android.hardware.location.NanoAppState;
28 import android.os.ParcelFileDescriptor;
29 
30 import androidx.test.InstrumentationRegistry;
31 
32 import org.junit.Assert;
33 
34 import java.io.BufferedReader;
35 import java.io.FileInputStream;
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.io.InputStreamReader;
39 import java.nio.charset.StandardCharsets;
40 import java.util.List;
41 import java.util.concurrent.CountDownLatch;
42 import java.util.concurrent.TimeUnit;
43 import java.util.concurrent.TimeoutException;
44 
45 /**
46  * A set of helper functions for PTS CHRE tests.
47  */
48 public class ChreTestUtil {
49     // Various timeouts for Context Hub operations.
50     private static final long TIMEOUT_LOAD_NANOAPP_SECONDS = 5;
51     private static final long TIMEOUT_UNLOAD_NANOAPP_SECONDS = 5;
52     private static final long QUERY_NANOAPPS_TIMEOUT_SECONDS = 5;
53 
54     /**
55      * Read the nanoapp to an InputStream object.
56      *
57      * @param context  the Context to find the asset resources
58      * @param fileName the fileName of the nanoapp
59      * @return the InputStream of the nanoapp
60      */
getNanoAppInputStream(Context context, String fileName)61     public static InputStream getNanoAppInputStream(Context context, String fileName) {
62         InputStream inputStream = null;
63         try {
64             inputStream = context.getAssets().open(fileName);
65         } catch (IOException e) {
66             Assert.fail("Could not find asset " + fileName + ": " + e.toString());
67         }
68         return inputStream;
69     }
70 
71     /**
72      * Creates a NanoAppBinary object from the nanoapp fileName.
73      *
74      * @param fileName the fileName of the nanoapp
75      * @return the NanoAppBinary object
76      */
createNanoAppBinary(String fileName)77     public static NanoAppBinary createNanoAppBinary(String fileName) {
78         Context context = InstrumentationRegistry.getTargetContext();
79 
80         InputStream stream = getNanoAppInputStream(context, fileName);
81         byte[] binary = null;
82         try {
83             binary = new byte[stream.available()];
84             stream.read(binary);
85         } catch (IOException e) {
86             Assert.fail("IOException while reading binary for " + fileName + ": " + e.getMessage());
87         }
88 
89         return new NanoAppBinary(binary);
90     }
91 
92     /**
93      * Loads a nanoapp.
94      *
95      * @param manager       The ContextHubManager to use to load the nanoapp.
96      * @param info          The ContextHubInfo describing the Context Hub to load the nanoapp to.
97      * @param nanoAppBinary The nanoapp binary to load.
98      * @return true if the load succeeded.
99      */
loadNanoApp( ContextHubManager manager, ContextHubInfo info, NanoAppBinary nanoAppBinary)100     public static boolean loadNanoApp(
101             ContextHubManager manager, ContextHubInfo info, NanoAppBinary nanoAppBinary) {
102         ContextHubTransaction<Void> txn = manager.loadNanoApp(info, nanoAppBinary);
103         ContextHubTransaction.Response<Void> resp = null;
104         try {
105             resp = txn.waitForResponse(TIMEOUT_LOAD_NANOAPP_SECONDS, TimeUnit.SECONDS);
106         } catch (TimeoutException | InterruptedException e) {
107             Assert.fail(e.getMessage());
108         }
109 
110         return resp != null && resp.getResult() == ContextHubTransaction.RESULT_SUCCESS;
111     }
112 
113     /**
114      * Same as loadNanoApp(), but asserts that it succeeds.
115      */
loadNanoAppAssertSuccess( ContextHubManager manager, ContextHubInfo info, NanoAppBinary nanoAppBinary)116     public static void loadNanoAppAssertSuccess(
117             ContextHubManager manager, ContextHubInfo info, NanoAppBinary nanoAppBinary) {
118         if (!loadNanoApp(manager, info, nanoAppBinary)) {
119             Assert.fail("Failed to load nanoapp");
120         }
121     }
122 
123     /**
124      * Unloads a nanoapp.
125      *
126      * @param manager   The ContextHubManager to use to unload the nanoapp.
127      * @param info      The ContextHubInfo describing the Context Hub to unload the nanoapp from.
128      * @param nanoAppId The 64-bit ID of the nanoapp to unload.
129      * @return true if the unload succeeded.
130      */
unloadNanoApp( ContextHubManager manager, ContextHubInfo info, long nanoAppId)131     public static boolean unloadNanoApp(
132             ContextHubManager manager, ContextHubInfo info, long nanoAppId) {
133         ContextHubTransaction<Void> txn = manager.unloadNanoApp(info, nanoAppId);
134         ContextHubTransaction.Response<Void> resp = null;
135         try {
136             resp = txn.waitForResponse(TIMEOUT_UNLOAD_NANOAPP_SECONDS, TimeUnit.SECONDS);
137         } catch (TimeoutException | InterruptedException e) {
138             Assert.fail(e.getMessage());
139         }
140 
141         return resp != null && resp.getResult() == ContextHubTransaction.RESULT_SUCCESS;
142     }
143     /**
144      * Same as unloadNanoApp(), but asserts that it succeeds.
145      */
unloadNanoAppAssertSuccess( ContextHubManager manager, ContextHubInfo info, long nanoAppId)146     public static void unloadNanoAppAssertSuccess(
147             ContextHubManager manager, ContextHubInfo info, long nanoAppId) {
148         if (!unloadNanoApp(manager, info, nanoAppId)) {
149             Assert.fail("Failed to unload nanoapp");
150         }
151     }
152 
153     /**
154      * Executes a given shell command.
155      *
156      * @param instrumentation The instrumentation to use.
157      * @param command         The shell command to execute.
158      * @return The string output.
159      */
executeShellCommand(Instrumentation instrumentation, String command)160     public static String executeShellCommand(Instrumentation instrumentation, String command) {
161         final ParcelFileDescriptor pfd = instrumentation.getUiAutomation()
162                 .executeShellCommand(command);
163         try (InputStream in = new FileInputStream(pfd.getFileDescriptor())) {
164             return readFromInputStream(in);
165         } catch (Exception e) {
166             Assert.fail(e.getMessage());
167         } finally {
168             closeOrAssert(pfd);
169         }
170         return null;
171     }
172 
173     /**
174      * Executes a given shell command using the app context rather than the shells so that the app's
175      * permissions are used.
176      *
177      * @param command         The shell command to execute.
178      * @return The string output.
179      */
executeShellCommandWithAppPerms(String command)180     public static String executeShellCommandWithAppPerms(String command) throws Exception {
181         final Process process = Runtime.getRuntime().exec(command);
182         process.waitFor();
183         return readFromInputStream(process.getInputStream());
184     }
185 
186     /**
187      * @param input The string input of an integer.
188      * @return The converted integer.
189      */
convertToIntegerOrFail(String input)190     public static int convertToIntegerOrFail(String input) {
191         try {
192             return Integer.parseInt(input);
193         } catch (NumberFormatException e) {
194             Assert.fail(e.getMessage());
195         }
196 
197         return -1;
198     }
199 
200     /**
201      * @param input The string input of an integer.
202      * @return The converted integer.
203      */
convertToIntegerOrReturnZero(String input)204     public static int convertToIntegerOrReturnZero(String input) {
205         try {
206             return Integer.parseInt(input);
207         } catch (NumberFormatException e) {
208             return 0;
209         }
210     }
211 
212     /**
213      * Get all the nanoapps currently loaded on device.
214      *
215      * @return The nanoapps loaded currently.
216      */
queryNanoAppsAssertSuccess( ContextHubManager contextHubManager, ContextHubInfo contextHubInfo)217     public static List<NanoAppState> queryNanoAppsAssertSuccess(
218             ContextHubManager contextHubManager, ContextHubInfo contextHubInfo) {
219         ContextHubTransaction<List<NanoAppState>> transaction =
220                 contextHubManager.queryNanoApps(contextHubInfo);
221         assertTransactionSuccessSync(transaction, QUERY_NANOAPPS_TIMEOUT_SECONDS);
222         ContextHubTransaction.Response<List<NanoAppState>> response = null;
223         try {
224             response = transaction.waitForResponse(QUERY_NANOAPPS_TIMEOUT_SECONDS,
225                     TimeUnit.SECONDS);
226         } catch (InterruptedException e) {
227             Assert.fail("InterruptedException while getting query response");
228         } catch (TimeoutException e) {
229             Assert.fail("TimeoutException while getting query response");
230         }
231         return response.getContents();
232     }
233 
234     /**
235      * Queries for the nanoapp version.
236      *
237      * @param contextHubManager The ContextHubManager to use.
238      * @param contextHubInfo    The ContextHubInfo describing the Context Hub to query.
239      * @param nanoAppId         The ID of the nanoapp to get the version for.
240      * @return The nanoapp version.
241      */
getNanoAppVersion(ContextHubManager contextHubManager, ContextHubInfo contextHubInfo, long nanoAppId)242     public static int getNanoAppVersion(ContextHubManager contextHubManager,
243             ContextHubInfo contextHubInfo, long nanoAppId) {
244         List<NanoAppState> stateList = queryNanoAppsAssertSuccess(contextHubManager,
245                 contextHubInfo);
246         for (NanoAppState state : stateList) {
247             if (state.getNanoAppId() == nanoAppId) {
248                 return (int) state.getNanoAppVersion();
249             }
250         }
251 
252         Assert.fail("Could not query for nanoapp with ID 0x" + Long.toHexString(nanoAppId));
253         return -1;
254     }
255 
256     /**
257      * @param closeable The object to close.
258      */
closeOrAssert(AutoCloseable closeable)259     private static void closeOrAssert(AutoCloseable closeable) {
260         try {
261             closeable.close();
262         } catch (Exception e) {
263             Assert.fail(e.getMessage());
264         }
265     }
266 
readFromInputStream(InputStream in)267     private static String readFromInputStream(InputStream in) throws Exception {
268         StringBuilder out = new StringBuilder();
269         BufferedReader br = new BufferedReader(new InputStreamReader(in,
270                 StandardCharsets.UTF_8));
271         String str = null;
272         while ((str = br.readLine()) != null) {
273             out.append(str);
274         }
275 
276         closeOrAssert(br);
277         return out.toString();
278     }
279 
280     /**
281      * Assert that the context hub transaction gets a successful response.
282      *
283      * @param transaction      The context hub transaction
284      * @param timeoutInSeconds The timeout while waiting for the transaction response in seconds
285      */
assertTransactionSuccessSync( ContextHubTransaction<?> transaction, long timeoutInSeconds)286     private static void assertTransactionSuccessSync(
287             ContextHubTransaction<?> transaction, long timeoutInSeconds) throws AssertionError {
288         if (transaction == null) {
289             Assert.fail("ContextHubTransaction cannot be null");
290         }
291 
292         String type = ContextHubTransaction.typeToString(transaction.getType(),
293                 true /* upperCase */);
294         ContextHubTransaction.Response<?> response = null;
295         try {
296             response = transaction.waitForResponse(timeoutInSeconds, TimeUnit.SECONDS);
297         } catch (InterruptedException e) {
298             Assert.fail("InterruptedException while waiting for " + type + " transaction");
299         } catch (TimeoutException e) {
300             Assert.fail("TimeoutException while waiting for " + type + " transaction");
301         }
302 
303         Assert.assertTrue(type + " transaction failed with error code " + response.getResult(),
304                 response.getResult() == ContextHubTransaction.RESULT_SUCCESS);
305     }
306 
assertLatchCountedDown(CountDownLatch latch, long timeoutThreshold)307     public static void assertLatchCountedDown(CountDownLatch latch, long timeoutThreshold)
308             throws InterruptedException {
309         boolean isCountedDown = latch.await(timeoutThreshold, TimeUnit.SECONDS);
310         assertWithMessage(
311                         "Waiting for latch to count down timeout after %s seconds",
312                         timeoutThreshold)
313                 .that(isCountedDown)
314                 .isTrue();
315     }
316 }
317