Add functional tests for brillopad

Change-Id: I1faae3d9e55c223d5b090682205f4e3b26a134fe
diff --git a/src/com/android/tradefed/util/brillopad/LogcatParser.java b/src/com/android/tradefed/util/brillopad/LogcatParser.java
index fa4f84f..9dac4bf 100644
--- a/src/com/android/tradefed/util/brillopad/LogcatParser.java
+++ b/src/com/android/tradefed/util/brillopad/LogcatParser.java
@@ -16,10 +16,15 @@
 package com.android.tradefed.util.brillopad;
 
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.InputStreamSource;
 import com.android.tradefed.util.ArrayUtil;
 import com.android.tradefed.util.brillopad.item.GenericLogcatItem;
 import com.android.tradefed.util.brillopad.item.LogcatItem;
 
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
@@ -92,6 +97,14 @@
     private LinkedList<String> mRingBuffer = new LinkedList<String>();
     private String mYear = null;
 
+    LogcatItem mLogcat = new LogcatItem();
+
+    Map<String, LogcatData> mDataMap = new HashMap<String, LogcatData>();
+    List<LogcatData> mDataList = new LinkedList<LogcatData>();
+
+    private Date mStartTime = null;
+    private Date mStopTime = null;
+
     /**
      * Constructor for {@link LogcatParser}.
      */
@@ -117,92 +130,130 @@
     }
 
     /**
+     * Parse a logcat from a {@link BufferedReader} into an {@link LogcatItem} object.
+     *
+     * @param input a {@link BufferedReader}.
+     * @return The {@link LogcatItem}.
+     * @see #parse(List)
+     */
+    public LogcatItem parse(BufferedReader input) throws IOException {
+        String line;
+        while ((line = input.readLine()) != null) {
+            parseLine(line);
+        }
+        commit();
+
+        return mLogcat;
+    }
+
+    /**
+     * Parse a logcat from a {@link InputStreamSource} into an {@link LogcatItem} object.
+     *
+     * @param input a {@link InputStreamSource}.
+     * @return The {@link LogcatItem}.
+     * @see #parse(List)
+     */
+    public LogcatItem parse(InputStreamSource input) throws IOException {
+        InputStream stream = input.createInputStream();
+        return parse(new BufferedReader(new InputStreamReader(stream)));
+    }
+
+    /**
      * {@inheritDoc}
      *
      * @return The {@link LogcatItem}.
      */
     public LogcatItem parse(List<String> lines) {
-        LogcatItem logcat = new LogcatItem();
-
-        Map<String, LogcatData> dataMap = new HashMap<String, LogcatData>();
-        List<LogcatData> dataList = new LinkedList<LogcatData>();
-
-        Date startTime = null;
-        Date stopTime = null;
-
         for (String line : lines) {
-            Integer pid = null;
-            Integer tid = null;
-            Date time = null;
-            String level = null;
-            String tag = null;
-            String msg = null;
+            parseLine(line);
+        }
+        commit();
 
-            Matcher m = THREADTIME_LINE.matcher(line);
-            Matcher tm = TIME_LINE.matcher(line);
-            if (m.matches()) {
-                time = parseTime(m.group(1));
-                pid = Integer.parseInt(m.group(2));
-                tid = Integer.parseInt(m.group(3));
-                level = m.group(4);
-                tag = m.group(5);
-                msg = m.group(6);
-            } else if (tm.matches()) {
-                time = parseTime(tm.group(1));
-                level = tm.group(2);
-                tag = tm.group(3);
-                pid = Integer.parseInt(tm.group(4));
-                msg = tm.group(5);
-            } else {
-                CLog.w("Failed to parse line '%s'", line);
-                continue;
-            }
+        return mLogcat;
+    }
 
