blob: f564df193a42292ed83157d8ac56d34b4f9d6ea9 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os.cts;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceTestCase;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ProcfsHostTests extends DeviceTestCase {
// We need a running test app to test /proc/[PID]/* files.
private static final String TEST_APP_PACKAGE = "android.os.procfs";
private static final String TEST_APP_CLASS = "ProcfsTest";
private static final String START_TEST_APP_COMMAND =
String.format(
"am start -W -a android.intent.action.MAIN -n %s/%s.%s",
TEST_APP_PACKAGE, TEST_APP_PACKAGE, TEST_APP_CLASS);
private static final String TEST_APP_LOG_REGEXP = "PID is (\\d+)";
private static final Pattern TEST_APP_LOG_PATTERN = Pattern.compile(TEST_APP_LOG_REGEXP);
private static final String PROC_STAT_PATH = "/proc/stat";
private static final String PROC_STAT_READ_COMMAND = "head -1 ";
// Verfies the first line of /proc/stat includes 'cpu' followed by 10 numbers.
// The 10th column was introduced in kernel version 2.6.33.
private static final String PROC_STAT_REGEXP = "cpu ( \\d+){10,10}";
private static final Pattern PROC_STAT_PATTERN = Pattern.compile(PROC_STAT_REGEXP);
// In Linux, a process's stat file (/proc/[PID]/stat) and a thread's (/proc/[PID]/task/[TID]/stat)
// share the same format. We want to verify these stat files include pid (a number), file name
// (a string in parentheses), and state (a character), followed by 41 or more numbers.
// The 44th column was introduced in kernel version 2.6.24.
private static final String PID_TID_STAT_REGEXP = "\\d+ \\(.*\\) [A-Za-z]( [\\d-]+){41,}";
private static final Pattern PID_TID_STAT_PATTERN = Pattern.compile(PID_TID_STAT_REGEXP);
// Interval in milliseconds between two sequential reads when checking whether a file is being
// updated.
private static final long UPDATE_READ_INTERVAL_MS = 100;
// Max time in milliseconds waiting for a file being update. If a file's content does not change
// during the period, it is not considered being actively updated.
private static final long UPDATE_MAX_WAIT_TIME_MS = 5000;
// A reference to the device under test, which gives us a handle to run commands.
private ITestDevice mDevice;
// Process ID of test app.
//
// Technically it doesn't need to be static in the master branch because a single object will be
// created to run different tests (calling various test* methods). However, this piece of code
// may be ported to older branches (e.g., mnc-dev) to test compatibility where a single object
// is created for each test. In these older branches it is useful to make this field static, so
// we don't have to kill and restart the app between tests.
private static int mTestAppPid = -1;
@Override
protected synchronized void setUp() throws Exception {
super.setUp();
mDevice = getDevice();
// Start the test app if not already.
if (mTestAppPid == -1) {
mTestAppPid = startTestApp();
}
}
/**
* Tests that host, as the shell user, can read /proc/stat file, the file is in a reasonable
* shape, and the file is being updated.
*
* @throws Exception
*/
public void testProcStat() throws Exception {
testFile(PROC_STAT_PATH, PROC_STAT_READ_COMMAND, PROC_STAT_PATTERN);
}
/**
* Tests that host, as the shell user, can read /proc/[PID]/stat file, the file is in a reasonable
* shape, and the file is being updated.
*
* @throws Exception
*/
public void testProcPidStat() throws Exception {
testFile("/proc/" + mTestAppPid + "/stat", "cat ", PID_TID_STAT_PATTERN);
}
/**
* Tests that host, as the shell user, can read /proc/[PID]/task/[TID]/stat files, and the files
* are in a reasonable shape. Also verifies there are more than one such files (a typical Android
* app easily has 10+ threads including those from Android runtime).
*
* <p>Note we are not testing whether these files are being updated because some Android runtime
* threads may be idling for a while so it is hard to test whether they are being updated within a
* limited time window (such as 'Profile Saver' thread in art/runtime/jit/profile_saver.h and
* 'JDWP' thread).
*
* @throws Exception
*/
public void testProcTidStat() throws Exception {
int[] tids = lookForTidsInProcess(mTestAppPid);
assertTrue("/proc/" + mTestAppPid + "/task/ includes < 2 threads", tids.length >= 2);
for (int tid : tids) {
readAndCheckFile(
"/proc/" + mTestAppPid + "/task/" + tid + "/stat", "cat ", PID_TID_STAT_PATTERN);
}
}
/**
* Tests that host, as the shell user, can read the file at the given absolute path by using the
* given read command, the file is in the expected format pattern, and the file is being updated.
*
* @throws Exception
*/
private void testFile(String absolutePath, String readCommand, Pattern pattern) throws Exception {
String content = readAndCheckFile(absolutePath, readCommand, pattern);
// Check the file is being updated.
long waitTime = 0;
while (waitTime < UPDATE_MAX_WAIT_TIME_MS) {
java.lang.Thread.sleep(UPDATE_READ_INTERVAL_MS);
waitTime += UPDATE_READ_INTERVAL_MS;
String newContent = readAndCheckFile(absolutePath, readCommand, pattern);
if (!newContent.equals(content)) {
return;
}
}
assertTrue(absolutePath + " not actively updated. Content: \"" + content + "\"", false);
}
/**
* Starts the test app and returns its process ID.
*
* @throws Exception
*/
private int startTestApp() throws Exception {
// Clear logcat.
mDevice.executeAdbCommand("logcat", "-c");
// Start the app activity and wait for it to complete.
String results = mDevice.executeShellCommand(START_TEST_APP_COMMAND);
// Dump logcat.
String logs =
mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", TEST_APP_CLASS + ":I", "*:S");
// Search for string contianing the process ID.
int pid = -1;
Scanner in = new Scanner(logs);
while (in.hasNextLine()) {
String line = in.nextLine();
if (line.startsWith("I/" + TEST_APP_CLASS)) {
Matcher m = TEST_APP_LOG_PATTERN.matcher(line.split(":")[1].trim());
if (m.matches()) {
pid = Integer.parseInt(m.group(1));
}
}
}
in.close();
// Assert test app's pid is captured from log.
assertTrue(
"Test app PID not captured. results = \"" + results + "\"; logs = \"" + logs + "\"",
pid > 0);
return pid;
}
/**
* Reads and returns the file content at the given absolute path by using the given read command,
* after ensuring it is in the expected pattern.
*
* @throws Exception
*/
private String readAndCheckFile(String absolutePath, String readCommand, Pattern pattern)
throws Exception {
String readResult = getDevice().executeShellCommand(readCommand + absolutePath);
assertNotNull("Unexpected empty file " + absolutePath, readResult);
readResult = readResult.trim();
assertTrue(
"Unexpected format of " + absolutePath + ": \"" + readResult + "\"",
pattern.matcher(readResult).matches());
return readResult;
}
/**
* Returns the thread IDs in a given process.
*
* @throws Exception
*/
private int[] lookForTidsInProcess(int pid) throws Exception {
String taskPath = "/proc/" + pid + "/task";
// Explicitly pass -1 to 'ls' to get one per line rather than relying on adb not allocating a
// tty.
String lsOutput = getDevice().executeShellCommand("ls -1 " + taskPath);
assertNotNull("Unexpected empty directory " + taskPath, lsOutput);
String[] threads = lsOutput.split("\\s+");
int[] tids = new int[threads.length];
for (int i = 0; i < threads.length; i++) {
tids[i] = Integer.parseInt(threads[i]);
}
return tids;
}
}