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