-            if (startTime == null) {
-                startTime = time;
-            }
-            stopTime = time;
+    /**
+     * Parse a line of input.
+     *
+     * @param line The line to parse
+     */
+    private void parseLine(String line) {
+        Integer pid = null;
+        Integer tid = null;
+        Date time = null;
+        String level = null;
+        String tag = null;
+        String msg = null;
 
-            // ANRs are split when START matches a line.  The newest entry is kept in the dataMap
-            // for quick lookup while all entries are added to the list.
-            if ("E".equals(level) && "ActivityManager".equals(tag)) {
-                String key = encodeLine(pid, tid, level, tag);
-                LogcatData data;
-                if (!dataMap.containsKey(key) || AnrParser.START.matcher(msg).matches()) {
-                    data = new LogcatData(pid, tid, time, level, tag, getLastPreamble(),
-                            getProcPreamble(pid));
-                    dataMap.put(key, data);
-                    dataList.add(data);
-                } else {
-                    data = dataMap.get(key);
-                }
-                data.mLines.add(msg);
-            }
-
-            // PID and TID are enough to separate Java and native crashes.
-            if (("E".equals(level) && "AndroidRuntime".equals(tag)) ||
-                    ("I".equals(level) && "DEBUG".equals(tag))) {
-                String key = encodeLine(pid, tid, level, tag);
-                LogcatData data;
-                if (!dataMap.containsKey(key)) {
-                    data = new LogcatData(pid, tid, time, level, tag, getLastPreamble(),
-                            getProcPreamble(pid));
-                    dataMap.put(key, data);
-                    dataList.add(data);
-                } else {
-                    data = dataMap.get(key);
-                }
-                data.mLines.add(msg);
-            }
-
-            // After parsing the line, add it the the buffer for the preambles.
-            mRingBuffer.add(line);
-            if (mRingBuffer.size() > MAX_BUFF_SIZE) {
-                mRingBuffer.removeFirst();
-            }
+        Matcher m = THREADTIME_LINE.matcher(line);
+        Matcher tm = TIME_LINE.matcher(line);
+        if (m.matches()) {
+            time = parseTime(m.group(1));
+            pid = Integer.parseInt(m.group(2));
+            tid = Integer.parseInt(m.group(3));
+            level = m.group(4);
+            tag = m.group(5);
+            msg = m.group(6);
+        } else if (tm.matches()) {
+            time = parseTime(tm.group(1));
+            level = tm.group(2);
+            tag = tm.group(3);
+            pid = Integer.parseInt(tm.group(4));
+            msg = tm.group(5);
+        } else {
+            CLog.w("Failed to parse line '%s'", line);
+            return;
         }
 
-        for (LogcatData data : dataList) {
+        if (mStartTime == null) {
+            mStartTime = time;
+        }
+        mStopTime = time;
+
+        // ANRs are split when START matches a line.  The newest entry is kept in the dataMap
+        // for quick lookup while all entries are added to the list.
+        if ("E".equals(level) && "ActivityManager".equals(tag)) {
+            String key = encodeLine(pid, tid, level, tag);
+            LogcatData data;
+            if (!mDataMap.containsKey(key) || AnrParser.START.matcher(msg).matches()) {
+                data = new LogcatData(pid, tid, time, level, tag, getLastPreamble(),
+                        getProcPreamble(pid));
+                mDataMap.put(key, data);
+                mDataList.add(data);
+            } else {
+                data = mDataMap.get(key);
+            }
+            data.mLines.add(msg);
+        }
+
+        // PID and TID are enough to separate Java and native crashes.
+        if (("E".equals(level) && "AndroidRuntime".equals(tag)) ||
+                ("I".equals(level) && "DEBUG".equals(tag))) {
+            String key = encodeLine(pid, tid, level, tag);
+            LogcatData data;
+            if (!mDataMap.containsKey(key)) {
+                data = new LogcatData(pid, tid, time, level, tag, getLastPreamble(),
+                        getProcPreamble(pid));
+                mDataMap.put(key, data);
+                mDataList.add(data);
+            } else {
+                data = mDataMap.get(key);
+            }
+            data.mLines.add(msg);
+        }
+
+        // After parsing the line, add it the the buffer for the preambles.
+        mRingBuffer.add(line);
+        if (mRingBuffer.size() > MAX_BUFF_SIZE) {
+            mRingBuffer.removeFirst();
+        }
+    }
+
+    /**
+     * Signal that the input has finished.
+     */
+    private void commit() {
+        for (LogcatData data : mDataList) {
             GenericLogcatItem item = null;
             if ("E".equals(data.mLevel) && "ActivityManager".equals(data.mTag)) {
                 CLog.v("Parsing ANR: %s", data.mLines);
@@ -220,13 +271,12 @@
                 item.setTid(data.mTid);
                 item.setLastPreamble(data.mLastPreamble);
                 item.setProcessPreamble(data.mProcPreamble);
-                logcat.addEvent(item);
+                mLogcat.addEvent(item);
             }
         }
 
-        logcat.setStartTime(startTime);
-        logcat.setStopTime(stopTime);
-        return logcat;
+        mLogcat.setStartTime(mStartTime);
+        mLogcat.setStopTime(mStopTime);
     }
 
     /**
diff --git a/src/com/android/tradefed/util/brillopad/item/MonkeyLogItem.java b/src/com/android/tradefed/util/brillopad/item/MonkeyLogItem.java
index 41ceb10..ddaab8f 100644
--- a/src/com/android/tradefed/util/brillopad/item/MonkeyLogItem.java
+++ b/src/com/android/tradefed/util/brillopad/item/MonkeyLogItem.java
@@ -236,7 +236,7 @@
     /**
      * Get if the monkey run finished without crashing.
      */
-    public boolean isFinished() {
+    public boolean getIsFinished() {
         return (Boolean) getAttribute(IS_FINISHED);
     }
 
diff --git a/tests/src/com/android/tradefed/util/brillopad/BrillopadFuncTests.java b/tests/src/com/android/tradefed/util/brillopad/BrillopadFuncTests.java
new file mode 100644
index 0000000..091a803
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/brillopad/BrillopadFuncTests.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2011 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.tradefed.util.brillopad;
+
+import com.android.tradefed.testtype.DeviceTestSuite;
+
+import junit.framework.Test;
+
+/**
+ * A test suite for the Brillopad functional tests.
+ * <p>
+ * In order to run this suite, there must be a bugreport, logcat, and monkey log in
+ * {@code /tmp/bugreport.txt}, {@code /tmp/logcat.txt}, and {@code /tmp/monkey_log.txt}.
+ * </p>
+ */
+public class BrillopadFuncTests extends DeviceTestSuite {
+
+    public BrillopadFuncTests() {
+        super();
+
+        addTestSuite(BugreportParserFuncTest.class);
+        addTestSuite(LogcatParserFuncTest.class);
+        addTestSuite(MonkeyLogParserFuncTest.class);
+    }
+
+    public static Test suite() {
+        return new BrillopadFuncTests();
+    }
+}
diff --git a/tests/src/com/android/tradefed/util/brillopad/BugreportParserFuncTest.java b/tests/src/com/android/tradefed/util/brillopad/BugreportParserFuncTest.java
index 9258f26..793e61b 100644
--- a/tests/src/com/android/tradefed/util/brillopad/BugreportParserFuncTest.java
+++ b/tests/src/com/android/tradefed/util/brillopad/BugreportParserFuncTest.java
@@ -15,76 +15,84 @@
  */
 package com.android.tradefed.util.brillopad;
 
+import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.InputStreamSource;
 import com.android.tradefed.result.SnapshotInputStreamSource;
-import com.android.tradefed.util.brillopad.item.AnrItem;
 import com.android.tradefed.util.brillopad.item.BugreportItem;
-import com.android.tradefed.util.brillopad.item.JavaCrashItem;
-import com.android.tradefed.util.brillopad.item.NativeCrashItem;
 
 import junit.framework.TestCase;
 
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.InputStream;
-import java.util.List;
+import java.io.FileNotFoundException;
+import java.io.IOException;
 
 /**
  * Functional tests for {@link BugreportParser}
  */
 public class BugreportParserFuncTest extends TestCase {
-    private static final File BR_FILE = new File("/tmp/bugreport.txt");
-
-/* FIXME: flesh this out with known bugreports
-    public void disable_testFullMockedParse() throws Exception {
-        BugreportParser parser = new BugreportParser();
-        ISectionParser mockSP = EasyMock.createStrictMock(ISectionParser.class);
-        parser.addSectionParser(mockSP, "------ MEMORY INFO .*");
-
-        // Set up expectations
-        EasyMock.expect(mockSP.parseBlock((List<String>)EasyMock.anyObject())).andReturn(null);
-        EasyMock.replay(mockSP);
-
-        if (!BR_FILE.exists()) {
-            fail(String.format("Full Parse test requires sample bugreport at %s", BR_FILE));
-        }
-
-        BufferedReader reader = new BufferedReader(new FileReader(BR_FILE));
-        parser.parse(reader);
-        EasyMock.verify(mockSP);
-    }
-*/
+    // FIXME: Make bugreport file configurable.
+    private static final String BUGREPORT_PATH = "/tmp/bugreport.txt";
 
     /**
-     * A "test" that is intended to force Brillopad to parse a bugreport found at {@code BR_FILE}.
-     * The purpose of this is to assist a developer in checking why a given bugreport file might not
-     * be parsed correctly by Brillopad.
-     * <p />
-     * Because the name doesn't start with "test", this method won't be picked up automatically by a
-     * JUnit test runner, and must be run by hand.  This is as intended.
+     * A test that is intended to force Brillopad to parse a bugreport. The purpose of this is to
+     * assist a developer in checking why a given bugreport file might not be parsed correctly by
+     * Brillopad.
      */
-    public void manualTestFullParse() throws Exception {
-        BugreportParser parser = new BugreportParser();
-
-        if (!BR_FILE.exists()) {
-            fail(String.format("Full Parse test requires sample bugreport at %s", BR_FILE));
+    public void testParse() {
+        InputStreamSource bugSource = null;
+        try {
+            bugSource = new SnapshotInputStreamSource(new FileInputStream(
+                    new File(BUGREPORT_PATH)));
+        } catch (FileNotFoundException e) {
+            fail(String.format("File not found at %s", BUGREPORT_PATH));
         }
-
-        InputStream bugStream = new FileInputStream(BR_FILE);
-        InputStreamSource bugSource = new SnapshotInputStreamSource(bugStream);
         BugreportItem bugreport = null;
         try {
-            bugreport = parser.parse(bugSource);
+            long start = System.currentTimeMillis();
+            bugreport = new BugreportParser().parse(bugSource);
+            long stop = System.currentTimeMillis();
+            CLog.e("Bugreport took %d ms to parse.", stop - start);
+        } catch (IOException e) {
+            fail(String.format("IOException: %s", e.toString()));
         } finally {
             bugSource.cancel();
         }
 
-        List<JavaCrashItem> jc = bugreport.getSystemLog().getJavaCrashes();
-        List<NativeCrashItem> nc = bugreport.getSystemLog().getNativeCrashes();
-        List<AnrItem> anr = bugreport.getSystemLog().getAnrs();
-        System.err.format("Got %d items for JAVA CRASH, %d for NATIVE CRASH, and %d for ANR\n",
-                jc.size(), nc.size(), anr.size());
+        assertNotNull(bugreport);
+        assertNotNull(bugreport.getTime());
 
+        assertNotNull(bugreport.getSystemProps());
+        assertTrue(bugreport.getSystemProps().size() > 0);
+
+        assertNotNull(bugreport.getMemInfo());
+        assertTrue(bugreport.getMemInfo().size() > 0);
+
+        assertNotNull(bugreport.getProcrank());
+        assertTrue(bugreport.getProcrank().size() > 0);
+
+        assertNotNull(bugreport.getSystemLog());
+        assertNotNull(bugreport.getSystemLog().getStartTime());
+        assertNotNull(bugreport.getSystemLog().getStopTime());
+
+        CLog.e("Stats for bugreport:\n" +
+                "  Time: %s\n" +
+                "  System Properties: %d items\n" +
+                "  Mem info: %d items\n" +
+                "  Procrank: %d items\n" +
+                "  System Log:\n" +
+                "    Start time: %s\n" +
+                "    Stop time: %s\n" +
+                "    %d ANR(s), %d Java Crash(es), %d Native Crash(es)",
+                bugreport.getTime(),
+                bugreport.getSystemProps().size(),
+                bugreport.getMemInfo().size(),
+                bugreport.getProcrank().size(),
+                bugreport.getSystemLog().getStartTime().toString(),
+                bugreport.getSystemLog().getStopTime().toString(),
+                bugreport.getSystemLog().getAnrs().size(),
+                bugreport.getSystemLog().getJavaCrashes().size(),
+                bugreport.getSystemLog().getNativeCrashes().size());
     }
 }
 
diff --git a/tests/src/com/android/tradefed/util/brillopad/LogcatParserFuncTest.java b/tests/src/com/android/tradefed/util/brillopad/LogcatParserFuncTest.java
new file mode 100644
index 0000000..521c9d4
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/brillopad/LogcatParserFuncTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2011 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.tradefed.util.brillopad;
+
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.result.SnapshotInputStreamSource;
+import com.android.tradefed.util.brillopad.item.LogcatItem;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * Functional tests for {@link LogcatParser}
+ */
+public class LogcatParserFuncTest extends TestCase {
+    // FIXME: Make logcat file configurable.
+    private static final String LOGCAT_PATH = "/tmp/logcat.txt";
+
+    /**
+     * A test that is intended to force Brillopad to parse a logcat. The purpose of this is to
+     * assist a developer in checking why a given logcat file might not be parsed correctly by
+     * Brillopad.
+     */
+    public void testParse() {
+        InputStreamSource logcatSource = null;
+        try {
+            logcatSource = new SnapshotInputStreamSource(new FileInputStream(
+                    new File(LOGCAT_PATH)));
+        } catch (FileNotFoundException e) {
+            fail(String.format("File not found at %s", LOGCAT_PATH));
+        }
+        LogcatItem logcat = null;
+        try {
+            long start = System.currentTimeMillis();
+            logcat = new LogcatParser().parse(logcatSource);
+            long stop = System.currentTimeMillis();
+            CLog.e("Logcat took %d ms to parse.", stop - start);
+        } catch (IOException e) {
+            fail(String.format("IOException: %s", e.toString()));
+        } finally {
+            logcatSource.cancel();
+        }
+
+        assertNotNull(logcat);
+        assertNotNull(logcat.getStartTime());
+        assertNotNull(logcat.getStopTime());
+
+        CLog.e("Stats for logcat:\n" +
+                "  Start time: %s\n" +
+                "  Stop time: %s\n" +
+                "  %d ANR(s), %d Java Crash(es), %d Native Crash(es)",
+                logcat.getStartTime().toString(),
+                logcat.getStopTime().toString(),
+                logcat.getAnrs().size(),
+                logcat.getJavaCrashes().size(),
+                logcat.getNativeCrashes().size());
+    }
+}
+
diff --git a/tests/src/com/android/tradefed/util/brillopad/MonkeyLogParserFuncTest.java b/tests/src/com/android/tradefed/util/brillopad/MonkeyLogParserFuncTest.java
new file mode 100644
index 0000000..40a326a
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/brillopad/MonkeyLogParserFuncTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2011 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.tradefed.util.brillopad;
+
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.result.SnapshotInputStreamSource;
+import com.android.tradefed.util.brillopad.item.AnrItem;
+import com.android.tradefed.util.brillopad.item.JavaCrashItem;
+import com.android.tradefed.util.brillopad.item.MonkeyLogItem;
+import com.android.tradefed.util.brillopad.item.MonkeyLogItem.DroppedCategory;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * Functional tests for {@link MonkeyLogParser}
+ */
+public class MonkeyLogParserFuncTest extends TestCase {
+    // FIXME: Make monkey log file configurable.
+    private static final String MONKEY_LOG_PATH = "/tmp/monkey_log.txt";
+
+    /**
+     * A test that is intended to force Brillopad to parse a monkey log. The purpose of this is to
+     * assist a developer in checking why a given monkey log file might not be parsed correctly by
+     * Brillopad.
+     */
+    public void testParse() {
+        InputStreamSource monkeyLogSource = null;
+        try {
+            monkeyLogSource = new SnapshotInputStreamSource(new FileInputStream(
+                    new File(MONKEY_LOG_PATH)));
+        } catch (FileNotFoundException e) {
+            fail(String.format("File not found at %s", MONKEY_LOG_PATH));
+        }
+        MonkeyLogItem monkeyLog = null;
+        try {
+            long start = System.currentTimeMillis();
+            monkeyLog = new MonkeyLogParser().parse(monkeyLogSource);
+            long stop = System.currentTimeMillis();
+            CLog.e("Monkey log took %d ms to parse.", stop - start);
+        } catch (IOException e) {
+            fail(String.format("IOException: %s", e.toString()));
+        } finally {
+            monkeyLogSource.cancel();
+        }
+
+        assertNotNull(monkeyLog);
+        assertNotNull(monkeyLog.getStartTime());
+        assertNotNull(monkeyLog.getStopTime());
+        assertNotNull(monkeyLog.getTargetCount());
+        assertNotNull(monkeyLog.getThrottle());
+        assertNotNull(monkeyLog.getSeed());
+        assertNotNull(monkeyLog.getIgnoreSecurityExceptions());
+        assertTrue(monkeyLog.getPackages().size() > 0);
+        assertTrue(monkeyLog.getCategories().size() > 0);
+        assertNotNull(monkeyLog.getIsFinished());
+        assertNotNull(monkeyLog.getIntermediateCount());
+        assertNotNull(monkeyLog.getTotalDuration());
+        assertNotNull(monkeyLog.getStartUptimeDuration());
+        assertNotNull(monkeyLog.getStopUptimeDuration());
+
+
+        StringBuffer sb = new StringBuffer();
+        sb.append("Stats for monkey log:\n");
+        sb.append(String.format("  Start time: %s\n", monkeyLog.getStartTime()));
+        sb.append(String.format("  Stop time: %s\n", monkeyLog.getStopTime()));
+        sb.append(String.format("  Parameters: target-count=%d, throttle=%d, seed=%d, " +
+                "ignore-security-exceptions=%b\n",
+                monkeyLog.getTargetCount(), monkeyLog.getThrottle(), monkeyLog.getSeed(),
+                monkeyLog.getIgnoreSecurityExceptions()));
+        sb.append(String.format("  Packages: %s\n", monkeyLog.getPackages()));
+        sb.append(String.format("  Categories: %s\n", monkeyLog.getCategories()));
+        sb.append(String.format("  Status: finished=%b, final-count=%d, intermediate-count=%d\n",
+                monkeyLog.getIsFinished(), monkeyLog.getFinalCount(),
+                monkeyLog.getIntermediateCount()));
+        sb.append(String.format("  Run time: duration=%d ms, delta-uptime=%d (%d - %d) ms\n",
+                monkeyLog.getTotalDuration(),
+                monkeyLog.getStopUptimeDuration() - monkeyLog.getStartUptimeDuration(),
+                monkeyLog.getStopUptimeDuration(), monkeyLog.getStartUptimeDuration()));
+
+        sb.append("  Dropped events:");
+        for (DroppedCategory drop : DroppedCategory.values()) {
+            sb.append(String.format(" %s=%d,", drop.toString(), monkeyLog.getDroppedCount(drop)));
+        }
+        sb.deleteCharAt(sb.length()-1);
+        sb.append("\n");
+
+        if (monkeyLog.getCrash() != null && monkeyLog.getCrash() instanceof AnrItem) {
+            sb.append(String.format("  Stopped due to ANR\n"));
+        }
+        if (monkeyLog.getCrash() != null && monkeyLog.getCrash() instanceof JavaCrashItem) {
+            sb.append(String.format("  Stopped due to Java crash\n"));
+        }
+        CLog.e(sb.toString());
+    }
+}
+
diff --git a/tests/src/com/android/tradefed/util/brillopad/MonkeyLogParserTest.java b/tests/src/com/android/tradefed/util/brillopad/MonkeyLogParserTest.java
index 3f81812..1ca03ac 100644
--- a/tests/src/com/android/tradefed/util/brillopad/MonkeyLogParserTest.java
+++ b/tests/src/com/android/tradefed/util/brillopad/MonkeyLogParserTest.java
@@ -87,7 +87,7 @@
         assertEquals(4 * 60 * 1000 + 57 * 1000, monkeyLog.getTotalDuration().longValue());
         assertEquals(242130, monkeyLog.getStartUptimeDuration().longValue());
         assertEquals(539210, monkeyLog.getStopUptimeDuration().longValue());
-        assertTrue(monkeyLog.isFinished());
+        assertTrue(monkeyLog.getIsFinished());
         assertEquals(9900, monkeyLog.getIntermediateCount());
         assertEquals(10000, monkeyLog.getFinalCount().intValue());
         assertEquals(5, monkeyLog.getDroppedCount(DroppedCategory.KEYS).intValue());
@@ -191,7 +191,7 @@
         assertEquals(4 * 60 * 1000 + 14 * 1000, monkeyLog.getTotalDuration().longValue());
         assertEquals(216480, monkeyLog.getStartUptimeDuration().longValue());
         assertEquals(471370, monkeyLog.getStopUptimeDuration().longValue());
-        assertFalse(monkeyLog.isFinished());
+        assertFalse(monkeyLog.getIsFinished());
         assertEquals(5300, monkeyLog.getIntermediateCount());
         assertEquals(5322, monkeyLog.getFinalCount().intValue());
         assertNotNull(monkeyLog.getCrash());
@@ -262,7 +262,7 @@
         assertEquals(49 * 1000, monkeyLog.getTotalDuration().longValue());
         assertEquals(232650, monkeyLog.getStartUptimeDuration().longValue());
         assertEquals(282530, monkeyLog.getStopUptimeDuration().longValue());
-        assertFalse(monkeyLog.isFinished());
+        assertFalse(monkeyLog.getIsFinished());
         assertEquals(1600, monkeyLog.getIntermediateCount());
         assertEquals(1649, monkeyLog.getFinalCount().intValue());
         assertNotNull(monkeyLog.getCrash());