1 /*
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 
17 package com.android.internal.car.test;
18 
19 import static com.google.common.truth.Truth.assertWithMessage;
20 
21 import com.android.compatibility.common.util.CommonTestUtils;
22 import com.android.compatibility.common.util.PollingCheck;
23 import com.android.tradefed.log.LogUtil.CLog;
24 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
25 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
26 
27 import static org.junit.Assume.assumeTrue;
28 
29 import org.junit.After;
30 import org.junit.Before;
31 import org.junit.Test;
32 import org.junit.runner.RunWith;
33 
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.List;
37 import java.util.concurrent.atomic.AtomicReference;
38 
39 @RunWith(DeviceJUnit4ClassRunner.class)
40 public final class CarServiceCrashDumpTest extends BaseHostJUnit4Test {
41     private static final int DEFAULT_TIMEOUT_SEC = 20;
42     private static final long POLL_TIMEOUT_MS = 20000;
43     private static final String BUILD_TYPE_PROPERTY = "ro.build.type";
44 
45     // This must be in sync with WatchDog lib.
46     private static final List<String> HAL_INTERFACES_OF_INTEREST = Arrays.asList(
47             "android.hardware.audio@4.0::IDevicesFactory",
48             "android.hardware.audio@5.0::IDevicesFactory",
49             "android.hardware.audio@6.0::IDevicesFactory",
50             "android.hardware.audio@7.0::IDevicesFactory",
51             "android.hardware.biometrics.face@1.0::IBiometricsFace",
52             "android.hardware.biometrics.fingerprint@2.1::IBiometricsFingerprint",
53             "android.hardware.bluetooth@1.0::IBluetoothHci",
54             "android.hardware.camera.provider@2.4::ICameraProvider",
55             "android.hardware.gnss@1.0::IGnss",
56             "android.hardware.graphics.allocator@2.0::IAllocator",
57             "android.hardware.graphics.composer@2.1::IComposer",
58             "android.hardware.health@2.0::IHealth",
59             "android.hardware.light@2.0::ILight",
60             "android.hardware.media.c2@1.0::IComponentStore",
61             "android.hardware.media.omx@1.0::IOmx",
62             "android.hardware.media.omx@1.0::IOmxStore",
63             "android.hardware.neuralnetworks@1.0::IDevice",
64             "android.hardware.power.stats@1.0::IPowerStats",
65             "android.hardware.sensors@1.0::ISensors",
66             "android.hardware.sensors@2.0::ISensors",
67             "android.hardware.sensors@2.1::ISensors",
68             "android.hardware.vr@1.0::IVr",
69             "android.system.suspend@1.0::ISystemSuspend"
70     );
71 
72     // Which native processes to dump into dropbox's stack traces, must be in sync with Watchdog
73     // lib.
74     private static final String[] NATIVE_STACKS_OF_INTEREST = new String[] {
75         "/system/bin/audioserver",
76         "/system/bin/cameraserver",
77         "/system/bin/drmserver",
78         "/system/bin/keystore2",
79         "/system/bin/mediadrmserver",
80         "/system/bin/mediaserver",
81         "/system/bin/netd",
82         "/system/bin/sdcard",
83         "/system/bin/surfaceflinger",
84         "/system/bin/vold",
85         "media.extractor", // system/bin/mediaextractor
86         "media.metrics", // system/bin/mediametrics
87         "media.codec", // vendor/bin/hw/android.hardware.media.omx@1.0-service
88         "media.swcodec", // /apex/com.android.media.swcodec/bin/mediaswcodec
89         "media.transcoding", // Media transcoding service
90         "com.android.bluetooth",  // Bluetooth service
91         "/apex/com.android.os.statsd/bin/statsd",  // Stats daemon
92     };
93 
94     /**
95      * Executes the shell command and returns the output.
96      */
executeCommand(String command, Object... args)97     private String executeCommand(String command, Object... args) throws Exception {
98         String fullCommand = String.format(command, args);
99         return getDevice().executeShellCommand(fullCommand);
100     }
101 
102     /**
103      * Waits until the car service is ready.
104      */
waitForCarServiceReady()105     private void waitForCarServiceReady() throws Exception {
106         CommonTestUtils.waitUntil("timed out waiting for car service ",
107                 DEFAULT_TIMEOUT_SEC, () -> isCarServiceReady());
108     }
109 
isCarServiceReady()110     private boolean isCarServiceReady() {
111         String cmd = "service check car_service";
112         try {
113             String output = getDevice().executeShellCommand(cmd).strip();
114             return !output.endsWith("not found");
115         } catch (Exception e) {
116             CLog.w("%s failed: %s", cmd, e.getMessage());
117         }
118         return false;
119     }
120 
121     @Before
setUp()122     public void setUp() throws Exception {
123         executeCommand("logcat -c");
124     }
125 
126     /**
127      * Read the content of the dumped file.
128      */
getDumpFile()129     private String getDumpFile() throws Exception {
130         AtomicReference<String> log = new AtomicReference<>();
131         String dumpString = "ActivityManager: Dumping to ";
132         String doneDumpingString = "ActivityManager: Done dumping";
133         PollingCheck.check("dumpStackTrace not found in log", POLL_TIMEOUT_MS, () -> {
134             String logString = executeCommand("logcat -d");
135             if (logString.contains("ActivityManager: dumpStackTraces") && logString.contains(
136                     dumpString) && logString.contains(doneDumpingString)) {
137                 log.set(logString);
138                 return true;
139             }
140             return false;
141         });
142         String logString = log.get();
143         int start = logString.indexOf(dumpString) + dumpString.length();
144         int end = logString.indexOf("\n", start);
145         if (end == -1) {
146             end = logString.length();
147         }
148         return logString.substring(start, end);
149     }
150 
151     /**
152      * Get a list of PIDs for the interesting HALs that would be dumped.
153      */
getHalPids()154     private List<String> getHalPids() throws Exception {
155         String lshalResult = executeCommand("lshal -i -p");
156         List<String> pids = new ArrayList<String>();
157         int i = 0;
158         for (String line: lshalResult.split("\n")) {
159             line = line.strip();
160             if (line.equals("")) {
161                 // When we see an empty line, we stops the parsing.
162                 break;
163             }
164             if (i < 2) {
165                 // Skip the first two lines
166                 i++;
167                 continue;
168             }
169             String[] fields = line.split("\\s+");
170             for (String interestHal: HAL_INTERFACES_OF_INTEREST) {
171                 if (fields[0].contains(interestHal)) {
172                     pids.add(fields[1]);
173                     break;
174                 }
175             }
176             i++;
177         }
178         return pids;
179     }
180 
181     /**
182      * Get a list of PIDs for the native services that would be dumped.
183      */
getNativePids()184     private List<String> getNativePids() throws Exception {
185         List<String> pids = new ArrayList<String>();
186         for (String name: NATIVE_STACKS_OF_INTEREST) {
187             String pid = executeCommand(String.format("pidof %s", name)).strip();
188             if (!pid.equals("")) {
189                 pids.add(pid);
190             }
191         }
192         return pids;
193     }
194 
195     @Test
testCarServiceCrashDump()196     public void testCarServiceCrashDump() throws Exception {
197         String buildType = getDevice().getProperty("ro.build.type");
198         // Only run on userdebug devices.
199         assumeTrue(buildType.equals("userdebug") || buildType.equals("eng"));
200 
201         List<String> pids = new ArrayList<String>();
202 
203         getDevice().enableAdbRoot();
204 
205         String systemServerPid = executeCommand(String.format("pidof %s", "system_server")).strip();
206         assertWithMessage("system_service pid not empty").that(systemServerPid).isNotEmpty();
207         pids.add(systemServerPid);
208 
209         List<String> halPids = getHalPids();
210         pids.addAll(halPids);
211         assertWithMessage("hal pids").that(halPids.size() > 0).isTrue();
212 
213         List<String> nativePids = getNativePids();
214         pids.addAll(nativePids);
215         assertWithMessage("native pids").that(nativePids.size() > 0).isTrue();
216 
217         executeCommand("am crash --user 0 com.android.car");
218 
219         String dumpFile = getDumpFile();
220         assertWithMessage("dump file").that(dumpFile).isNotEmpty();
221 
222         String grepResult = executeCommand("cat %s", dumpFile);
223 
224         assertWithMessage("dumped content not empty").that(grepResult)
225                 .isNotEmpty();
226 
227         for (String pid : pids) {
228             assertWithMessage("dumped content contains interesting pid").that(grepResult)
229                     .contains(String.format("----- pid %s at", pid));
230         }
231     }
232 
233     @After
tearDown()234     public void tearDown() throws Exception {
235         waitForCarServiceReady();
236     }
237 }
238