blob: 18ace44a0ef952167d3d3dbc93345cdf8e35698f [file] [log] [blame]
/*
* Copyright (C) 2008 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 com.android.cts;
import android.annotation.cts.Profile;
import java.io.IOException;
import java.util.Collection;
/**
* Represents a runtime session for a test plan, takes charge in running a
* plan and the setup&tear-downs.
*/
public class TestSession {
private SessionObserver mSessionObserver;
private TestSessionLog mSessionLog;
private TestDevice mDevice;
private int mId;
private STATUS mStatus;
private static int sIdCounter = 0;
enum STATUS {
INIT, STARTED, INSTALLING, RUNNING, PAUSED, RESUMED, STOPPED, FINISHED
}
private int mRequiredDeviceNumber;
private boolean mTestStop;
private TestSessionThread mTestThread;
private boolean mNeedRestartAdbServer;
private static boolean mADBServerRestartedMode;
/** Running count of tests executed since last reboot. */
private static long mTestCount;
public TestSession(final TestSessionLog sessionLog,
final int requiredDeviceNum) {
mStatus = STATUS.INIT;
mNeedRestartAdbServer = false;
mADBServerRestartedMode = false;
mTestCount = 0;
mSessionLog = sessionLog;
mDevice = null;
mRequiredDeviceNumber = requiredDeviceNum;
mTestStop = false;
mId = sIdCounter++;
}
/**
* Get the last session ID.
*
* @return The last session ID.
*/
public static int getLastSessionId() {
return sIdCounter-1;
}
/**
* Set ADB server restarted mode.
*/
public static void setADBServerRestartedMode() {
mADBServerRestartedMode = true;
}
/**
* Reset ADB server restarted mode.
*/
public static void resetADBServerRestartedMode() {
mADBServerRestartedMode = false;
}
/**
* Check if it's in ADB server restarted mode.
*
* @return If in ADB server restarted mode, return true; else, return false.
*/
public static boolean isADBServerRestartedMode() {
return mADBServerRestartedMode;
}
/**
* Increase the test count.
*/
public static void incTestCount() {
mTestCount++;
}
/**
* Reset the test count.
*/
public static void resetTestCount() {
mTestCount = 0;
}
/**
* Get the test count recently has been run.
*
* @return The test count recently has been run.
*/
public static long getTestCount() {
return mTestCount;
}
/**
* Check if the test count exceeds the max test count. If the max test count is disabled
* (HostConfig.getMaxTestCount() <= 0), this method always returns false.
*
* @return true, if the max count is enabled and exceeded.
*/
public static boolean exceedsMaxCount() {
final long maxTestCount = HostConfig.getMaxTestCount();
return (maxTestCount > 0) && (mTestCount >= maxTestCount);
}
/**
* Get status.
*
* @return The status.
*/
public STATUS getStatus() {
return mStatus;
}
/**
* Get device ID.
*
* @return device ID.
*/
public String getDeviceId() {
if (mDevice == null) {
return null;
}
return mDevice.getSerialNumber();
}
/**
* Get the test device.
*
* @return the test device.
*/
public TestDevice getDevice() {
return mDevice;
}
/**
* Get the number of required devices.
*
* @return The number of required devices.
*/
public int getNumOfRequiredDevices() {
return mRequiredDeviceNumber;
}
/**
* Get ID.
*
* @return ID.
*/
public int getId() {
return mId;
}
/**
* Start the single test with full name.
*
* @param testFullName The test full name.
*/
public void start(final String testFullName) throws TestNotFoundException,
IllegalTestNameException, ADBServerNeedRestartException {
if ((testFullName == null) || (testFullName.length() == 0)) {
throw new IllegalArgumentException();
}
// The test full name follows the following rule:
// java_package_name.class_name#method_name.
// Other forms will be treated as illegal.
if (!testFullName.matches("(\\w+.)+\\w+")) {
throw new IllegalTestNameException(testFullName);
}
Test test = null;
TestPackage pkg = null;
if (-1 != testFullName.indexOf(Test.METHOD_SEPARATOR)) {
test = searchTest(testFullName);
if (test == null) {
throw new TestNotFoundException(
"The specific test does not exist: " + testFullName);
}
mTestThread = new TestSessionThread(this, test);
CUIOutputStream.println("start test " + testFullName);
} else {
pkg = searchTestPackage(testFullName);
if (pkg == null) {
throw new TestNotFoundException(
"The specific test package does not exist: " + testFullName);
}
mTestThread = new TestSessionThread(this, pkg, testFullName);
CUIOutputStream.println("start java package " + testFullName);
}
mStatus = STATUS.STARTED;
startImpl();
}
/**
* Implement starting/resuming session.
*/
private void startImpl() throws ADBServerNeedRestartException {
String resultPath = mSessionLog.getResultPath();
if ((resultPath == null) || (resultPath.length() == 0)) {
mSessionLog.setStartTime(System.currentTimeMillis());
}
resetTestCount();
mTestThread.start();
try {
mTestThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (mNeedRestartAdbServer && HostConfig.getMaxTestCount() > 0) {
throw new ADBServerNeedRestartException("Need restart ADB server");
}
}
/**
* Resume the test session.
*/
public void resume() throws ADBServerNeedRestartException {
mStatus = STATUS.RESUMED;
mTestThread = new TestSessionThread(this);
if (!isADBServerRestartedMode()) {
CUIOutputStream.println("resume test plan " + getSessionLog().getTestPlanName()
+ " (session id = " + mId + ")");
}
startImpl();
}
/**
* Search the test with the test full name given among the test
* packages contained within this session.
*
* @param testFullName The full name of the test.
* @return The test with the full name given.
*/
private Test searchTest(final String testFullName) {
Test test = null;
for (TestPackage pkg : mSessionLog.getTestPackages()) {
test = pkg.searchTest(testFullName);
if (test != null) {
break;
}
}
return test;
}
/**
* Search the test package with the specified java package name.
*
* @param javaPkgName The java package name.
* @return The test package with the specified java package name.
*/
private TestPackage searchTestPackage(String javaPkgName) {
for (TestPackage pkg : mSessionLog.getTestPackages()) {
Collection<Test> tests = pkg.getTests();
for (Test test : tests) {
String testFullName = test.getFullName();
if (testFullName.startsWith(javaPkgName)) {
//adjust the java package name to make it equal to some java package name
if (testFullName.charAt(javaPkgName.length()) != '.') {
javaPkgName = javaPkgName.substring(0, javaPkgName.lastIndexOf("."));
}
return pkg;
}
}
}
return null;
}
/**
* Start a new test session thread to execute the specific test plan.
*/
public void start() throws ADBServerNeedRestartException {
mStatus = STATUS.STARTED;
mSessionLog.setStartTime(System.currentTimeMillis());
mTestThread = new TestSessionThread(this);
CUIOutputStream.println("start test plan " + getSessionLog().getTestPlanName());
startImpl();
}
/**
* Set observer.
*
* @param so Session observer.
*/
public void setObserver(final SessionObserver so) {
mSessionObserver = so;
}
/**
* Print the message by appending the new line mark.
*
* @param msg The message to be print.
*/
private void println(final String msg) {
if (!mTestStop) {
CUIOutputStream.println(msg);
}
}
/**
* Set the {@link TestDevice} which will run the test.
*
* @param device The {@link TestDevice} will run the test.
*/
public void setTestDevice(final TestDevice device) {
mDevice = device;
}
/**
* Get the session log of this session.
*
* @return The session log of this session.
*/
public TestSessionLog getSessionLog() {
return mSessionLog;
}
/**
* Get the test packages contained within this session.
*
* @return The test packages contained within this session.
*/
public Collection<TestPackage> getTestPackages() {
return mSessionLog.getTestPackages();
}
/**
* The Thread to be run the {@link TestSession}
*/
class TestSessionThread extends Thread {
private final int MSEC_PER_SECOND = 1000;
private TestSession mTestSession;
private Test mTest;
private TestPackage mTestPackage;
private String mJavaPackageName;
private ResultObserver mResultObserver;
public TestSessionThread(final TestSession ts) {
mTestSession = ts;
mResultObserver = ResultObserver.getInstance();
}
public TestSessionThread(final TestSession ts, final Test test) {
mTestSession = ts;
mResultObserver = ResultObserver.getInstance();
mTest = test;
}
public TestSessionThread(final TestSession ts,
final TestPackage pkg, final String javaPkgName) {
mTestSession = ts;
mResultObserver = ResultObserver.getInstance();
mTestPackage = pkg;
mJavaPackageName = javaPkgName;
}
/** {@inheritDoc} */
@Override
public void run() {
Log.d("Start a test session.");
mNeedRestartAdbServer = false;
mResultObserver.setTestSessionLog(getSessionLog());
mResultObserver.start();
try {
if (mTest != null) {
TestPackage pkg = mTest.getTestPackage();
pkg.setSessionThread(this);
pkg.runTest(mDevice, mTest, mSessionLog.getProfile());
} else if (mTestPackage != null) {
mTestPackage.setSessionThread(this);
mTestPackage.run(mDevice, mJavaPackageName, mSessionLog);
} else {
for (TestPackage pkg : mSessionLog.getTestPackages()) {
if (!pkg.isAllTestsRun()) {
pkg.setSessionThread(this);
pkg.run(mDevice, null, mSessionLog);
if (!isAllTestsRun()) {
if (HostConfig.getMaxTestCount() > 0) {
// ADB server restart enabled
markNeedRestartADBServer();
return;
}
} else {
Log.d("All tests have been run.");
break;
}
}
}
mNeedRestartAdbServer = false;
displayTestResultSummary();
}
} catch (IOException e) {
Log.e("Got exception when running the package", e);
} catch (DeviceDisconnectedException e) {
Log.e("Device " + e.getMessage() + " disconnected ", null);
} catch (ADBServerNeedRestartException e) {
Log.d(e.getMessage());
if (mTest == null) {
markNeedRestartADBServer();
return;
}
} catch (InvalidApkPathException e) {
Log.e(e.getMessage(), null);
} catch (InvalidNameSpaceException e) {
Log.e(e.getMessage(), null);
}
long startTime = getSessionLog().getStartTime().getTime();
displayTimeInfo(startTime, System.currentTimeMillis());
mStatus = STATUS.FINISHED;
mTestSession.getSessionLog().setEndTime(System.currentTimeMillis());
mSessionObserver.notifyFinished(mTestSession);
notifyResultObserver();
}
/**
* Mark need restarting ADB server.
*/
private void markNeedRestartADBServer() {
Log.d("mark mNeedRestartAdbServer to true");
mNeedRestartAdbServer = true;
mStatus = STATUS.FINISHED;
notifyResultObserver();
return;
}
/**
* Notify result observer.
*/
private void notifyResultObserver() {
mResultObserver.notifyUpdate();
mResultObserver.finish();
}
/**
* Check if all tests contained in all of the test packages has been run.
*
* @return If all tests have been run, return true; else, return false.
*/
private boolean isAllTestsRun() {
Collection<TestPackage> pkgs = getTestPackages();
for (TestPackage pkg : pkgs) {
if (!pkg.isAllTestsRun()) {
return false;
}
}
return true;
}
/**
* Display the summary of test result.
*/
private void displayTestResultSummary() {
int passNum = mSessionLog.getTestList(CtsTestResult.CODE_PASS).size();
int failNum = mSessionLog.getTestList(CtsTestResult.CODE_FAIL).size();
int omittedNum = mSessionLog.getTestList(CtsTestResult.CODE_OMITTED).size();
int notExecutedNum = mSessionLog.getTestList(CtsTestResult.CODE_NOT_EXECUTED).size();
int timeOutNum = mSessionLog.getTestList(CtsTestResult.CODE_TIMEOUT).size();
int total = passNum + failNum + notExecutedNum + timeOutNum;
println("Test summary: pass=" + passNum
+ " fail=" + failNum
+ " timeOut=" + timeOutNum
+ " omitted=" + omittedNum
+ " notExecuted=" + notExecutedNum
+ " Total=" + total);
}
/**
* Display the time information of running a test plan.
*
* @param startTime start time in milliseconds.
* @param endTime end time in milliseconds.
*/
private void displayTimeInfo(final long startTime, final long endTime) {
long diff = endTime - startTime;
long seconds = diff / MSEC_PER_SECOND;
long millisec = diff % MSEC_PER_SECOND;
println("Time: " + seconds + "." + millisec + "s\n");
}
}
/**
* Update test result after executing each test.
* During running test, the process may be interrupted. To avoid
* test result losing, it's needed to update the test result into
* xml file after executing each test, which is done by this observer.
* The possible reasons causing interruption to the process include:
* <ul>
* <li> Device disconnected
* <li> Run time exception
* <li> System crash
* <li> User action to cause the system exit
* </ul>
*
*/
static class ResultObserver {
static private boolean mFinished = false;
static private boolean mNotified = false; //used for avoiding race condition
static private boolean mNeedUpdate = true;
static private TestSessionLog mSessionLog;
static final ResultObserver sInstance = new ResultObserver();
private Observer mObserver;
/**
* Get the static instance.
*
* @return The static instance.
*/
public static final ResultObserver getInstance() {
return sInstance;
}
/**
* Set TestSessionLog.
*
* @param log The TestSessionLog.
*/
public void setTestSessionLog(TestSessionLog log) {
mSessionLog = log;
}
/**
* Notify this updating thread to update the test result to xml file.
*/
public void notifyUpdate() {
if (mObserver != null) {
synchronized (mObserver) {
mNotified = true;
mObserver.notify();
}
}
}
/**
* Start the observer.
*/
public void start() {
mFinished = false;
mNeedUpdate = true;
mObserver = new Observer();
mObserver.start();
}
/**
* Finish updating.
*/
public void finish() {
mFinished = true;
mNeedUpdate = false;
notifyUpdate();
try {
mObserver.join();
mObserver = null;
} catch (InterruptedException e) {
}
}
/**
* Observer which updates the test result to result XML file.
*
*/
class Observer extends Thread {
/** {@inheritDoc} */
@Override
public void run() {
while (!mFinished) {
try {
synchronized (this) {
if ((!mNotified) && (!mFinished)) {
wait();
}
mNotified = false;
}
if (mNeedUpdate && (mSessionLog != null)) {
mSessionLog.sessionComplete();
}
} catch (InterruptedException e) {
}
}
}
}
}
}