Home | History | Annotate | Download | only in cts
      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
     71   public void setBuild(IBuildInfo buildInfo) {
     72       mBuild = buildInfo;
     73   }
     74 
     75   @Override
     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    */
     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    */
     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    */
    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    */
    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    */
    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    */
    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    */
    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