• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 android.car.apitest;
18 
19 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
20 import static com.android.compatibility.common.util.TestUtils.BooleanSupplierWithThrow;
21 
22 import static com.google.common.truth.Truth.assertThat;
23 import static com.google.common.truth.Truth.assertWithMessage;
24 
25 import static org.junit.Assume.assumeFalse;
26 
27 import android.annotation.NonNull;
28 import android.app.ActivityManager;
29 import android.app.UiAutomation;
30 import android.car.Car;
31 import android.car.test.AbstractExpectableTestCase;
32 import android.car.test.ApiCheckerRule;
33 import android.content.ComponentName;
34 import android.content.Context;
35 import android.content.ServiceConnection;
36 import android.hardware.automotive.vehicle.InitialUserInfoRequestType;
37 import android.os.Build;
38 import android.os.IBinder;
39 import android.os.Looper;
40 import android.os.ParcelFileDescriptor;
41 import android.os.PowerManager;
42 import android.os.SystemClock;
43 import android.util.Log;
44 
45 import androidx.test.platform.app.InstrumentationRegistry;
46 
47 import org.junit.After;
48 import org.junit.Before;
49 import org.junit.Rule;
50 
51 import java.io.BufferedReader;
52 import java.io.ByteArrayOutputStream;
53 import java.io.FileDescriptor;
54 import java.io.FileInputStream;
55 import java.io.IOException;
56 import java.io.InputStream;
57 import java.io.InputStreamReader;
58 import java.util.Collection;
59 import java.util.concurrent.Semaphore;
60 import java.util.concurrent.TimeUnit;
61 
62 /**
63  * Base class for tests that don't need to connect to a {@link android.car.Car} object.
64  *
65  * <p>For tests that don't need a {@link android.car.Car} object, use
66  * {@link CarLessApiTestBase} instead.
67  */
68 public abstract class CarApiTestBase extends AbstractExpectableTestCase {
69 
70     private static final String TAG = CarApiTestBase.class.getSimpleName();
71 
72     protected static final long DEFAULT_WAIT_TIMEOUT_MS = 120_000;
73 
74     /**
75      * Constant used to wait blindly, when there is no condition that can be checked.
76      */
77     private static final int SUSPEND_TIMEOUT_MS = 5_000;
78 
79     /**
80      * How long to sleep (multiple times) while waiting for a condition.
81      */
82     private static final int SMALL_NAP_MS = 100;
83 
84     protected static final ReceiverTrackingContext sContext = new ReceiverTrackingContext(
85             InstrumentationRegistry.getInstrumentation().getTargetContext());
86 
87     private Car mCar;
88 
89     protected final DefaultServiceConnectionListener mConnectionListener =
90             new DefaultServiceConnectionListener();
91 
92     // TODO(b/242350638): temporary hack to allow subclasses to disable checks - should be removed
93     // when not needed anymore
94     private final ApiCheckerRule.Builder mApiCheckerRuleBuilder = new ApiCheckerRule.Builder();
95 
96     @Rule
97     public final ApiCheckerRule mApiCheckerRule;
98 
99     // TODO(b/242350638): temporary hack to allow subclasses to disable checks - should be removed
100     // when not needed anymore
CarApiTestBase()101     protected CarApiTestBase() {
102         configApiCheckerRule(mApiCheckerRuleBuilder);
103         mApiCheckerRule = mApiCheckerRuleBuilder.build();
104     }
105 
106     // TODO(b/242350638): temporary hack to allow subclasses to disable checks - should be removed
107     // when not needed anymore
configApiCheckerRule(ApiCheckerRule.Builder builder)108     protected void configApiCheckerRule(ApiCheckerRule.Builder builder) {
109         Log.v(TAG, "Good News, Everyone! Class " + getClass()
110                 + " doesn't override configApiCheckerRule()");
111     }
112 
113     @Before
setFixturesAndConnectToCar()114     public final void setFixturesAndConnectToCar() throws Exception {
115         Log.d(TAG, "setFixturesAndConnectToCar() for " + getTestName());
116 
117         mCar = Car.createCar(getContext(), mConnectionListener);
118         mCar.connect();
119         mConnectionListener.waitForConnection(DEFAULT_WAIT_TIMEOUT_MS);
120     }
121 
122     @Before
dontStopUserOnSwitch()123     public final void dontStopUserOnSwitch() throws Exception {
124         Log.d(TAG, "Calling am.setStopUserOnSwitch(false) for " + getTestName());
125         getContext().getSystemService(ActivityManager.class)
126                 .setStopUserOnSwitch(ActivityManager.STOP_USER_ON_SWITCH_FALSE);
127     }
128 
129     @After
disconnectCar()130     public final void disconnectCar() throws Exception {
131         if (mCar == null) {
132             Log.wtf(TAG, "no mCar on " + getTestName() + ".tearDown()");
133             return;
134         }
135         mCar.disconnect();
136     }
137 
138     @After
resetStopUserOnSwitch()139     public final void resetStopUserOnSwitch() throws Exception {
140         Log.d(TAG, "Calling am.setStopUserOnSwitch(default) for " + getTestName());
141         getContext().getSystemService(ActivityManager.class)
142                 .setStopUserOnSwitch(ActivityManager.STOP_USER_ON_SWITCH_DEFAULT);
143     }
144 
145     @After
checkReceiversUnregisters()146     public final void checkReceiversUnregisters() {
147         Collection<String> receivers = sContext.getReceiversInfo();
148         Log.d(TAG, "Checking if all receivers were unregistered.");
149 
150         assertWithMessage("Broadcast receivers that are not unregistered: %s", receivers)
151                 .that(receivers).isEmpty();
152     }
153 
getCar()154     protected Car getCar() {
155         return mCar;
156     }
157 
getContext()158     protected final Context getContext() {
159         return sContext;
160     }
161 
162     @SuppressWarnings("TypeParameterUnusedInFormals") // error prone complains about returning <T>
getCarService(@onNull String serviceName)163     protected final <T> T getCarService(@NonNull String serviceName) {
164         assertThat(serviceName).isNotNull();
165         Object service = mCar.getCarManager(serviceName);
166         assertWithMessage("Could not get service %s", serviceName).that(service).isNotNull();
167 
168         @SuppressWarnings("unchecked")
169         T castService = (T) service;
170         return castService;
171     }
172 
assertMainThread()173     protected static void assertMainThread() {
174         assertThat(Looper.getMainLooper().isCurrentThread()).isTrue();
175     }
176 
177     protected static final class DefaultServiceConnectionListener implements ServiceConnection {
178         private final Semaphore mConnectionWait = new Semaphore(0);
179 
waitForConnection(long timeoutMs)180         public void waitForConnection(long timeoutMs) throws InterruptedException {
181             mConnectionWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS);
182         }
183 
184         @Override
onServiceConnected(ComponentName name, IBinder service)185         public void onServiceConnected(ComponentName name, IBinder service) {
186             assertMainThread();
187             mConnectionWait.release();
188         }
189 
190         @Override
onServiceDisconnected(ComponentName name)191         public void onServiceDisconnected(ComponentName name) {
192             assertMainThread();
193             fail("Car service crashed");
194         }
195     }
196 
suspendToRamAndResume()197     protected static void suspendToRamAndResume()
198             throws Exception {
199         Log.d(TAG, "Emulate suspend to RAM and resume");
200         try {
201             Log.d(TAG, "Disabling background users starting on garage mode");
202             runShellCommand("cmd car_service set-start-bg-users-on-garage-mode false");
203 
204             PowerManager powerManager = sContext.getSystemService(PowerManager.class);
205             // clear log
206             runShellCommand("logcat -b all -c");
207             // We use a simulated suspend because physically suspended devices cannot be woken up by
208             // a shell command.
209             runShellCommand("cmd car_service suspend --simulate --skip-garagemode "
210                     + "--wakeup-after 3");
211             // Check for suspend success
212             waitUntil("screen is still on after suspend",
213                     SUSPEND_TIMEOUT_MS, () -> !powerManager.isScreenOn());
214 
215             // The device will resume after 3 seconds.
216             waitForLogcatMessage("logcat -b events", "car_user_svc_initial_user_info_req_complete: "
217                     + InitialUserInfoRequestType.RESUME, 60_000);
218         } catch (Exception e) {
219             runShellCommand("cmd car_service set-start-bg-users-on-garage-mode true");
220         }
221     }
222 
223     /**
224      * Wait for a particular logcat message.
225      *
226      * @param cmd is logcat command with buffer or filters
227      * @param match is the string to be found in the log
228      * @param timeoutMs for which call should wait for the match
229      */
waitForLogcatMessage(String cmd, String match, int timeoutMs)230     protected static void waitForLogcatMessage(String cmd, String match, int timeoutMs) {
231         Log.d(TAG, "waiting for logcat match: " + match);
232         long startTime = SystemClock.elapsedRealtime();
233         UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
234         ParcelFileDescriptor output = automation.executeShellCommand(cmd);
235         FileDescriptor fd = output.getFileDescriptor();
236         FileInputStream fileInputStream = new FileInputStream(fd);
237         try (BufferedReader bufferedReader = new BufferedReader(
238                 new InputStreamReader(fileInputStream))) {
239             String line;
240             while ((line = bufferedReader.readLine()) != null) {
241                 if (line.contains(match)) {
242                     Log.d(TAG, "match found in "
243                             + (SystemClock.elapsedRealtime() - startTime) + " ms");
244                     break;
245                 }
246                 if ((SystemClock.elapsedRealtime() - startTime) > timeoutMs) {
247                     fail("logcat message(%s) not found in %d ms", match, timeoutMs);
248                 }
249             }
250         } catch (IOException e) {
251             fail("match (%s) was not found, IO exception: %s", match, e);
252         }
253     }
254 
waitUntil(String msg, long timeoutMs, BooleanSupplierWithThrow condition)255     protected static boolean waitUntil(String msg, long timeoutMs,
256             BooleanSupplierWithThrow condition) {
257         long deadline = SystemClock.elapsedRealtime() + timeoutMs;
258         do {
259             try {
260                 if (condition.getAsBoolean()) {
261                     return true;
262                 }
263             } catch (Exception e) {
264                 Log.e(TAG, "Exception in waitUntil: " + msg);
265                 throw new RuntimeException(e);
266             }
267             SystemClock.sleep(SMALL_NAP_MS);
268         } while (SystemClock.elapsedRealtime() < deadline);
269 
270         fail("%s after %d ms", msg, timeoutMs);
271         return false;
272     }
273 
274     // TODO(b/250914846): Clean this up once the investigation is done.
275     // Same as waitUntil except for not failing the test.
waitUntilNoFail(long timeoutMs, BooleanSupplierWithThrow condition)276     protected static boolean waitUntilNoFail(long timeoutMs,
277             BooleanSupplierWithThrow condition) {
278         long deadline = SystemClock.elapsedRealtime() + timeoutMs;
279         do {
280             try {
281                 if (condition.getAsBoolean()) {
282                     return true;
283                 }
284             } catch (Exception e) {
285                 Log.e(TAG, "Exception in waitUntilNoFail");
286                 throw new RuntimeException(e);
287             }
288             SystemClock.sleep(SMALL_NAP_MS);
289         } while (SystemClock.elapsedRealtime() < deadline);
290 
291         return false;
292     }
293 
requireNonUserBuild()294     protected void requireNonUserBuild() {
295         assumeFalse("Requires Shell commands that are not available on user builds", Build.IS_USER);
296     }
297 
getTestName()298     protected String getTestName() {
299         return getClass().getSimpleName() + "." + mApiCheckerRule.getTestMethodName();
300     }
301 
fail(String format, Object...args)302     protected static void fail(String format, Object...args) {
303         String message = String.format(format, args);
304         Log.e(TAG, "test failed: " + message);
305         org.junit.Assert.fail(message);
306     }
307 
executeShellCommand(String commandFormat, Object... args)308     protected static String executeShellCommand(String commandFormat, Object... args)
309             throws IOException {
310         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
311         return executeShellCommand(uiAutomation, commandFormat, args);
312     }
313 
executeShellCommand(UiAutomation uiAutomation, String commandFormat, Object... args)314     private static String executeShellCommand(UiAutomation uiAutomation, String commandFormat,
315             Object... args) throws IOException {
316         ParcelFileDescriptor stdout = uiAutomation.executeShellCommand(
317                 String.format(commandFormat, args));
318         try (InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(stdout)) {
319             ByteArrayOutputStream result = new ByteArrayOutputStream();
320             byte[] buffer = new byte[1024];
321             int length;
322             while ((length = inputStream.read(buffer)) != -1) {
323                 result.write(buffer, 0, length);
324             }
325             return result.toString("UTF-8");
326         }
327     }
328 }
329