diff --git a/testrunner/test_defs.xml b/testrunner/test_defs.xml
index 6f073bbb1..55ddfdfa7 100644
--- a/testrunner/test_defs.xml
+++ b/testrunner/test_defs.xml
@@ -291,6 +291,13 @@ See test_defs.xsd for more information.
coverage_target="framework"
cts="true" />
+
+
-yyyyMMdd-HH:mm:ss.xml, into the
+ * directory out/-yyyyMMdd-HH:mm:ss with the contents:
+ *
+ *
+ *
+ * script_name="filename"
+ * monkeyRunnerVersion="0.2"
+ *
+ *
+ *
+ * ...
+ *
+ *
+ * dateTime="20090921-17:08:43"
+ *
+ *
+ *
+ * ...
+ *
+ * dateTime="20090921-17:09:44"
+ *
+ *
+ *
+ * ...
+ *
+ *
+ * And then zip it up with all the screenshots in the file: -yyyyMMdd-HH:mm:ss.zip.
+ */
+
+public class MonkeyRecorder {
+
+ // xml file to store output results in
+ private static String mXmlFilename;
+ private static FileWriter mXmlFile;
+ private static XMLWriter mXmlWriter;
+
+ // unique subdirectory to put results in (screenshots and xml file)
+ private static String mDirname;
+ private static List mScreenShotNames = new ArrayList();
+
+ // where we store all the results for all the script runs
+ private static final String ROOT_DIR = "out";
+
+ // for getting the date and time in now()
+ private static final SimpleDateFormat SIMPLE_DATE_TIME_FORMAT = new SimpleDateFormat("yyyyMMdd-HH:mm:ss");
+
+ /**
+ * Create a new MonkeyRecorder that records commands and zips up screenshots for submittal
+ *
+ * @param scriptName filepath of the monkey script we are running
+ */
+ public MonkeyRecorder(String scriptName) throws IOException {
+ // Create directory structure to store xml file, images and zips
+ File scriptFile = new File(scriptName);
+ scriptName = scriptFile.getName(); // Get rid of path
+ mDirname = ROOT_DIR + "/" + stripType(scriptName) + "-" + now();
+ new File(mDirname).mkdirs();
+
+ // Initialize xml file
+ mXmlFilename = stampFilename(stripType(scriptName) + ".xml");
+ initXmlFile(scriptName);
+ }
+
+ // Get the current date and time in a simple string format (used for timestamping filenames)
+ private static String now() {
+ return SIMPLE_DATE_TIME_FORMAT.format(Calendar.getInstance().getTime());
+ }
+
+ /**
+ * Initialize the xml file writer
+ *
+ * @param scriptName filename (not path) of the monkey script, stored as attribute in the xml file
+ */
+ private static void initXmlFile(String scriptName) throws IOException {
+ mXmlFile = new FileWriter(mDirname + "/" + mXmlFilename);
+ mXmlWriter = new XMLWriter(mXmlFile);
+ mXmlWriter.begin();
+ mXmlWriter.comment("Monkey Script Results");
+ mXmlWriter.start("script_run");
+ mXmlWriter.addAttribute("script_name", scriptName);
+ }
+
+ /**
+ * Add a comment to the xml file.
+ *
+ * @param comment comment to add to the xml file
+ */
+ public static void addComment(String comment) throws IOException {
+ mXmlWriter.comment(comment);
+ }
+
+ /**
+ * Begin writing a command xml element
+ */
+ public static void startCommand() throws IOException {
+ mXmlWriter.start("command");
+ mXmlWriter.addAttribute("dateTime", now());
+ }
+
+ /**
+ * Write a command name attribute in a command xml element.
+ * It's add as a sinlge script command could be multiple monkey commands.
+ *
+ * @param cmd command sent to the monkey
+ */
+ public static void addInput(String cmd) throws IOException {
+ String name = "cmd";
+ String value = cmd;
+ mXmlWriter.tag("input", name, value);
+ }
+
+ /**
+ * Write a response xml element in a command.
+ * Attributes include the monkey result, datetime, and possibly screenshot filename
+ *
+ * @param result response of the monkey to the command
+ * @param filename filename of the screen shot (or other file to be included)
+ */
+ public static void addResult(String result, String filename) throws IOException {
+ int num_args = 2;
+ String[] names = new String[3];
+ String[] values = new String[3];
+ names[0] = "result";
+ values[0] = result;
+ names[1] = "dateTime";
+ values[1] = now();
+ if (filename.length() != 0) {
+ names[2] = "screenshot";
+ values[2] = stampFilename(filename);
+ addScreenShot(filename);
+ num_args = 3;
+ }
+ mXmlWriter.tag("response", names, values, num_args);
+ }
+
+ /**
+ * Add an attribut to an xml element. name="escaped_value"
+ *
+ * @param name name of the attribute
+ * @param value value of the attribute
+ */
+ public static void addAttribute(String name, String value) throws IOException {
+ mXmlWriter.addAttribute(name, value);
+ }
+
+ /**
+ * Add an xml device variable element. name="escaped_value"
+ *
+ * @param name name of the variable
+ * @param value value of the variable
+ */
+ public static void addDeviceVar(String name, String value) throws IOException {
+ String[] names = {"name", "value"};
+ String[] values = {name, value};
+ mXmlWriter.tag("device_var", names, values, names.length);
+ }
+
+ /**
+ * Move the screenshot to storage and remember you did it so it can be zipped up later.
+ *
+ * @param filename file name of the screenshot to be stored (Not path name)
+ */
+ private static void addScreenShot(String filename) {
+ File file = new File(filename);
+ String screenShotName = stampFilename(filename);
+ file.renameTo(new File(mDirname, screenShotName));
+ mScreenShotNames.add(screenShotName);
+ }
+
+ /**
+ * Finish writing a command xml element
+ */
+ public static void endCommand() throws IOException {
+ mXmlWriter.end();
+ }
+
+ /**
+ * Add datetime in front of filetype (the stuff after and including the last infamous '.')
+ *
+ * @param filename path of file to be stamped
+ */
+ private static String stampFilename(String filename) {
+ //
+ int typeIndex = filename.lastIndexOf('.');
+ if (typeIndex == -1) {
+ return filename + "-" + now();
+ }
+ return filename.substring(0, typeIndex) + "-" + now() + filename.substring(typeIndex);
+ }
+
+ /**
+ * Strip out the file type (the stuff after and including the last infamous '.')
+ *
+ * @param filename path of file to be stripped of type information
+ */
+ private static String stripType(String filename) {
+ //
+ int typeIndex = filename.lastIndexOf('.');
+ if (typeIndex == -1)
+ return filename;
+ return filename.substring(0, typeIndex);
+ }
+
+ /**
+ * Add a signature element
+ *
+ * @param filename path of file to be signatured
+ */
+ private static void addMD5Signature(String filename) throws IOException {
+ String signature = "";
+ // find signature... MD5 sig = new MD5(filename); signature = sig.toString();
+ String[] names = new String[] { "type", "filename", "signature" };
+ String[] values = new String[] { "md5", filename, signature };
+ mXmlWriter.tag("Signature", names, values, values.length);
+ }
+
+
+ /**
+ * Close the monkeyRecorder by closing the xml file and zipping it up with the screenshots.
+ *
+ * @param filename path of file to be stripped of type information
+ */
+ public static void close() throws IOException {
+ // zip up xml file and screenshots into ROOT_DIR.
+ byte[] buf = new byte[1024];
+ String zipFileName = mXmlFilename + ".zip";
+ endCommand();
+ mXmlFile.close();
+ FileOutputStream zipFile = new FileOutputStream(ROOT_DIR + "/" + zipFileName);
+ ZipOutputStream out = new ZipOutputStream(zipFile);
+
+ // add the xml file
+ addFileToZip(out, mDirname + "/" + mXmlFilename, buf);
+
+ // Add the screenshots
+ for (String filename : mScreenShotNames) {
+ addFileToZip(out, mDirname + "/" + filename, buf);
+ }
+ out.close();
+ }
+
+ /**
+ * Helper function to zip up a file into an open zip archive.
+ *
+ * @param zip the stream of the zip archive
+ * @param filepath the filepath of the file to be added to the zip archive
+ * @param buf storage place to stage reads of file before zipping
+ */
+ private static void addFileToZip(ZipOutputStream zip, String filepath, byte[] buf) throws IOException {
+ FileInputStream in = new FileInputStream(filepath);
+ zip.putNextEntry(new ZipEntry(filepath));
+ int len;
+ while ((len = in.read(buf)) > 0) {
+ zip.write(buf, 0, len);
+ }
+ zip.closeEntry();
+ in.close();
+ }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java
index cbc881c6c..07a473917 100644
--- a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java
@@ -30,18 +30,21 @@ import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import javax.imageio.ImageIO;
/**
* MonkeyRunner is a host side application to control a monkey instance on a
- * device. MonkeyRunner provides some useful helper functions to control the
- * device as well as various other methods to help script tests.
+ * device. MonkeyRunner provides some useful helper functions to control the
+ * device as well as various other methods to help script tests.
*/
public class MonkeyRunner {
@@ -50,27 +53,46 @@ public class MonkeyRunner {
static Socket monkeySocket = null;
static IDevice monkeyDevice;
-
+
static BufferedReader monkeyReader;
static BufferedWriter monkeyWriter;
+ static String monkeyResponse;
+
+ static MonkeyRecorder monkeyRecorder;
static String scriptName = null;
+
+ // Obtain a suitable logger.
+ private static Logger logger = Logger.getLogger("com.android.monkeyrunner");
// delay between key events
final static int KEY_INPUT_DELAY = 1000;
+
+ // version of monkey runner
+ final static String monkeyRunnerVersion = "0.31";
- public static void main(String[] args) {
+ // TODO: interface cmd; class xml tags; fix logger; test class/script
+ public static void main(String[] args) throws IOException {
+
+ // haven't figure out how to get below INFO...bad parent. Pass -v INFO to turn on logging
+ logger.setLevel(Level.parse("WARNING"));
processOptions(args);
+ logger.info("initAdb");
initAdbConnection();
+ logger.info("openMonkeyConnection");
openMonkeyConnection();
+ logger.info("start_script");
start_script();
+ logger.info("ScriptRunner.run");
ScriptRunner.run(scriptName);
+ logger.info("end_script");
end_script();
+ logger.info("closeMonkeyConnection");
closeMonkeyConnection();
}
@@ -166,13 +188,15 @@ public class MonkeyRunner {
try {
InetAddress addr = InetAddress.getByName(monkeyServer);
monkeySocket = new Socket(addr, monkeyPort);
+ monkeyWriter = new BufferedWriter(new OutputStreamWriter(monkeySocket.getOutputStream()));
+ monkeyReader = new BufferedReader(new InputStreamReader(monkeySocket.getInputStream()));
} catch (UnknownHostException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
}
}
-
+
/**
* Close tcp session with the monkey on the device
*
@@ -189,47 +213,59 @@ public class MonkeyRunner {
}
/**
- * This is a house cleaning type routine to run before starting a script. Puts
- * the device in a known state.
+ * This is a house cleaning routine to run before starting a script. Puts
+ * the device in a known state and starts recording interesting info.
*/
- public static void start_script() {
- press("menu");
- press("menu");
- press("home");
+ public static void start_script() throws IOException {
+ press("menu", false);
+ press("menu", false);
+ press("home", false);
+
+ // Start recording the script output, might want md5 signature of file for completeness
+ monkeyRecorder = new MonkeyRecorder(scriptName);
+
+ // Record what device and version of software we are running on
+ monkeyRecorder.addAttribute("monkeyRunnerVersion", monkeyRunnerVersion);
+ addDeviceVars();
+ monkeyRecorder.addComment("Script commands");
}
- public static void end_script() {
- String command = "END";
- sendMonkeyEvent(command);
+ /**
+ * This is a house cleaning routine to run after finishing a script.
+ * Puts the monkey server in a known state and closes the recording.
+ */
+ public static void end_script() throws IOException {
+ String command = "done";
+ sendMonkeyEvent(command, false, false);
+
+ // Stop the recording and zip up the results
+ monkeyRecorder.close();
}
/** This is a method for scripts to launch an activity on the device
*
* @param name The name of the activity to launch
*/
- public static void launch_activity(String name) {
- try {
- System.out.println("Launching: " + name);
- monkeyDevice.executeShellCommand("am start -a android.intent.action.MAIN -n "
- + name, new NullOutputReceiver());
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
+ public static void launch_activity(String name) throws IOException {
+ System.out.println("Launching: " + name);
+ recordCommand("Launching: " + name);
+ monkeyDevice.executeShellCommand("am start -a android.intent.action.MAIN -n "
+ + name, new NullOutputReceiver());
+ // void return, so no response given, just close the command element in the xml file.
+ monkeyRecorder.endCommand();
+ }
/**
* Grabs the current state of the screen stores it as a png
*
* @param tag filename or tag descriptor of the screenshot
*/
- public static void grabscreen(String tag) {
+ public static void grabscreen(String tag) throws IOException {
tag += ".png";
try {
Thread.sleep(1000);
getDeviceImage(monkeyDevice, tag, false);
- } catch (IOException e) {
- e.printStackTrace();
} catch (InterruptedException e) {
}
}
@@ -239,9 +275,11 @@ public class MonkeyRunner {
*
* @param msec msecs to sleep for
*/
- public static void sleep(int msec) {
+ public static void sleep(int msec) throws IOException {
try {
+ recordCommand("sleep: " + msec);
Thread.sleep(msec);
+ recordResponse("OK");
} catch (InterruptedException e) {
e.printStackTrace();
}
@@ -253,12 +291,10 @@ public class MonkeyRunner {
* @param x x-coordinate
* @param y y-coordinate
*/
- public static boolean tap(int x, int y) {
- String command = "touch down " + x + " " + y + "\r\n" +
- "touch up " + x + " " + y + "\r\n";
-
- System.out.println("Tapping: " + x + ", " + y);
- return sendMonkeyEvent(command);
+ public static boolean tap(int x, int y) throws IOException {
+ String command = "tap " + x + " " + y;
+ boolean result = sendMonkeyEvent(command);
+ return result;
}
/**
@@ -266,25 +302,33 @@ public class MonkeyRunner {
*
* @param key key to press
*/
- public static boolean press(String key) {
- String command = "key down " + key + "\r\n" +
- "key up " + key + "\r\n";
+ public static boolean press(String key) throws IOException {
+ return press(key, true);
+ }
- System.out.println("Pressing: " + key);
- return sendMonkeyEvent(command);
+ /**
+ * Press function for scripts to call on a particular button or key
+ *
+ * @param key key to press
+ * @param print whether to send output to user
+ */
+ private static boolean press(String key, boolean print) throws IOException {
+ String command = "press " + key;
+ boolean result = sendMonkeyEvent(command, print, true);
+ return result;
}
/**
* dpad down function
*/
- public static boolean down() {
+ public static boolean down() throws IOException {
return press("dpad_down");
}
/**
* dpad up function
*/
- public static boolean up() {
+ public static boolean up() throws IOException {
return press("dpad_up");
}
@@ -293,69 +337,173 @@ public class MonkeyRunner {
*
* @param text text to type
*/
- public static boolean type(String text) {
- System.out.println("Typing: " + text);
- for (int i=0; i 2) {
+ monkeyRecorder.addDeviceVar(name, monkeyResponse.substring(3));
+ } else {
+ // only got OK - good variable but no value
+ monkeyRecorder.addDeviceVar(name, "null");
+ }
+ } else {
+ // error returned - couldn't get var value for name... include error return
+ monkeyRecorder.addDeviceVar(name, monkeyResponse);
+ }
+ } else {
+ // no monkeyResponse - bad variable with no value
+ monkeyRecorder.addDeviceVar(name, "null");
+ }
+ }
+ } else {
+ // it's an error, can't find variable names...
+ monkeyRecorder.addAttribute("listvar", monkeyResponse);
+ }
+ }
+
/**
* Process the command-line options
*
* @return Returns true if options were parsed with no apparent errors.
*/
- public static void processOptions(String[] args) {
+ private static void processOptions(String[] args) {
// parse command line parameters.
int index = 0;
@@ -364,7 +512,7 @@ public class MonkeyRunner {
if ("-s".equals(argument)) {
if(index == args.length) {
- printAndExit("Missing Server after -s", false);
+ printUsageAndQuit("Missing Server after -s");
}
monkeyServer = args[index++];
@@ -372,18 +520,32 @@ public class MonkeyRunner {
} else if ("-p".equals(argument)) {
// quick check on the next argument.
if (index == args.length) {
- printAndExit("Missing Server IP after -p", false /* terminate */);
+ printUsageAndQuit("Missing Server port after -p");
}
monkeyPort = Integer.parseInt(args[index++]);
- } else {
- // get the filepath of the script to run.
- scriptName = argument;
- // should not be any other device.
- //if (index < args.length) {
- // printAndExit("Too many arguments!", false /* terminate */);
- //}
+ } else if ("-v".equals(argument)) {
+ // quick check on the next argument.
+ if (index == args.length) {
+ printUsageAndQuit("Missing Log Level after -v");
+ }
+
+ Level level = Level.parse(args[index++]);
+ logger.setLevel(level);
+ level = logger.getLevel();
+ System.out.println("Log level set to: " + level + "(" + level.intValue() + ").");
+ System.out.println("Warning: Log levels below INFO(800) not working currently... parent issues");
+
+ } else if (argument.startsWith("-")) {
+ // we have an unrecognized argument.
+ printUsageAndQuit("Unrecognized argument: " + argument + ".");
+
+ monkeyPort = Integer.parseInt(args[index++]);
+
+ } else {
+ // get the filepath of the script to run. This will be the last undashed argument.
+ scriptName = argument;
}
} while (index < args.length);
}
@@ -394,22 +556,29 @@ public class MonkeyRunner {
private static void getDeviceImage(IDevice device, String filepath, boolean landscape)
throws IOException {
RawImage rawImage;
+ recordCommand("grabscreen");
+ System.out.println("Grabbing Screeshot: " + filepath + ".");
try {
rawImage = device.getScreenshot();
}
catch (IOException ioe) {
+ recordResponse("No frame buffer", "");
printAndExit("Unable to get frame buffer: " + ioe.getMessage(), true /* terminate */);
return;
}
// device/adb not available?
- if (rawImage == null)
+ if (rawImage == null) {
+ recordResponse("No image", "");
return;
-
+ }
+
assert rawImage.bpp == 16;
BufferedImage image;
+
+ logger.info("Raw Image - height: " + rawImage.height + ", width: " + rawImage.width);
if (landscape) {
// convert raw data to an Image
@@ -458,16 +627,20 @@ public class MonkeyRunner {
}
if (!ImageIO.write(image, "png", new File(filepath))) {
+ recordResponse("No png writer", "");
throw new IOException("Failed to find png writer");
}
+ recordResponse("OK", filepath);
}
- private static void printUsageAndQuit() {
+ private static void printUsageAndQuit(String message) {
// 80 cols marker: 01234567890123456789012345678901234567890123456789012345678901234567890123456789
+ System.out.println(message);
System.out.println("Usage: monkeyrunner [options] SCRIPT_FILE");
System.out.println("");
System.out.println(" -s MonkeyServer IP Address.");
System.out.println(" -p MonkeyServer TCP Port.");
+ System.out.println(" -v MonkeyServer Logging level (ALL, FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE, OFF)");
System.out.println("");
System.out.println("");