Code drop from //branches/cupcake/...@124589
This commit is contained in:
@@ -103,7 +103,7 @@ public class ClientData {
|
||||
private boolean mIsDdmAware;
|
||||
|
||||
// the client's process ID
|
||||
private int mPid = -1;
|
||||
private final int mPid;
|
||||
|
||||
// Java VM identification string
|
||||
private String mVmIdentifier;
|
||||
@@ -272,14 +272,6 @@ public class ClientData {
|
||||
return mPid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets process ID.
|
||||
*/
|
||||
void setPid(int pid) {
|
||||
assert pid != mPid;
|
||||
mPid = pid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Client's VM identifier.
|
||||
*/
|
||||
|
||||
@@ -30,8 +30,10 @@ import java.util.Map;
|
||||
|
||||
/**
|
||||
* A Device. It can be a physical device or an emulator.
|
||||
*
|
||||
* TODO: make this class package-protected, and shift all callers to use IDevice
|
||||
*/
|
||||
public final class Device {
|
||||
public final class Device implements IDevice {
|
||||
/**
|
||||
* The state of a device.
|
||||
*/
|
||||
@@ -61,22 +63,9 @@ public final class Device {
|
||||
}
|
||||
}
|
||||
|
||||
public final static String PROP_BUILD_VERSION = "ro.build.version.release";
|
||||
public final static String PROP_DEBUGGABLE = "ro.debuggable";
|
||||
|
||||
/** Serial number of the first connected emulator. */
|
||||
public final static String FIRST_EMULATOR_SN = "emulator-5554"; //$NON-NLS-1$
|
||||
|
||||
/** Emulator Serial Number regexp. */
|
||||
final static String RE_EMULATOR_SN = "emulator-(\\d+)"; //$NON-NLS-1$
|
||||
|
||||
/** Device change bit mask: {@link DeviceState} change. */
|
||||
public static final int CHANGE_STATE = 0x0001;
|
||||
/** Device change bit mask: {@link Client} list change. */
|
||||
public static final int CHANGE_CLIENT_LIST = 0x0002;
|
||||
/** Device change bit mask: build info change. */
|
||||
public static final int CHANGE_BUILD_INFO = 0x0004;
|
||||
|
||||
/** Serial number of the device */
|
||||
String serialNumber = null;
|
||||
|
||||
@@ -94,38 +83,41 @@ public final class Device {
|
||||
*/
|
||||
private SocketChannel mSocketChannel;
|
||||
|
||||
/**
|
||||
* Returns the serial number of the device.
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getSerialNumber()
|
||||
*/
|
||||
public String getSerialNumber() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the state of the device.
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getState()
|
||||
*/
|
||||
public DeviceState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the device properties. It contains the whole output of 'getprop'
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getProperties()
|
||||
*/
|
||||
public Map<String, String> getProperties() {
|
||||
return Collections.unmodifiableMap(mProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of property for this device.
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getPropertyCount()
|
||||
*/
|
||||
public int getPropertyCount() {
|
||||
return mProperties.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a property value.
|
||||
* @param name the name of the value to return.
|
||||
* @return the value or <code>null</code> if the property does not exist.
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getProperty(java.lang.String)
|
||||
*/
|
||||
public String getProperty(String name) {
|
||||
return mProperties.get(name);
|
||||
@@ -137,46 +129,49 @@ public final class Device {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the device is ready.
|
||||
* @return <code>true</code> if {@link #getState()} returns {@link DeviceState#ONLINE}.
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#isOnline()
|
||||
*/
|
||||
public boolean isOnline() {
|
||||
return state == DeviceState.ONLINE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if the device is an emulator.
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#isEmulator()
|
||||
*/
|
||||
public boolean isEmulator() {
|
||||
return serialNumber.matches(RE_EMULATOR_SN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the device is offline.
|
||||
* @return <code>true</code> if {@link #getState()} returns {@link DeviceState#OFFLINE}.
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#isOffline()
|
||||
*/
|
||||
public boolean isOffline() {
|
||||
return state == DeviceState.OFFLINE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the device is in bootloader mode.
|
||||
* @return <code>true</code> if {@link #getState()} returns {@link DeviceState#BOOTLOADER}.
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#isBootLoader()
|
||||
*/
|
||||
public boolean isBootLoader() {
|
||||
return state == DeviceState.BOOTLOADER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the {@link Device} has {@link Client}s.
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#hasClients()
|
||||
*/
|
||||
public boolean hasClients() {
|
||||
return mClients.size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array of clients.
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getClients()
|
||||
*/
|
||||
public Client[] getClients() {
|
||||
synchronized (mClients) {
|
||||
@@ -184,10 +179,9 @@ public final class Device {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Client} by its application name.
|
||||
* @param applicationName the name of the application
|
||||
* @return the <code>Client</code> object or <code>null</code> if no match was found.
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getClient(java.lang.String)
|
||||
*/
|
||||
public Client getClient(String applicationName) {
|
||||
synchronized (mClients) {
|
||||
@@ -202,9 +196,9 @@ public final class Device {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link SyncService} object to push / pull files to and from the device.
|
||||
* @return <code>null</code> if the SyncService couldn't be created.
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getSyncService()
|
||||
*/
|
||||
public SyncService getSyncService() {
|
||||
SyncService syncService = new SyncService(AndroidDebugBridge.sSocketAddr, this);
|
||||
@@ -215,28 +209,25 @@ public final class Device {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link FileListingService} for this device.
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getFileListingService()
|
||||
*/
|
||||
public FileListingService getFileListingService() {
|
||||
return new FileListingService(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a screen shot of the device and returns it as a {@link RawImage}.
|
||||
* @return the screenshot as a <code>RawImage</code> or <code>null</code> if
|
||||
* something went wrong.
|
||||
* @throws IOException
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getScreenshot()
|
||||
*/
|
||||
public RawImage getScreenshot() throws IOException {
|
||||
return AdbHelper.getFrameBuffer(AndroidDebugBridge.sSocketAddr, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a shell command on the device, and sends the result to a receiver.
|
||||
* @param command The command to execute
|
||||
* @param receiver The receiver object getting the result from the command.
|
||||
* @throws IOException
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#executeShellCommand(java.lang.String, com.android.ddmlib.IShellOutputReceiver)
|
||||
*/
|
||||
public void executeShellCommand(String command, IShellOutputReceiver receiver)
|
||||
throws IOException {
|
||||
@@ -244,20 +235,17 @@ public final class Device {
|
||||
receiver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the event log service and outputs the event log to the {@link LogReceiver}.
|
||||
* @param receiver the receiver to receive the event log entries.
|
||||
* @throws IOException
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#runEventLogService(com.android.ddmlib.log.LogReceiver)
|
||||
*/
|
||||
public void runEventLogService(LogReceiver receiver) throws IOException {
|
||||
AdbHelper.runEventLogService(AndroidDebugBridge.sSocketAddr, this, receiver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a port forwarding between a local and a remote port.
|
||||
* @param localPort the local port to forward
|
||||
* @param remotePort the remote port.
|
||||
* @return <code>true</code> if success.
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#createForward(int, int)
|
||||
*/
|
||||
public boolean createForward(int localPort, int remotePort) {
|
||||
try {
|
||||
@@ -269,11 +257,9 @@ public final class Device {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a port forwarding between a local and a remote port.
|
||||
* @param localPort the local port to forward
|
||||
* @param remotePort the remote port.
|
||||
* @return <code>true</code> if success.
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#removeForward(int, int)
|
||||
*/
|
||||
public boolean removeForward(int localPort, int remotePort) {
|
||||
try {
|
||||
@@ -285,9 +271,9 @@ public final class Device {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the client by pid or <code>null</code> if pid is unknown
|
||||
* @param pid the pid of the client.
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getClientName(int)
|
||||
*/
|
||||
public String getClientName(int pid) {
|
||||
synchronized (mClients) {
|
||||
|
||||
@@ -84,16 +84,21 @@ final class HandleHello extends ChunkHandler {
|
||||
|
||||
vmIdent = getString(data, vmIdentLen);
|
||||
appName = getString(data, appNameLen);
|
||||
|
||||
|
||||
Log.d("ddm-hello", "HELO: v=" + version + ", pid=" + pid
|
||||
+ ", vm='" + vmIdent + "', app='" + appName + "'");
|
||||
|
||||
ClientData cd = client.getClientData();
|
||||
|
||||
synchronized (cd) {
|
||||
cd.setVmIdentifier(vmIdent);
|
||||
cd.setClientDescription(appName);
|
||||
cd.setPid(pid);
|
||||
cd.isDdmAware(true);
|
||||
if (cd.getPid() == pid) {
|
||||
cd.setVmIdentifier(vmIdent);
|
||||
cd.setClientDescription(appName);
|
||||
cd.isDdmAware(true);
|
||||
} else {
|
||||
Log.e("ddm-hello", "Received pid (" + pid + ") does not match client pid ("
|
||||
+ cd.getPid() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
client = checkDebuggerPortForAppName(client, appName);
|
||||
|
||||
167
tools/ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java
Executable file
167
tools/ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java
Executable file
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* 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.ddmlib;
|
||||
|
||||
import com.android.ddmlib.Device.DeviceState;
|
||||
import com.android.ddmlib.log.LogReceiver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* A Device. It can be a physical device or an emulator.
|
||||
*/
|
||||
public interface IDevice {
|
||||
|
||||
public final static String PROP_BUILD_VERSION = "ro.build.version.release";
|
||||
public final static String PROP_BUILD_VERSION_NUMBER = "ro.build.version.sdk";
|
||||
public final static String PROP_DEBUGGABLE = "ro.debuggable";
|
||||
/** Serial number of the first connected emulator. */
|
||||
public final static String FIRST_EMULATOR_SN = "emulator-5554"; //$NON-NLS-1$
|
||||
/** Device change bit mask: {@link DeviceState} change. */
|
||||
public static final int CHANGE_STATE = 0x0001;
|
||||
/** Device change bit mask: {@link Client} list change. */
|
||||
public static final int CHANGE_CLIENT_LIST = 0x0002;
|
||||
/** Device change bit mask: build info change. */
|
||||
public static final int CHANGE_BUILD_INFO = 0x0004;
|
||||
|
||||
/**
|
||||
* Returns the serial number of the device.
|
||||
*/
|
||||
public String getSerialNumber();
|
||||
|
||||
/**
|
||||
* Returns the state of the device.
|
||||
*/
|
||||
public DeviceState getState();
|
||||
|
||||
/**
|
||||
* Returns the device properties. It contains the whole output of 'getprop'
|
||||
*/
|
||||
public Map<String, String> getProperties();
|
||||
|
||||
/**
|
||||
* Returns the number of property for this device.
|
||||
*/
|
||||
public int getPropertyCount();
|
||||
|
||||
/**
|
||||
* Returns a property value.
|
||||
* @param name the name of the value to return.
|
||||
* @return the value or <code>null</code> if the property does not exist.
|
||||
*/
|
||||
public String getProperty(String name);
|
||||
|
||||
/**
|
||||
* Returns if the device is ready.
|
||||
* @return <code>true</code> if {@link #getState()} returns {@link DeviceState#ONLINE}.
|
||||
*/
|
||||
public boolean isOnline();
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if the device is an emulator.
|
||||
*/
|
||||
public boolean isEmulator();
|
||||
|
||||
/**
|
||||
* Returns if the device is offline.
|
||||
* @return <code>true</code> if {@link #getState()} returns {@link DeviceState#OFFLINE}.
|
||||
*/
|
||||
public boolean isOffline();
|
||||
|
||||
/**
|
||||
* Returns if the device is in bootloader mode.
|
||||
* @return <code>true</code> if {@link #getState()} returns {@link DeviceState#BOOTLOADER}.
|
||||
*/
|
||||
public boolean isBootLoader();
|
||||
|
||||
/**
|
||||
* Returns whether the {@link Device} has {@link Client}s.
|
||||
*/
|
||||
public boolean hasClients();
|
||||
|
||||
/**
|
||||
* Returns the array of clients.
|
||||
*/
|
||||
public Client[] getClients();
|
||||
|
||||
/**
|
||||
* Returns a {@link Client} by its application name.
|
||||
* @param applicationName the name of the application
|
||||
* @return the <code>Client</code> object or <code>null</code> if no match was found.
|
||||
*/
|
||||
public Client getClient(String applicationName);
|
||||
|
||||
/**
|
||||
* Returns a {@link SyncService} object to push / pull files to and from the device.
|
||||
* @return <code>null</code> if the SyncService couldn't be created.
|
||||
*/
|
||||
public SyncService getSyncService();
|
||||
|
||||
/**
|
||||
* Returns a {@link FileListingService} for this device.
|
||||
*/
|
||||
public FileListingService getFileListingService();
|
||||
|
||||
/**
|
||||
* Takes a screen shot of the device and returns it as a {@link RawImage}.
|
||||
* @return the screenshot as a <code>RawImage</code> or <code>null</code> if
|
||||
* something went wrong.
|
||||
* @throws IOException
|
||||
*/
|
||||
public RawImage getScreenshot() throws IOException;
|
||||
|
||||
/**
|
||||
* Executes a shell command on the device, and sends the result to a receiver.
|
||||
* @param command The command to execute
|
||||
* @param receiver The receiver object getting the result from the command.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void executeShellCommand(String command,
|
||||
IShellOutputReceiver receiver) throws IOException;
|
||||
|
||||
/**
|
||||
* Runs the event log service and outputs the event log to the {@link LogReceiver}.
|
||||
* @param receiver the receiver to receive the event log entries.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void runEventLogService(LogReceiver receiver) throws IOException;
|
||||
|
||||
/**
|
||||
* Creates a port forwarding between a local and a remote port.
|
||||
* @param localPort the local port to forward
|
||||
* @param remotePort the remote port.
|
||||
* @return <code>true</code> if success.
|
||||
*/
|
||||
public boolean createForward(int localPort, int remotePort);
|
||||
|
||||
/**
|
||||
* Removes a port forwarding between a local and a remote port.
|
||||
* @param localPort the local port to forward
|
||||
* @param remotePort the remote port.
|
||||
* @return <code>true</code> if success.
|
||||
*/
|
||||
public boolean removeForward(int localPort, int remotePort);
|
||||
|
||||
/**
|
||||
* Returns the name of the client by pid or <code>null</code> if pid is unknown
|
||||
* @param pid the pid of the client.
|
||||
*/
|
||||
public String getClientName(int pid);
|
||||
|
||||
}
|
||||
@@ -48,6 +48,8 @@ public final class NativeAllocationInfo {
|
||||
sAllocFunctionFilter.add("operator new"); //$NON-NLS-1$
|
||||
sAllocFunctionFilter.add("leak_free"); //$NON-NLS-1$
|
||||
sAllocFunctionFilter.add("chk_free"); //$NON-NLS-1$
|
||||
sAllocFunctionFilter.add("chk_memalign"); //$NON-NLS-1$
|
||||
sAllocFunctionFilter.add("Malloc"); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
private final int mSize;
|
||||
|
||||
@@ -228,6 +228,9 @@ public final class EventLogParser {
|
||||
// just ignore this description if data type and data unit don't match
|
||||
// TODO: log the error.
|
||||
}
|
||||
} else {
|
||||
Log.e("EventLogParser", //$NON-NLS-1$
|
||||
String.format("Can't parse %1$s", description)); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.ddmlib.testrunner;
|
||||
|
||||
/**
|
||||
* Listener for instrumentation test runs
|
||||
*
|
||||
* Modeled after junit.runner.TestRunListener
|
||||
*/
|
||||
public interface ITestRunListener {
|
||||
public static final int STATUS_ERROR = 1;
|
||||
public static final int STATUS_FAILURE = 2;
|
||||
|
||||
/**
|
||||
* Reports the start of a test run
|
||||
* @param testCount - total number of tests in test run
|
||||
* */
|
||||
public void testRunStarted(int testCount);
|
||||
|
||||
/**
|
||||
* Reports end of test run
|
||||
* @param elapsedTime - device reported elapsed time, in milliseconds
|
||||
*/
|
||||
public void testRunEnded(long elapsedTime);
|
||||
|
||||
/**
|
||||
* Reports test run stopped before completion
|
||||
* @param elapsedTime - device reported elapsed time, in milliseconds
|
||||
*/
|
||||
public void testRunStopped(long elapsedTime);
|
||||
|
||||
/**
|
||||
* Reports the start of an individual test case
|
||||
*/
|
||||
public void testStarted(String className, String testName);
|
||||
|
||||
/**
|
||||
* Reports the execution end of an individual test case
|
||||
* If no testFailed has been reported, this is a passed test
|
||||
*/
|
||||
public void testEnded(String className, String testName);
|
||||
|
||||
/**
|
||||
* Reports the failure of a individual test case
|
||||
* Will be called between testStarted and testEnded
|
||||
*
|
||||
* @param status - one of STATUS_ERROR, STATUS_FAILURE
|
||||
* @param className - name of test class
|
||||
* @param testName - name of test method
|
||||
* @param trace - stack trace of failure
|
||||
*/
|
||||
public void testFailed(int status, String className, String testName, String trace);
|
||||
|
||||
/**
|
||||
* Reports test run failed to execute due to a fatal error
|
||||
*/
|
||||
public void testRunFailed(String errorMessage);
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
* 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.ddmlib.testrunner;
|
||||
|
||||
import com.android.ddmlib.IShellOutputReceiver;
|
||||
import com.android.ddmlib.Log;
|
||||
import com.android.ddmlib.MultiLineReceiver;
|
||||
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Parses the 'raw output mode' results of an instrument test run from shell, and informs a
|
||||
* ITestRunListener of the results
|
||||
*
|
||||
* Expects the following output:
|
||||
*
|
||||
* If fatal error occurred when attempted to run the tests:
|
||||
* <i> INSTRUMENTATION_FAILED: </i>
|
||||
*
|
||||
* Otherwise, expect a series of test results, each one containing a set of status key/value
|
||||
* pairs, delimited by a start(1)/pass(0)/fail(-2)/error(-1) status code result. At end of test
|
||||
* run, expects that the elapsed test time in seconds will be displayed
|
||||
*
|
||||
* i.e.
|
||||
* <i>
|
||||
* INSTRUMENTATION_STATUS_CODE: 1
|
||||
* INSTRUMENTATION_STATUS: class=com.foo.FooTest
|
||||
* INSTRUMENTATION_STATUS: test=testFoo
|
||||
* INSTRUMENTATION_STATUS: numtests=2
|
||||
* INSTRUMENTATION_STATUS: stack=com.foo.FooTest#testFoo:312
|
||||
* com.foo.X
|
||||
* INSTRUMENTATION_STATUS_CODE: -2
|
||||
* ...
|
||||
*
|
||||
* Time: X
|
||||
* </i>
|
||||
*
|
||||
* Note that the "value" portion of the key-value pair may wrap over several text lines
|
||||
*/
|
||||
public class InstrumentationResultParser extends MultiLineReceiver {
|
||||
|
||||
// relevant test status keys
|
||||
private static final String CODE_KEY = "code";
|
||||
private static final String TEST_KEY = "test";
|
||||
private static final String CLASS_KEY = "class";
|
||||
private static final String STACK_KEY = "stack";
|
||||
private static final String NUMTESTS_KEY = "numtests";
|
||||
|
||||
// test result status codes
|
||||
private static final int FAILURE_STATUS_CODE = -2;
|
||||
private static final int START_STATUS_CODE = 1;
|
||||
private static final int ERROR_STATUS_CODE = -1;
|
||||
private static final int OK_STATUS_CODE = 0;
|
||||
|
||||
// recognized output patterns
|
||||
private static final String STATUS_PREFIX = "INSTRUMENTATION_STATUS: ";
|
||||
private static final String STATUS_PREFIX_CODE = "INSTRUMENTATION_STATUS_CODE: ";
|
||||
private static final String STATUS_FAILED = "INSTRUMENTATION_FAILED: ";
|
||||
private static final String TIME_REPORT = "Time: ";
|
||||
|
||||
private final ITestRunListener mTestListener;
|
||||
/** key-value map for current test */
|
||||
private Map<String, String> mStatusValues;
|
||||
/** stores the current "key" portion of the status key-value being parsed */
|
||||
private String mCurrentKey;
|
||||
/** stores the current "value" portion of the status key-value being parsed */
|
||||
private StringBuilder mCurrentValue;
|
||||
/** true if start of test has already been reported to listener */
|
||||
private boolean mTestStartReported;
|
||||
/** the elapsed time of the test run, in ms */
|
||||
private long mTestTime;
|
||||
/** true if current test run has been canceled by user */
|
||||
private boolean mIsCancelled;
|
||||
|
||||
private static final String LOG_TAG = "InstrumentationResultParser";
|
||||
|
||||
/**
|
||||
* Creates the InstrumentationResultParser
|
||||
* @param listener - listener to report results to. will be informed of test results as the
|
||||
* tests are executing
|
||||
*/
|
||||
public InstrumentationResultParser(ITestRunListener listener) {
|
||||
mStatusValues = new Hashtable<String, String>();
|
||||
mCurrentKey = null;
|
||||
setTrimLine(false);
|
||||
mTestListener = listener;
|
||||
mTestStartReported = false;
|
||||
mTestTime = 0;
|
||||
mIsCancelled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the instrumentation test output from shell
|
||||
* @see MultiLineReceiver#processNewLines
|
||||
*/
|
||||
@Override
|
||||
public void processNewLines(String[] lines) {
|
||||
for (String line : lines) {
|
||||
parse(line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an individual output line. Expects a line that either is:
|
||||
* a) the start of a new status line (ie. starts with STATUS_PREFIX or STATUS_PREFIX_CODE),
|
||||
* and thus there is a new key=value pair to parse, and the previous key-value pair is
|
||||
* finished
|
||||
* b) a continuation of the previous status (ie the "value" portion of the key has wrapped
|
||||
* to the next line.
|
||||
* c) a line reporting a fatal error in the test run (STATUS_FAILED)
|
||||
* d) a line reporting the total elapsed time of the test run.
|
||||
*
|
||||
* @param line - text output line
|
||||
*/
|
||||
private void parse(String line) {
|
||||
if (line.startsWith(STATUS_PREFIX_CODE)) {
|
||||
// Previous status key-value has been collected. Store it.
|
||||
submitCurrentKeyValue();
|
||||
parseStatusCode(line);
|
||||
} else if (line.startsWith(STATUS_PREFIX)) {
|
||||
// Previous status key-value has been collected. Store it.
|
||||
submitCurrentKeyValue();
|
||||
parseKey(line, STATUS_PREFIX.length());
|
||||
} else if (line.startsWith(STATUS_FAILED)) {
|
||||
Log.e(LOG_TAG, "test run failed " + line);
|
||||
mTestListener.testRunFailed(line);
|
||||
} else if (line.startsWith(TIME_REPORT)) {
|
||||
parseTime(line, TIME_REPORT.length());
|
||||
} else {
|
||||
if (mCurrentValue != null) {
|
||||
// this is a value that has wrapped to next line.
|
||||
mCurrentValue.append("\r\n");
|
||||
mCurrentValue.append(line);
|
||||
} else {
|
||||
Log.w(LOG_TAG, "unrecognized line " + line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the currently parsed key-value pair in the status map
|
||||
*/
|
||||
private void submitCurrentKeyValue() {
|
||||
if (mCurrentKey != null && mCurrentValue != null) {
|
||||
mStatusValues.put(mCurrentKey, mCurrentValue.toString());
|
||||
mCurrentKey = null;
|
||||
mCurrentValue = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the key from the current line
|
||||
* Expects format of "key=value",
|
||||
* @param line - full line of text to parse
|
||||
* @param keyStartPos - the starting position of the key in the given line
|
||||
*/
|
||||
private void parseKey(String line, int keyStartPos) {
|
||||
int endKeyPos = line.indexOf('=', keyStartPos);
|
||||
if (endKeyPos != -1) {
|
||||
mCurrentKey = line.substring(keyStartPos, endKeyPos).trim();
|
||||
parseValue(line, endKeyPos+1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the start of a key=value pair.
|
||||
* @param line - full line of text to parse
|
||||
* @param valueStartPos - the starting position of the value in the given line
|
||||
*/
|
||||
private void parseValue(String line, int valueStartPos) {
|
||||
mCurrentValue = new StringBuilder();
|
||||
mCurrentValue.append(line.substring(valueStartPos));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses out a status code result. For consistency, stores the result as a CODE entry in
|
||||
* key-value status map
|
||||
*/
|
||||
private void parseStatusCode(String line) {
|
||||
String value = line.substring(STATUS_PREFIX_CODE.length()).trim();
|
||||
mStatusValues.put(CODE_KEY, value);
|
||||
|
||||
// this means we're done with current test result bundle
|
||||
reportResult(mStatusValues);
|
||||
mStatusValues.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if test run canceled
|
||||
*
|
||||
* @see IShellOutputReceiver#isCancelled()
|
||||
*/
|
||||
public boolean isCancelled() {
|
||||
return mIsCancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests cancellation of test result parsing
|
||||
*/
|
||||
public void cancel() {
|
||||
mIsCancelled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports a test result to the test run listener. Must be called when a individual test
|
||||
* result has been fully parsed.
|
||||
* @param statusMap - key-value status pairs of test result
|
||||
*/
|
||||
private void reportResult(Map<String, String> statusMap) {
|
||||
String className = statusMap.get(CLASS_KEY);
|
||||
String testName = statusMap.get(TEST_KEY);
|
||||
String statusCodeString = statusMap.get(CODE_KEY);
|
||||
|
||||
if (className == null || testName == null || statusCodeString == null) {
|
||||
Log.e(LOG_TAG, "invalid instrumentation status bundle " + statusMap.toString());
|
||||
return;
|
||||
}
|
||||
className = className.trim();
|
||||
testName = testName.trim();
|
||||
|
||||
reportTestStarted(statusMap);
|
||||
|
||||
try {
|
||||
int statusCode = Integer.parseInt(statusCodeString);
|
||||
|
||||
switch (statusCode) {
|
||||
case START_STATUS_CODE:
|
||||
mTestListener.testStarted(className, testName);
|
||||
break;
|
||||
case FAILURE_STATUS_CODE:
|
||||
mTestListener.testFailed(ITestRunListener.STATUS_FAILURE, className, testName,
|
||||
getTrace(statusMap));
|
||||
mTestListener.testEnded(className, testName);
|
||||
break;
|
||||
case ERROR_STATUS_CODE:
|
||||
mTestListener.testFailed(ITestRunListener.STATUS_ERROR, className, testName,
|
||||
getTrace(statusMap));
|
||||
mTestListener.testEnded(className, testName);
|
||||
break;
|
||||
case OK_STATUS_CODE:
|
||||
mTestListener.testEnded(className, testName);
|
||||
break;
|
||||
default:
|
||||
Log.e(LOG_TAG, "Expected status code, received: " + statusCodeString);
|
||||
mTestListener.testEnded(className, testName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
Log.e(LOG_TAG, "Expected integer status code, received: " + statusCodeString);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports the start of a test run, and the total test count, if it has not been previously
|
||||
* reported
|
||||
* @param statusMap - key-value status pairs
|
||||
*/
|
||||
private void reportTestStarted(Map<String, String> statusMap) {
|
||||
// if start test run not reported yet
|
||||
if (!mTestStartReported) {
|
||||
String numTestsString = statusMap.get(NUMTESTS_KEY);
|
||||
if (numTestsString != null) {
|
||||
try {
|
||||
int numTests = Integer.parseInt(numTestsString);
|
||||
mTestListener.testRunStarted(numTests);
|
||||
mTestStartReported = true;
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
Log.e(LOG_TAG, "Unexpected numTests format " + numTestsString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stack trace of the current failed test, from the provided key-value status map
|
||||
*/
|
||||
private String getTrace(Map<String, String> statusMap) {
|
||||
String stackTrace = statusMap.get(STACK_KEY);
|
||||
if (stackTrace != null) {
|
||||
return stackTrace;
|
||||
}
|
||||
else {
|
||||
Log.e(LOG_TAG, "Could not find stack trace for failed test ");
|
||||
return new Throwable("Unknown failure").toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses out and store the elapsed time
|
||||
*/
|
||||
private void parseTime(String line, int startPos) {
|
||||
String timeString = line.substring(startPos);
|
||||
try {
|
||||
float timeSeconds = Float.parseFloat(timeString);
|
||||
mTestTime = (long)(timeSeconds * 1000);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
Log.e(LOG_TAG, "Unexpected time format " + timeString);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by parent when adb session is complete.
|
||||
*/
|
||||
@Override
|
||||
public void done() {
|
||||
super.done();
|
||||
mTestListener.testRunEnded(mTestTime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* 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.ddmlib.testrunner;
|
||||
|
||||
|
||||
import com.android.ddmlib.IDevice;
|
||||
import com.android.ddmlib.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Runs a Android test command remotely and reports results
|
||||
*/
|
||||
public class RemoteAndroidTestRunner {
|
||||
|
||||
private static final char CLASS_SEPARATOR = ',';
|
||||
private static final char METHOD_SEPARATOR = '#';
|
||||
private static final char RUNNER_SEPARATOR = '/';
|
||||
private String mClassArg;
|
||||
private final String mPackageName;
|
||||
private final String mRunnerName;
|
||||
private String mExtraArgs;
|
||||
private boolean mLogOnlyMode;
|
||||
private IDevice mRemoteDevice;
|
||||
private InstrumentationResultParser mParser;
|
||||
|
||||
private static final String LOG_TAG = "RemoteAndroidTest";
|
||||
private static final String DEFAULT_RUNNER_NAME =
|
||||
"android.test.InstrumentationTestRunner";
|
||||
|
||||
/**
|
||||
* Creates a remote android test runner.
|
||||
* @param packageName - the Android application package that contains the tests to run
|
||||
* @param runnerName - the instrumentation test runner to execute. If null, will use default
|
||||
* runner
|
||||
* @param remoteDevice - the Android device to execute tests on
|
||||
*/
|
||||
public RemoteAndroidTestRunner(String packageName,
|
||||
String runnerName,
|
||||
IDevice remoteDevice) {
|
||||
|
||||
mPackageName = packageName;
|
||||
mRunnerName = runnerName;
|
||||
mRemoteDevice = remoteDevice;
|
||||
mClassArg = null;
|
||||
mExtraArgs = "";
|
||||
mLogOnlyMode = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternate constructor. Uses default instrumentation runner
|
||||
* @param packageName - the Android application package that contains the tests to run
|
||||
* @param remoteDevice - the Android device to execute tests on
|
||||
*/
|
||||
public RemoteAndroidTestRunner(String packageName,
|
||||
IDevice remoteDevice) {
|
||||
this(packageName, null, remoteDevice);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the application package name
|
||||
*/
|
||||
public String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the runnerName
|
||||
*/
|
||||
public String getRunnerName() {
|
||||
if (mRunnerName == null) {
|
||||
return DEFAULT_RUNNER_NAME;
|
||||
}
|
||||
return mRunnerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the complete instrumentation component path
|
||||
*/
|
||||
private String getRunnerPath() {
|
||||
return getPackageName() + RUNNER_SEPARATOR + getRunnerName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets to run only tests in this class
|
||||
* Must be called before 'run'
|
||||
* @param className - fully qualified class name (eg x.y.z)
|
||||
*/
|
||||
public void setClassName(String className) {
|
||||
mClassArg = className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets to run only tests in the provided classes
|
||||
* Must be called before 'run'
|
||||
* If providing more than one class, requires a InstrumentationTestRunner that supports
|
||||
* the multiple class argument syntax
|
||||
* @param classNames - array of fully qualified class name (eg x.y.z)
|
||||
*/
|
||||
public void setClassNames(String[] classNames) {
|
||||
StringBuilder classArgBuilder = new StringBuilder();
|
||||
|
||||
for (int i=0; i < classNames.length; i++) {
|
||||
if (i != 0) {
|
||||
classArgBuilder.append(CLASS_SEPARATOR);
|
||||
}
|
||||
classArgBuilder.append(classNames[i]);
|
||||
}
|
||||
mClassArg = classArgBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets to run only specified test method
|
||||
* Must be called before 'run'
|
||||
* @param className - fully qualified class name (eg x.y.z)
|
||||
* @param testName - method name
|
||||
*/
|
||||
public void setMethodName(String className, String testName) {
|
||||
mClassArg = className + METHOD_SEPARATOR + testName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets extra arguments to include in instrumentation command.
|
||||
* Must be called before 'run'
|
||||
* @param instrumentationArgs - must not be null
|
||||
*/
|
||||
public void setExtraArgs(String instrumentationArgs) {
|
||||
if (instrumentationArgs == null) {
|
||||
throw new IllegalArgumentException("instrumentationArgs cannot be null");
|
||||
}
|
||||
mExtraArgs = instrumentationArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the extra instrumentation arguments
|
||||
*/
|
||||
public String getExtraArgs() {
|
||||
return mExtraArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this test run to log only mode - skips test execution
|
||||
*/
|
||||
public void setLogOnly(boolean logOnly) {
|
||||
mLogOnlyMode = logOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute this test run
|
||||
*
|
||||
* @param listener - listener to report results to
|
||||
*/
|
||||
public void run(ITestRunListener listener) {
|
||||
final String runCaseCommandStr = "am instrument -w -r "
|
||||
+ getClassCmd() + " " + getLogCmd() + " " + getExtraArgs() + " " + getRunnerPath();
|
||||
Log.d(LOG_TAG, runCaseCommandStr);
|
||||
mParser = new InstrumentationResultParser(listener);
|
||||
|
||||
try {
|
||||
mRemoteDevice.executeShellCommand(runCaseCommandStr, mParser);
|
||||
} catch (IOException e) {
|
||||
Log.e(LOG_TAG, e);
|
||||
listener.testRunFailed(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests cancellation of this test run
|
||||
*/
|
||||
public void cancel() {
|
||||
if (mParser != null) {
|
||||
mParser.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the test class argument
|
||||
*/
|
||||
private String getClassArg() {
|
||||
return mClassArg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full instrumentation command which specifies the test classes to execute.
|
||||
* Returns an empty string if no classes were specified
|
||||
*/
|
||||
private String getClassCmd() {
|
||||
String classArg = getClassArg();
|
||||
if (classArg != null) {
|
||||
return "-e class " + classArg;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full command to enable log only mode - if specified. Otherwise returns an
|
||||
* empty string
|
||||
*/
|
||||
private String getLogCmd() {
|
||||
if (mLogOnlyMode) {
|
||||
return "-e log true";
|
||||
}
|
||||
else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user