1 /* 2 * Copyright (C) 2016 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.os.cts; 18 19 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; 20 import com.android.tradefed.build.IBuildInfo; 21 import com.android.tradefed.device.ITestDevice; 22 import com.android.tradefed.testtype.DeviceTestCase; 23 import com.android.tradefed.testtype.IBuildReceiver; 24 25 import java.io.File; 26 import java.util.Scanner; 27 import java.util.regex.Matcher; 28 import java.util.regex.Pattern; 29 30 public class ProcfsHostTests extends DeviceTestCase implements IBuildReceiver { 31 // We need a running test app to test /proc/[PID]/* files. 32 private static final String TEST_APP_PACKAGE = "android.os.procfs"; 33 private static final String TEST_APP_CLASS = "ProcfsTest"; 34 private static final String APK_NAME = "CtsHostProcfsTestApp.apk"; 35 private static final String START_TEST_APP_COMMAND = 36 String.format( 37 "am start -W -a android.intent.action.MAIN -n %s/%s.%s", 38 TEST_APP_PACKAGE, TEST_APP_PACKAGE, TEST_APP_CLASS); 39 private static final String TEST_APP_LOG_REGEXP = "PID is (\\d+)"; 40 private static final Pattern TEST_APP_LOG_PATTERN = Pattern.compile(TEST_APP_LOG_REGEXP); 41 42 private static final String PROC_STAT_PATH = "/proc/stat"; 43 private static final String PROC_STAT_READ_COMMAND = "head -1 "; 44 // Verfies the first line of /proc/stat includes 'cpu' followed by 10 numbers. 45 // The 10th column was introduced in kernel version 2.6.33. 46 private static final String PROC_STAT_REGEXP = "cpu ( \\d+){10,10}"; 47 private static final Pattern PROC_STAT_PATTERN = Pattern.compile(PROC_STAT_REGEXP); 48 49 // In Linux, a process's stat file (/proc/[PID]/stat) and a thread's (/proc/[PID]/task/[TID]/stat) 50 // share the same format. We want to verify these stat files include pid (a number), file name 51 // (a string in parentheses), and state (a character), followed by 41 or more numbers. 52 // The 44th column was introduced in kernel version 2.6.24. 53 private static final String PID_TID_STAT_REGEXP = "\\d+ \\(.*\\) [A-Za-z]( [\\d-]+){41,}"; 54 private static final Pattern PID_TID_STAT_PATTERN = Pattern.compile(PID_TID_STAT_REGEXP); 55 56 // Interval in milliseconds between two sequential reads when checking whether a file is being 57 // updated. 58 private static final long UPDATE_READ_INTERVAL_MS = 100; 59 // Max time in milliseconds waiting for a file being update. If a file's content does not change 60 // during the period, it is not considered being actively updated. 61 private static final long UPDATE_MAX_WAIT_TIME_MS = 5000; 62 63 // A reference to the device under test, which gives us a handle to run commands. 64 private ITestDevice mDevice; 65 66 private int mTestAppPid = -1; 67 68 private IBuildInfo mBuild; 69 70 @Override setBuild(IBuildInfo buildInfo)71 public void setBuild(IBuildInfo buildInfo) { 72 mBuild = buildInfo; 73 } 74 75 @Override setUp()76 protected synchronized void setUp() throws Exception { 77 super.setUp(); 78 mDevice = getDevice(); 79 mTestAppPid = startTestApp(); 80 } 81 82 /** 83 * Tests that host, as the shell user, can read /proc/stat file, the file is in a reasonable 84 * shape, and the file is being updated. 85 * 86 * @throws Exception 87 */ testProcStat()88 public void testProcStat() throws Exception { 89 testFile(PROC_STAT_PATH, PROC_STAT_READ_COMMAND, PROC_STAT_PATTERN); 90 } 91 92 /** 93 * Tests that host, as the shell user, can read /proc/[PID]/stat file, the file is in a reasonable 94 * shape, and the file is being updated. 95 * 96 * @throws Exception 97 */ testProcPidStat()98 public void testProcPidStat() throws Exception { 99 testFile("/proc/" + mTestAppPid + "/stat", "cat ", PID_TID_STAT_PATTERN); 100 } 101 102 /** 103 * Tests that host, as the shell user, can read /proc/[PID]/task/[TID]/stat files, and the files 104 * are in a reasonable shape. Also verifies there are more than one such files (a typical Android 105 * app easily has 10+ threads including those from Android runtime). 106 * 107 * <p>Note we are not testing whether these files are being updated because some Android runtime 108 * threads may be idling for a while so it is hard to test whether they are being updated within a 109 * limited time window (such as 'Profile Saver' thread in art/runtime/jit/profile_saver.h and 110 * 'JDWP' thread). 111 * 112 * @throws Exception 113 */ testProcTidStat()114 public void testProcTidStat() throws Exception { 115 int[] tids = lookForTidsInProcess(mTestAppPid); 116 assertTrue("/proc/" + mTestAppPid + "/task/ includes < 2 threads", tids.length >= 2); 117 for (int tid : tids) { 118 readAndCheckFile( 119 "/proc/" + mTestAppPid + "/task/" + tid + "/stat", "cat ", PID_TID_STAT_PATTERN); 120 } 121 } 122 123 /** 124 * Tests that host, as the shell user, can read the file at the given absolute path by using the 125 * given read command, the file is in the expected format pattern, and the file is being updated. 126 * 127 * @throws Exception 128 */ testFile(String absolutePath, String readCommand, Pattern pattern)129 private void testFile(String absolutePath, String readCommand, Pattern pattern) throws Exception { 130 String content = readAndCheckFile(absolutePath, readCommand, pattern); 131 132 // Check the file is being updated. 133 long waitTime = 0; 134 while (waitTime < UPDATE_MAX_WAIT_TIME_MS) { 135 java.lang.Thread.sleep(UPDATE_READ_INTERVAL_MS); 136 waitTime += UPDATE_READ_INTERVAL_MS; 137 String newContent = readAndCheckFile(absolutePath, readCommand, pattern); 138 if (!newContent.equals(content)) { 139 return; 140 } 141 } 142 assertTrue(absolutePath + " not actively updated. Content: \"" + content + "\"", false); 143 } 144 145 /** 146 * Starts the test app and returns its process ID. 147 * 148 * @throws Exception 149 */ startTestApp()150 private int startTestApp() throws Exception { 151 152 // Uninstall+install the app 153 mDevice.uninstallPackage(TEST_APP_PACKAGE); 154 CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild); 155 File app = buildHelper.getTestFile(APK_NAME); 156 String[] options = {}; 157 mDevice.installPackage(app, false, options); 158 159 // Clear logcat. 160 mDevice.executeAdbCommand("logcat", "-c"); 161 // Start the app activity and wait for it to complete. 162 String results = mDevice.executeShellCommand(START_TEST_APP_COMMAND); 163 // Dump logcat. 164 String logs = 165 mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", TEST_APP_CLASS + ":I", "*:S"); 166 // Search for string contianing the process ID. 167 int pid = -1; 168 Scanner in = new Scanner(logs); 169 while (in.hasNextLine()) { 170 String line = in.nextLine(); 171 if (line.startsWith("I/" + TEST_APP_CLASS)) { 172 Matcher m = TEST_APP_LOG_PATTERN.matcher(line.split(":")[1].trim()); 173 if (m.matches()) { 174 pid = Integer.parseInt(m.group(1)); 175 } 176 } 177 } 178 in.close(); 179 // Assert test app's pid is captured from log. 180 assertTrue( 181 "Test app PID not captured. results = \"" + results + "\"; logs = \"" + logs + "\"", 182 pid > 0); 183 return pid; 184 } 185 186 /** 187 * Reads and returns the file content at the given absolute path by using the given read command, 188 * after ensuring it is in the expected pattern. 189 * 190 * @throws Exception 191 */ readAndCheckFile(String absolutePath, String readCommand, Pattern pattern)192 private String readAndCheckFile(String absolutePath, String readCommand, Pattern pattern) 193 throws Exception { 194 String readResult = getDevice().executeShellCommand(readCommand + absolutePath); 195 assertNotNull("Unexpected empty file " + absolutePath, readResult); 196 readResult = readResult.trim(); 197 assertTrue( 198 "Unexpected format of " + absolutePath + ": \"" + readResult + "\"", 199 pattern.matcher(readResult).matches()); 200 return readResult; 201 } 202 203 /** 204 * Returns the thread IDs in a given process. 205 * 206 * @throws Exception 207 */ lookForTidsInProcess(int pid)208 private int[] lookForTidsInProcess(int pid) throws Exception { 209 String taskPath = "/proc/" + pid + "/task"; 210 // Explicitly pass -1 to 'ls' to get one per line rather than relying on adb not allocating a 211 // tty. 212 String lsOutput = getDevice().executeShellCommand("ls -1 " + taskPath); 213 assertNotNull("Unexpected empty directory " + taskPath, lsOutput); 214 215 String[] threads = lsOutput.split("\\s+"); 216 int[] tids = new int[threads.length]; 217 for (int i = 0; i < threads.length; i++) { 218 tids[i] = Integer.parseInt(threads[i]); 219 } 220 return tids; 221 } 222 } 223