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