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