Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2008 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  *      httprunPackage://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.cts;
     18 
     19 import com.android.cts.HostConfig.CaseRepository;
     20 import com.android.cts.HostConfig.PlanRepository;
     21 import com.android.ddmlib.AndroidDebugBridge;
     22 
     23 import org.xml.sax.SAXException;
     24 
     25 import java.io.BufferedWriter;
     26 import java.io.File;
     27 import java.io.FileNotFoundException;
     28 import java.io.FileReader;
     29 import java.io.FileWriter;
     30 import java.io.IOException;
     31 import java.security.NoSuchAlgorithmException;
     32 import java.util.ArrayList;
     33 import java.util.Collection;
     34 import java.util.HashMap;
     35 
     36 import javax.xml.parsers.ParserConfigurationException;
     37 import javax.xml.transform.TransformerException;
     38 import javax.xml.transform.TransformerFactoryConfigurationError;
     39 
     40 /**
     41  * Act as the host for the device connections, also provides management of
     42  * sessions.
     43  */
     44 public class TestHost extends XMLResourceHandler implements SessionObserver {
     45     public static final String TEMP_PLAN_NAME = "tempPlan";
     46 
     47     enum ActionType {
     48         RUN_SINGLE_TEST, RUN_SINGLE_JAVA_PACKAGE, START_NEW_SESSION, RESUME_SESSION
     49     }
     50     /**
     51      * Definition of the modes the TestHost will run with.
     52      * <ul>
     53      *    <li> RUN: For this mode, the TestHost will run the plan or
     54      *              package directly without starting the UI.
     55      *    <li> CONSOLE: For this mode, the TestHost will start the UI
     56      *                  and wait for input from user.
     57      * </ul>
     58      */
     59     enum MODE {
     60         UNINITIALIZED, RUN, CONSOLE
     61     }
     62 
     63     static private ArrayList<TestSession> sSessions = new ArrayList<TestSession>();
     64     static private DeviceManager sDeviceManager = new DeviceManager();
     65     static private Object sTestSessionSync = new Object();
     66 
     67     static private ConsoleUi sConsoleUi;
     68 
     69     static private HostConfig sConfig;
     70 
     71     private static TestHost sInstance;
     72     static MODE sMode = MODE.UNINITIALIZED;
     73     private static boolean sQuick = false;
     74 
     75     public static void main(final String[] mainArgs) {
     76         CUIOutputStream.println("Android CTS version " + Version.asString());
     77 
     78         if (HostLock.lock() == false) {
     79             Log.e("Error: CTS is being used at the moment."
     80                     + " No more than one CTS instance is allowed simultaneously", null);
     81             exit();
     82         }
     83 
     84         sDeviceManager.initAdb();
     85 
     86         sConsoleUi = new ConsoleUi(getInstance());
     87         CommandParser cp = init(sConsoleUi, mainArgs);
     88 
     89         if (sMode == MODE.RUN) {
     90             try {
     91                 /* After booting up, the connection between
     92                  * CTS host and device isn't ready. It's needed
     93                  * to wait for 3 seconds for device ready to
     94                  * start the the mode of no console UI.
     95                  */
     96                 Thread.sleep(3000);
     97                 cp.removeKey(CTSCommand.OPTION_CFG);
     98                 sConsoleUi.processCommand(cp);
     99             } catch (InterruptedException e) {
    100                 Log.e("Met InterruptedException", e);
    101             } catch (Exception e) {
    102                 Log.e("Met exception when processing command", e);
    103             }
    104         } else if (sMode == MODE.CONSOLE) {
    105             sConsoleUi.startUi();
    106         }
    107 
    108         exit();
    109     }
    110 
    111     /**
    112      * Release host lock and then exit.
    113      */
    114     private static void exit() {
    115         Log.closeLog();
    116         HostLock.release();
    117         System.exit(-1);
    118     }
    119 
    120     /**
    121      * Extract mode from the options used to activating CTS.
    122      *
    123      * @param cp Command container.
    124      * @return The mode.
    125      */
    126     static private MODE getMode(final CommandParser cp) {
    127         String action = cp.getAction();
    128         if ((action != null) && (action.equals(CTSCommand.START))) {
    129             return MODE.RUN;
    130         } else {
    131             return MODE.CONSOLE;
    132         }
    133     }
    134 
    135     /**
    136      * Start zipped package.
    137      *
    138      * @param pathName  The path name of the zipped package.
    139      */
    140     public void startZippedPackage(final String pathName)
    141                 throws FileNotFoundException,
    142                        IOException,
    143                        ParserConfigurationException,
    144                        TransformerFactoryConfigurationError,
    145                        TransformerException,
    146                        DeviceNotAvailableException,
    147                        TestNotFoundException,
    148                        SAXException,
    149                        TestPlanNotFoundException,
    150                        IllegalTestNameException,
    151                        InterruptedException, DeviceDisconnectedException,
    152                        NoSuchAlgorithmException, InvalidNameSpaceException,
    153                        InvalidApkPathException {
    154 
    155         // step 1: add package
    156         if (!addPackage(pathName)) {
    157             return;
    158         }
    159 
    160         // step 2: create plan
    161         ArrayList<String> packages = new ArrayList<String>();
    162         String pkgName = pathName.substring(pathName
    163                 .lastIndexOf(File.separator) + 1, pathName.lastIndexOf("."));
    164         packages.add(pkgName);
    165         HashMap<String, ArrayList<String>> selectedResult =
    166                        new HashMap<String, ArrayList<String>>();
    167         selectedResult.put(pkgName, null);
    168         TestSessionBuilder.getInstance().serialize(TEMP_PLAN_NAME, packages, selectedResult);
    169 
    170         // step 3: start the plan
    171         TestSession ts = startSession(TEMP_PLAN_NAME, getFirstAvailableDevice().getSerialNumber(),
    172                 null);
    173 
    174         // step 4: copy the resulting zip file
    175         String resultName = pathName.substring(0, pathName.lastIndexOf("."))
    176                 + ".zip";
    177         TestSessionLog log = ts.getSessionLog();
    178         copyFile(log.getResultPath() + ".zip", resultName);
    179 
    180         // step 5: clear the temporary working environment
    181         removePlans(TEMP_PLAN_NAME);
    182         //give the system some time to avoid asserting
    183         Thread.sleep(1000);
    184 
    185         removePackages(pkgName);
    186         //give the system some time to avoid asserting
    187         Thread.sleep(1000);
    188     }
    189 
    190     /**
    191      * Copy the source file to the destination file.
    192      *
    193      * @param srcFileName The name of the source file.
    194      * @param dstFileName The name of the destination file.
    195      */
    196     private void copyFile(final String srcFileName, final String dstFileName) throws IOException {
    197         FileReader input = new FileReader(new File(srcFileName));
    198         BufferedWriter output = new BufferedWriter(new FileWriter(dstFileName));
    199 
    200         int c;
    201         while ((c = input.read()) != -1) {
    202             output.write(c);
    203         }
    204 
    205         input.close();
    206         output.flush();
    207         output.close();
    208     }
    209 
    210     /**
    211      * Add a package by the path and package name.
    212      *
    213      * @param pathName The path name.
    214      * @return If succeed in adding package, return true; else, return false.
    215      */
    216     public boolean addPackage(final String pathName) throws FileNotFoundException,
    217             IOException, NoSuchAlgorithmException {
    218 
    219         CaseRepository caseRepo = sConfig.getCaseRepository();
    220         if (!HostUtils.isFileExist(pathName)) {
    221             Log.e("Package error: package file " + pathName + " doesn't exist.", null);
    222             return false;
    223         }
    224 
    225         if (!caseRepo.isValidPackageName(pathName)) {
    226             return false;
    227         }
    228 
    229         caseRepo.addPackage(pathName);
    230         return true;
    231     }
    232 
    233     /**
    234      * Remove plans from the plan repository according to the specific plan name.
    235      *
    236      * @param name The plan name.
    237      */
    238     public void removePlans(final String name) {
    239         if ((name == null) || (name.length() == 0)) {
    240             CUIOutputStream.println("Please add plan name or all as parameter.");
    241             return;
    242         }
    243 
    244         PlanRepository planRepo = sConfig.getPlanRepository();
    245         if (name.equals(HostConfig.ALL)) {
    246             ArrayList<String> plans = planRepo.getAllPlanNames();
    247             for (String plan : plans) {
    248                 removePlan(plan, planRepo);
    249             }
    250         } else {
    251             if (!planRepo.getAllPlanNames().contains(name)) {
    252                 Log.e("No plan named " + name + " in repository!", null);
    253                 return;
    254             }
    255             removePlan(name, planRepo);
    256         }
    257     }
    258 
    259     /**
    260      * Remove a specified plan from the plan repository.
    261      *
    262      * @param planName The plan name.
    263      * @param planRepo The plan repository.
    264      */
    265     private void removePlan(final String planName, final PlanRepository planRepo) {
    266         File planFile = new File(planRepo.getPlanPath(planName));
    267         if (!planFile.isFile() || !planFile.exists()) {
    268             Log.e("Can't locate the file of the plan, please check your repository!", null);
    269             return;
    270         }
    271 
    272         if (!planFile.canWrite()) {
    273             Log.e("Can't delete this plan, permission denied!", null);
    274             return;
    275         }
    276 
    277         if (!planFile.delete()) {
    278             Log.e(planName + " plan file delete failed", null);
    279         }
    280     }
    281 
    282     /**
    283      * Remove packages from the case repository..
    284      *
    285      * @param packageName The java package name to be removed from the case repository.
    286      */
    287     public void removePackages(final String packageName)
    288             throws IndexOutOfBoundsException {
    289         CaseRepository caseRepo = sConfig.getCaseRepository();
    290 
    291         if ((packageName == null) || (packageName.length() == 0)) {
    292             CUIOutputStream.println("Please add package name or all as parameter.");
    293             return;
    294         }
    295 
    296         caseRepo.removePackages(packageName);
    297     }
    298 
    299     /**
    300      * Initialize TestHost with the arguments passed in.
    301      *
    302      * @param mainArgs The arguments.
    303      * @return CommandParser which contains the command and options.
    304      */
    305     static CommandParser init(final ConsoleUi cui, final String[] mainArgs) {
    306         CommandParser cp = null;
    307         String cfgPath= null;
    308 
    309         if (mainArgs.length == 0) {
    310             sMode = MODE.CONSOLE;
    311             cfgPath = System.getProperty("HOST_CONFIG");
    312             if ((cfgPath == null) || (cfgPath.length() == 0)) {
    313                 Log.e("Please make sure environment variable CTS_HOST_CFG is "
    314                        + "set as {cts install path}[/host_config.xml].", null);
    315                 exit();
    316             }
    317         } else if (mainArgs.length == 1) {
    318             sMode = MODE.CONSOLE;
    319             cfgPath = mainArgs[0];
    320         } else {
    321             String cmdLine = "";
    322             for (int i = 0; i < mainArgs.length; i ++) {
    323                 cmdLine += mainArgs[i] + " ";
    324             }
    325 
    326             try {
    327                 cp = CommandParser.parse(cmdLine);
    328                 if (!cui.validateCommandParams(cp)) {
    329                     Log.e("Please type in arguments correctly to activate CTS.", null);
    330                     exit();
    331                 }
    332             } catch (UnknownCommandException e1) {
    333                 Log.e("Please type in arguments correctly to activate CTS.", null);
    334                 exit();
    335             } catch (CommandNotFoundException e1) {
    336                 Log.e("Please type in arguments correctly to activate CTS.", null);
    337                 exit();
    338             }
    339 
    340             sMode = getMode(cp);
    341             if (sMode == MODE.RUN) {
    342                 if (cp.containsKey(CTSCommand.OPTION_CFG)) {
    343                     cfgPath = cp.getValue(CTSCommand.OPTION_CFG);
    344                 } else {
    345                     cfgPath = System.getProperty("HOST_CONFIG");
    346                     if ((cfgPath == null) || (cfgPath.length() == 0)) {
    347                         Log.e("Please make sure environment variable CTS_HOST_CFG "
    348                                + "is set as {cts install path}[/host_config.xml].", null);
    349                         exit();
    350                     }
    351                 }
    352             }
    353 
    354             if (cp.containsKey(CTSCommand.OPTION_QUICK)) {
    355                 sQuick = true;
    356             }
    357         }
    358 
    359         if ((cfgPath == null) || (cfgPath.length() == 0)) {
    360             Log.e("Please type in arguments correctly to activate CTS.", null);
    361             exit();
    362         }
    363 
    364         String filePath = getConfigFilePath(cfgPath);
    365         try {
    366             if (loadConfig(filePath) == false) {
    367                 exit();
    368             }
    369             if (sQuick) {
    370                 HostConfig.Ints.valueOf("postInstallWaitMs").setValue(1);
    371             }
    372 
    373             Log.initLog(sConfig.getLogRoot());
    374             sConfig.loadRepositories(sQuick);
    375         } catch (Exception e) {
    376             Log.e("Error while parsing cts config file", e);
    377             exit();
    378         }
    379         return cp;
    380     }
    381 
    382     /**
    383      * Singleton generator.
    384      *
    385      * @return The TestHost.
    386      */
    387     public static TestHost getInstance() {
    388         if (sInstance == null) {
    389             sInstance = new TestHost();
    390         }
    391 
    392         return sInstance;
    393     }
    394 
    395     /**
    396      * Get configuration file from the arguments given.
    397      *
    398      * @param filePath The file path.
    399      * @return The the path of the configuration file.
    400      */
    401     static private String getConfigFilePath(final String filePath) {
    402         if (filePath != null) {
    403             if (!HostUtils.isFileExist(filePath)) {
    404                 Log.e("Configuration file \"" + filePath + "\" doesn't exist.", null);
    405                 exit();
    406             }
    407         } else {
    408             Log.e("Configuration file doesn't exist.", null);
    409             exit();
    410         }
    411 
    412         return filePath;
    413     }
    414 
    415     /**
    416      * Load configuration from the given file.
    417      *
    418      * @param configPath The configuration path.
    419      * @return If succeed, return true; else, return false.
    420      */
    421     static boolean loadConfig(final String configPath) throws SAXException,
    422             IOException, ParserConfigurationException {
    423         sConfig = HostConfig.getInstance();
    424 
    425         return sConfig.load(configPath);
    426     }
    427 
    428     /**
    429      * Get case repository.
    430      *
    431      * @return The case repository.
    432      */
    433     public HostConfig.CaseRepository getCaseRepository() {
    434         return sConfig.getCaseRepository();
    435     }
    436 
    437     /**
    438      * Get plan repository.
    439      *
    440      * @return The plan repository.
    441      */
    442     public HostConfig.PlanRepository getPlanRepository() {
    443         return sConfig.getPlanRepository();
    444     }
    445 
    446     /**
    447      * Run the specified {@link TestSession} on the specified {@link TestDevice}(s)
    448      *
    449      * @param ts the specified {@link TestSession}
    450      * @param deviceId the ID of the specified {@link TestDevice}
    451      * @param testFullName The full name of the test to be run.
    452      * @param javaPkgName The specific java package name to be run.
    453      * @param type The action type to activate the test session.
    454      */
    455     static private void runTest(final TestSession ts, final String deviceId,
    456             final String testFullName, final String javaPkgName, ActionType type)
    457             throws DeviceNotAvailableException, TestNotFoundException, IllegalTestNameException,
    458             DeviceDisconnectedException, InvalidNameSpaceException,
    459             InvalidApkPathException {
    460 
    461         if (ts == null) {
    462             return;
    463         }
    464 
    465         ts.setObserver(getInstance());
    466         TestDevice device = sDeviceManager.allocateFreeDeviceById(deviceId);
    467         TestSessionLog sessionLog = ts.getSessionLog();
    468         ts.setTestDevice(device);
    469         ts.getDevice().installDeviceSetupApp();
    470         if (!sQuick) {
    471             sessionLog.setDeviceInfo(ts.getDevice().getDeviceInfo());
    472         }
    473 
    474         boolean finish = false;
    475         while (!finish) {
    476             ts.getDevice().disableKeyguard();
    477             try {
    478                 switch (type) {
    479                 case RUN_SINGLE_TEST:
    480                     ts.start(testFullName);
    481                     break;
    482 
    483                 case RUN_SINGLE_JAVA_PACKAGE:
    484                     ts.start(javaPkgName);
    485                     break;
    486 
    487                 case START_NEW_SESSION:
    488                     ts.start();
    489                     break;
    490 
    491                 case RESUME_SESSION:
    492                     ts.resume();
    493                     break;
    494                 }
    495 
    496                 finish = true;
    497             } catch (ADBServerNeedRestartException e) {
    498                 Log.d(e.getMessage());
    499                 Log.i("Max ADB operations reached. Restarting ADB...");
    500 
    501                 TestSession.setADBServerRestartedMode();
    502                 sDeviceManager.restartADBServer(ts);
    503 
    504                 type = ActionType.RESUME_SESSION;
    505             }
    506         }
    507 
    508         TestSession.resetADBServerRestartedMode();
    509         if (HostConfig.getMaxTestCount() > 0) {
    510             sDeviceManager.resetTestDevice(ts.getDevice());
    511         }
    512 
    513         ts.getDevice().uninstallDeviceSetupApp();
    514     }
    515 
    516     /**
    517      * Create {@link TestSession} according to the specified test plan.
    518      *
    519      * @param testPlanName the name of the specified test plan
    520      * @return a {@link TestSession}
    521      */
    522     static public TestSession createSession(final String testPlanName)
    523             throws IOException, TestNotFoundException, SAXException,
    524             ParserConfigurationException, TestPlanNotFoundException, NoSuchAlgorithmException {
    525 
    526         String testPlanPath = sConfig.getPlanRepository().getPlanPath(testPlanName);
    527         TestSession ts = TestSessionBuilder.getInstance().build(testPlanPath);
    528         sSessions.add(ts);
    529 
    530         return ts;
    531     }
    532 
    533     /** {@inheritDoc} */
    534     public void notifyFinished(final TestSession ts) {
    535         // As test run on a session, so just keep session info in debug level
    536         Log.d("Session " + ts.getId() + " finished.");
    537 
    538         synchronized (sTestSessionSync) {
    539             sTestSessionSync.notify();
    540         }
    541         ts.getSessionLog().sessionComplete();
    542     }
    543 
    544     /**
    545      * Tear down ADB connection.
    546      */
    547     public void tearDown() {
    548         AndroidDebugBridge.disconnectBridge();
    549         AndroidDebugBridge.terminate();
    550     }
    551 
    552     /**
    553      * Get the sessions connected with devices.
    554      *
    555      * @return The sessions.
    556      */
    557     public Collection<TestSession> getSessions() {
    558         return sSessions;
    559     }
    560 
    561     /**
    562      * Get session by session ID.
    563      *
    564      * @param sessionId The session ID.
    565      * @return The session.
    566      */
    567     public TestSession getSession(final int sessionId) {
    568         for (TestSession session : sSessions) {
    569             if (session.getId() == sessionId) {
    570                 return session;
    571             }
    572         }
    573         return null;
    574     }
    575 
    576     /**
    577      * Get session by test plan name.
    578      *
    579      * @param testPlanName Test plan name.
    580      * @return The session corresponding to the test plan name.
    581      */
    582     public ArrayList<TestSession> getSessionList(final String testPlanName) {
    583         ArrayList<TestSession> list = new ArrayList<TestSession>();
    584         for (TestSession session : sSessions) {
    585             if (testPlanName.equals(session.getSessionLog().getTestPlanName())) {
    586                 list.add(session);
    587             }
    588         }
    589         return list;
    590     }
    591 
    592     /**
    593      * List the ID, name and status of all {@link TestDevice} which connected to
    594      * the {@link TestHost}.
    595      *
    596      * @return a string list of {@link TestDevice}'s id, name and status.
    597      */
    598     public String[] listDevices() {
    599         ArrayList<String> deviceList = new ArrayList<String>();
    600         TestDevice[] devices = sDeviceManager.getDeviceList();
    601 
    602         for (TestDevice device : devices) {
    603             deviceList.add(device.getSerialNumber() + "\t" + device.getStatusAsString());
    604         }
    605         return deviceList.toArray(new String[deviceList.size()]);
    606     }
    607 
    608     /**
    609      * Get device list connected with the host.
    610      *
    611      * @return The device list connected with the host.
    612      */
    613     public TestDevice[] getDeviceList() {
    614         return sDeviceManager.getDeviceList();
    615     }
    616 
    617     /**
    618      * Get the first available device.
    619      *
    620      * @return the first available device or null if none are available.
    621      */
    622     public TestDevice getFirstAvailableDevice() {
    623         for (TestDevice td : sDeviceManager.getDeviceList()) {
    624             if (td.getStatus() == TestDevice.STATUS_IDLE) {
    625                 return td;
    626             }
    627         }
    628         return null;
    629     }
    630 
    631     /**
    632      * Get session logs.
    633      *
    634      * @return Session logs.
    635      */
    636     public Collection<TestSessionLog> getSessionLogs() {
    637         ArrayList<TestSessionLog> sessionLogs = new ArrayList<TestSessionLog>();
    638         for (TestSession session : sSessions) {
    639             sessionLogs.add(session.getSessionLog());
    640         }
    641         return sessionLogs;
    642     }
    643     /**
    644      * Start a test session.
    645      *
    646      * @param testPlanName TestPlan config file name
    647      * @param deviceId Target device ID
    648      * @param profile The profile of the device being tested.
    649      * @param javaPkgName The specific java package name to be run.
    650      */
    651     public TestSession startSession(final String testPlanName,
    652             String deviceId, final String javaPkgName)
    653             throws IOException, DeviceNotAvailableException,
    654             TestNotFoundException, SAXException, ParserConfigurationException,
    655             TestPlanNotFoundException, IllegalTestNameException,
    656             DeviceDisconnectedException, NoSuchAlgorithmException,
    657             InvalidNameSpaceException, InvalidApkPathException {
    658 
    659         TestSession ts = createSession(testPlanName);
    660         if ((javaPkgName != null) && (javaPkgName.length() != 0)) {
    661             runTest(ts, deviceId, null, javaPkgName, ActionType.RUN_SINGLE_JAVA_PACKAGE);
    662         } else {
    663             runTest(ts, deviceId, null, javaPkgName, ActionType.START_NEW_SESSION);
    664         }
    665 
    666         ts.getSessionLog().sessionComplete();
    667         return ts;
    668     }
    669 
    670     /**
    671      * Start a test session.
    672      *
    673      * @param ts The test session.
    674      * @param deviceId Target device ID.
    675      * @param testFullName Specific test full name.
    676      * @param javaPkgName The specific java package name to be run.
    677      * @param type The action type to activate the test session.
    678      */
    679     public TestSession startSession(final TestSession ts, String deviceId,
    680             final String testFullName, final String javaPkgName, ActionType type)
    681             throws DeviceNotAvailableException,
    682             TestNotFoundException, IllegalTestNameException,
    683             DeviceDisconnectedException, InvalidNameSpaceException,
    684             InvalidApkPathException {
    685 
    686         runTest(ts, deviceId, testFullName, javaPkgName, type);
    687         ts.getSessionLog().sessionComplete();
    688         return ts;
    689     }
    690 
    691     /**
    692      * Get plan name from what is typed in by the user.
    693      *
    694      * @param rawPlanName The raw plan name.
    695      * @return The plan name.
    696      */
    697     public String getPlanName(final String rawPlanName) {
    698         if (rawPlanName.indexOf("\\") != -1) {
    699             return rawPlanName.replaceAll("\\\\", "");
    700         }
    701         if (rawPlanName.indexOf("\"") != -1) {
    702             return rawPlanName.replaceAll("\"", "");
    703         }
    704         return rawPlanName;
    705     }
    706 
    707     /**
    708      * Add test session.
    709      *
    710      * @param ts The test session.
    711      */
    712     public void addSession(TestSession ts) {
    713         sSessions.add(ts);
    714     }
    715 }
    716