Create a hierarchical Brillopad parser for Android's bugreport format

Currently includes parsers for the following Bugreport sections:
Memory Info
Proc Rank
System Log (picks out ANRs, Java crashes, and Native crashes)
System Properties

It's been end-to-end tested against bugreports from a variety of
current devices, as well as an ANR bugreport from kila (Dream) running
Donut Burger build 3890, circa June, 2009.

Change-Id: I08d56aaab01278c5e52c2b9cf6e4c84b6fb2cebe
diff --git a/src/com/android/tradefed/util/brillopad/AbstractBlockParser.java b/src/com/android/tradefed/util/brillopad/AbstractBlockParser.java
new file mode 100644
index 0000000..30a8fc0
--- /dev/null
+++ b/src/com/android/tradefed/util/brillopad/AbstractBlockParser.java
@@ -0,0 +1,52 @@
+/*
+ * 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.util.brillopad.ItemList;
+import com.android.tradefed.util.brillopad.ILineParser;
+
+import java.util.List;
+
+/**
+ * A shim class that implements IBlockParser by calling ILineParser methods
+ */
+public abstract class AbstractBlockParser implements IBlockParser, ILineParser {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void parseBlock(List<String> input, ItemList itemlist) {
+        for (String line : input) {
+            if (line == null) {
+                CLog.w("Encountered unexpected null line; skipping...");
+                continue;
+            }
+            parseLine(line, itemlist);
+        }
+
+        // signal EOF
+        commit(itemlist);
+    }
+
+    /**
+     * Parses a single {@link String}
+     *
+     * @param line
+     */
+    abstract public void parseLine(String line, ItemList itemlist);
+}
diff --git a/src/com/android/tradefed/util/brillopad/AbstractParser.java b/src/com/android/tradefed/util/brillopad/AbstractParser.java
deleted file mode 100644
index 12b9f0b..0000000
--- a/src/com/android/tradefed/util/brillopad/AbstractParser.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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 java.io.BufferedReader;
-import java.io.IOException;
-
-/**
- * Abstract parser to implement common methods of all parsers.
- */
-public abstract class AbstractParser implements IParser {
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void parse(BufferedReader input) throws IOException {
-        String line;
-        while ((line = input.readLine()) != null) {
-            parse(line);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void parse(String[] input) throws IOException {
-        for (String line : input) {
-            if (line == null) {
-                throw new IOException("Encountered a null line");
-            }
-            parse(line);
-        }
-    }
-}
diff --git a/src/com/android/tradefed/util/brillopad/BugreportParser.java b/src/com/android/tradefed/util/brillopad/BugreportParser.java
new file mode 100644
index 0000000..b39025e
--- /dev/null
+++ b/src/com/android/tradefed/util/brillopad/BugreportParser.java
@@ -0,0 +1,69 @@
+/*
+ * 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.util.RegexTrie;
+import com.android.tradefed.util.brillopad.IBlockParser;
+import com.android.tradefed.util.brillopad.ItemList;
+import com.android.tradefed.util.brillopad.section.AbstractSectionParser;
+import com.android.tradefed.util.brillopad.section.MemInfoParser;
+import com.android.tradefed.util.brillopad.section.NoopSectionParser;
+import com.android.tradefed.util.brillopad.section.ProcRankParser;
+import com.android.tradefed.util.brillopad.section.SystemLogParser;
+import com.android.tradefed.util.brillopad.section.SystemPropParser;
+import com.android.tradefed.util.brillopad.section.syslog.AnrParser;
+import com.android.tradefed.util.brillopad.section.syslog.JavaCrashParser;
+import com.android.tradefed.util.brillopad.section.syslog.NativeCrashParser;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+
+/**
+ * A brillopad parser that understands the Android bugreport format
+ */
+public class BugreportParser extends AbstractSectionParser {
+    // For convenience, since these will likely be the most common events that people are
+    // interested in.
+    public static final String ANR = AnrParser.SECTION_NAME;
+    public static final String JAVA_CRASH = JavaCrashParser.SECTION_NAME;
+    public static final String NATIVE_CRASH = NativeCrashParser.SECTION_NAME;
+
+    /**
+     * Parse a bugreport file into a {@link ItemList} object.  This is the entrance method to the
+     * parser.
+     */
+    public void parse(BufferedReader input, ItemList itemlist) throws IOException {
+        String line;
+        while ((line = input.readLine()) != null) {
+            parseLine(line, itemlist);
+        }
+
+        // signal EOF
+        commit(itemlist);
+    }
+
+    @Override
+    public void addDefaultSectionParsers(RegexTrie<IBlockParser> sectionTrie) {
+        sectionTrie.put(new MemInfoParser(), MemInfoParser.SECTION_REGEX);
+        sectionTrie.put(new ProcRankParser(), ProcRankParser.SECTION_REGEX);
+        sectionTrie.put(new SystemPropParser(), SystemPropParser.SECTION_REGEX);
+        sectionTrie.put(new SystemLogParser(), SystemLogParser.SECTION_REGEX);
+
+        // Add a default section parser so that the Trie will commit prior sections
+        sectionTrie.put(new NoopSectionParser(), NoopSectionParser.SECTION_REGEX);
+    }
+}
+
diff --git a/src/com/android/tradefed/util/brillopad/IBlockParser.java b/src/com/android/tradefed/util/brillopad/IBlockParser.java
new file mode 100644
index 0000000..f4a0b7e
--- /dev/null
+++ b/src/com/android/tradefed/util/brillopad/IBlockParser.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+// note: import used for javadoc
+import com.android.tradefed.util.brillopad.item.IItem;
+
+import java.util.List;
+
+/**
+ * This interface defines the behavior for a block-oriented parser.  The parser will receive a block
+ * of input that it should consider complete.  It should do whatever is necessary to parse the input
+ * and commit the parsed data as arbitrarily many {@link IItem} instances in the passed
+ * {@link ItemList}.  Furthermore, the parser should be robust against invalid input -- the input
+ * format may drift over time.
+ */
+public interface IBlockParser {
+
+    /**
+     * Parses a block of input, and stores the parsed {@link IItem}s in the passed {@link ItemList}.
+     * <p/>
+     * This method will be called at most once per parser instance.
+     */
+    public void parseBlock(List<String> input, ItemList itemlist);
+}
+
diff --git a/src/com/android/tradefed/util/brillopad/ILineParser.java b/src/com/android/tradefed/util/brillopad/ILineParser.java
new file mode 100644
index 0000000..bba94f1
--- /dev/null
+++ b/src/com/android/tradefed/util/brillopad/ILineParser.java
@@ -0,0 +1,47 @@
+/*
+ * 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.util.brillopad.ItemList;
+
+import java.util.List;
+
+/**
+ * This interface defines the behavior for a line-oriented parser.  The parser should parse one
+ * line at a time, keeping state internally or stashing it in the {@link ItemList} as desired, and
+ * should interpret a {@code commit} call to indicate the end of input.
+ */
+public interface ILineParser {
+    /**
+     * parseList should take a line of input, do whatever is needed to parse it, and then either
+     * store the results in internal state or commit them to the passed {@link ItemList}.
+     * <p />
+     * The parser should robustly handle parse errors and make a best effort to return as much
+     * useful data as possible.
+     *
+     * @param line the line to parse
+     * @param itemlist the itemlist in which to store any permanent state
+     */
+    public void parseLine(String line, ItemList itemlist);
+
+    /**
+     * A signal that input is finished.
+     *
+     * @param itemlist the itemlist in which to store any permanent state
+     */
+    public void commit(ItemList itemlist);
+}
+
diff --git a/src/com/android/tradefed/util/brillopad/IParser.java b/src/com/android/tradefed/util/brillopad/IParser.java
deleted file mode 100644
index 37e2f70..0000000
--- a/src/com/android/tradefed/util/brillopad/IParser.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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 java.io.BufferedReader;
-import java.io.IOException;
-
-/**
- * Interface for all parsers.
- *
- * <p>
- * Parsers are meant to be used only once and contain state.  This means that an input can be split
- * up and parsed by the appropriate parse method without any affect on the parsing.  For example,
- * a buffered reader containing 10 lines can be broken into 10 individual lines with
- * {@link IParser#parse(String)} being run 10 times and that will produce the same state as
- * {@link IParser#parse(BufferedReader)} being run once with the original buffered reader.
- * </p>
- */
-public interface IParser {
-
-    /**
-     * Parses an {@link java.io.BufferedReader} object.
-     *
-     * @param input The {@link java.io.BufferedReader} object
-     * @throws IOException If there was a problem reading the Buffered reader
-     */
-    public void parse(BufferedReader input) throws IOException;
-
-    /**
-     * Parsers an array of {@link java.lang.String} objects.
-     *
-     * @param input The {@link java.lang.String} array.
-     * @throws IOException If any of the strings are null.
-     */
-    public void parse(String[] input) throws IOException;
-
-    /**
-     * Parses a single {@link java.lang.String}
-     *
-     * @param line
-     */
-    public void parse(String line);
-}
diff --git a/src/com/android/tradefed/util/brillopad/ItemList.java b/src/com/android/tradefed/util/brillopad/ItemList.java
new file mode 100644
index 0000000..fcfd8e8
--- /dev/null
+++ b/src/com/android/tradefed/util/brillopad/ItemList.java
@@ -0,0 +1,73 @@
+/*
+ * 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.util.brillopad.item.IItem;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A class to represent the contents of an Android bugreport
+ */
+public class ItemList {
+
+    private List<IItem> mItems = new LinkedList<IItem>();
+
+    public void addItem(IItem item) {
+        if (item == null) {
+            throw new NullPointerException();
+        }
+        mItems.add(item);
+    }
+
+    public List<IItem> getItems() {
+        return mItems;
+    }
+
+    public List<IItem> getByType(String regex) {
+        return getByType(Pattern.compile(regex));
+    }
+
+    public List<IItem> getByType(Pattern filter) {
+        // FIXME: implement this in a way that takes sublinear time
+        List<IItem> results = new LinkedList<IItem>();
+
+        for (IItem item : mItems) {
+            String section = item.getType();
+            if (section == null) {
+                continue;
+            }
+            Matcher m = filter.matcher(section);
+            if (m.matches()) {
+                results.add(item);
+            }
+        }
+
+        return results;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        return mItems.toString();
+    }
+}
+
diff --git a/src/com/android/tradefed/util/brillopad/item/AbstractItem.java b/src/com/android/tradefed/util/brillopad/item/AbstractItem.java
index dc0ea21..a3b1565 100644
--- a/src/com/android/tradefed/util/brillopad/item/AbstractItem.java
+++ b/src/com/android/tradefed/util/brillopad/item/AbstractItem.java
@@ -70,6 +70,14 @@
     }
 
     /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getType() {
+        return null;
+    }
+
+    /**
      * {@inhertiDoc}
      */
     @Override
diff --git a/src/com/android/tradefed/util/brillopad/item/GenericMapItem.java b/src/com/android/tradefed/util/brillopad/item/GenericMapItem.java
new file mode 100644
index 0000000..8ad1008
--- /dev/null
+++ b/src/com/android/tradefed/util/brillopad/item/GenericMapItem.java
@@ -0,0 +1,66 @@
+/*
+ * 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.item;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * An IItem that just represents a simple key/value map
+ */
+public class GenericMapItem<K, V> extends HashMap<K,V> implements IItem {
+    private String mType = null;
+
+    /**
+     * No-op zero-arg constructor
+     */
+    public GenericMapItem() {}
+
+    /**
+     * Convenience constructor that sets the type
+     */
+    public GenericMapItem(String type) {
+        setType(type);
+    }
+
+    /**
+     * Set the self-reported type that this {@link GenericMapItem} represents.
+     */
+    public void setType(String type) {
+        mType = type;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getType() {
+        return mType;
+    }
+
+    @Override
+    public IItem merge(IItem other) throws ConflictingItemException {
+        // FIXME
+        return (IItem)this;
+    }
+
+    @Override
+    public boolean isConsistent(IItem other) {
+        // FIXME
+        return true;
+    }
+}
+
diff --git a/src/com/android/tradefed/util/brillopad/item/IItem.java b/src/com/android/tradefed/util/brillopad/item/IItem.java
index d64514c..47bd71a 100644
--- a/src/com/android/tradefed/util/brillopad/item/IItem.java
+++ b/src/com/android/tradefed/util/brillopad/item/IItem.java
@@ -17,15 +17,15 @@
 
 /**
  * Interface for all items that are created by any parser.
- *
- * <p>
- * Items may contain one or more items.  For example, a Bugreport item will contain a Logcat item,
- * which itself might contain ANR, JavaCrash, and NativeCrash items.
- * </p>
  */
 public interface IItem {
 
     /**
+     * Determine what type this IItem represents.  May return {@code null}
+     */
+    public String getType();
+
+    /**
      * Merges the item and another into an item with the most complete information.
      *
      * <p>
diff --git a/src/com/android/tradefed/util/brillopad/item/NativeCrash.java b/src/com/android/tradefed/util/brillopad/item/NativeCrash.java
index d144133..dd823dc 100644
--- a/src/com/android/tradefed/util/brillopad/item/NativeCrash.java
+++ b/src/com/android/tradefed/util/brillopad/item/NativeCrash.java
@@ -21,7 +21,7 @@
  * Item to hold information associated with native crashes.
  */
 public final class NativeCrash extends AbstractLogcatItem {
-    private static final String[] ALLOWED_ATTRIBUTES = {"stack"};
+    private static final String[] ALLOWED_ATTRIBUTES = {"stack", "fingerprint", "app"};
 
     public NativeCrash() {
         super(ALLOWED_ATTRIBUTES);
diff --git a/src/com/android/tradefed/util/brillopad/section/AbstractSectionParser.java b/src/com/android/tradefed/util/brillopad/section/AbstractSectionParser.java
new file mode 100644
index 0000000..54a538d
--- /dev/null
+++ b/src/com/android/tradefed/util/brillopad/section/AbstractSectionParser.java
@@ -0,0 +1,125 @@
+/*
+ * 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.section;
+
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.RegexTrie;
+import com.android.tradefed.util.brillopad.AbstractBlockParser;
+import com.android.tradefed.util.brillopad.ItemList;
+import com.android.tradefed.util.brillopad.IBlockParser;
+import com.android.tradefed.util.brillopad.ILineParser;
+import com.android.tradefed.util.brillopad.section.NoopSectionParser;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A line-oriented parser that splits an input file into discrete sections and passes each section
+ * to an {@link IBlockParser} to parse.
+ */
+public abstract class AbstractSectionParser extends AbstractBlockParser implements ILineParser {
+    private RegexTrie<IBlockParser> mSectionTrie = new RegexTrie<IBlockParser>();
+    private IBlockParser mCurrentParser;
+    private List<String> mParseBlock = new LinkedList<String>();
+
+    /**
+     * Default constructor: use {@link NoopSectionParser} as the initial section parser.
+     */
+    public AbstractSectionParser() {
+        this(new NoopSectionParser());
+    }
+
+    /**
+     * Use the passed {@link IBlockParser} as the initial section parser.
+     *
+     * @param defaultParser the IBlockParser to use
+     */
+    public AbstractSectionParser(IBlockParser defaultParser) {
+        mCurrentParser = defaultParser;
+
+        addDefaultSectionParsers(mSectionTrie);
+    }
+
+    /**
+     * A method to be overridden by subclasses that allows them to specify the parsers to use for
+     * the different sections of the file.  It is assumed that a new section will start at a
+     * pattern that is recognizable with a single-line regular expression, and that a given section
+     * only ends when the subsequent section begins.
+     * <p />
+     * Parsers can skip sections by specifying that they be parsed by {@link NoopSectionParser}.
+     */
+    public abstract void addDefaultSectionParsers(RegexTrie<IBlockParser> sectionParsers);
+
+    /**
+     * A method to add a given section parser to the set of potential parsers to use.
+     * <p />
+     * @param parser The {@link IBlockParser} to add
+     * @param startPattern The regular expression to trigger this parser on
+    */
+    public void addSectionParser(IBlockParser parser, String startPattern) {
+        mSectionTrie.put(parser, startPattern);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void parseLine(String line, ItemList itemlist) {
+        IBlockParser nextParser = mSectionTrie.retrieve(line);
+
+        if (nextParser == null) {
+            // no match, so buffer this for the current parser, if there is one
+            if (mCurrentParser != null) {
+                mParseBlock.add(line);
+            } else {
+                CLog.w("Line outside of parsed section: %s", line);
+            }
+        } else {
+            // Switching parsers.  Send the parse block to the current parser and then rotate
+            if (mCurrentParser != null) {
+                mCurrentParser.parseBlock(mParseBlock, itemlist);
+                if (!(mCurrentParser instanceof NoopSectionParser)) {
+                    CLog.v("Just ran the parser; itemlist is now: %s", itemlist);
+                }
+            }
+            mParseBlock.clear();
+
+            // This stanza is all just debug
+            String prev = null;
+            String next = nextParser.getClass().getSimpleName();
+            if (mCurrentParser != null) {
+                prev = mCurrentParser.getClass().getSimpleName();
+            }
+            CLog.v("Switching parsers from %s to %s for line %s", prev, next, line);
+            mCurrentParser = nextParser;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void commit(ItemList itemlist) {
+        CLog.w("commit!");
+        if (mCurrentParser != null) {
+            mCurrentParser.parseBlock(mParseBlock, itemlist);
+            if (!(mCurrentParser instanceof NoopSectionParser)) {
+                CLog.v("Just committed; itemlist is now: %s", itemlist);
+            }
+        }
+    }
+}
+
diff --git a/src/com/android/tradefed/util/brillopad/section/MemInfoParser.java b/src/com/android/tradefed/util/brillopad/section/MemInfoParser.java
new file mode 100644
index 0000000..74a8225
--- /dev/null
+++ b/src/com/android/tradefed/util/brillopad/section/MemInfoParser.java
@@ -0,0 +1,58 @@
+/*
+ * 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.section;
+
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.brillopad.ItemList;
+import com.android.tradefed.util.brillopad.IBlockParser;
+import com.android.tradefed.util.brillopad.item.GenericMapItem;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A {@link ILineParser} to handle the Memory Info section of the bugreport
+ */
+public class MemInfoParser implements IBlockParser {
+    public static final String SECTION_NAME = "MEMORY INFO";
+    public static final String SECTION_REGEX = "------ MEMORY INFO .*";
+
+    /** Match a single MemoryInfo line, such as "MemFree:           65420 kB" */
+    private static final Pattern INFO_LINE = Pattern.compile("^([^:]+):\\s+(\\d+) kB");
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void parseBlock(List<String> block, ItemList itemlist) {
+        GenericMapItem<String, Integer> output =
+                new GenericMapItem<String, Integer>(SECTION_NAME);
+
+        for (String line : block) {
+            Matcher m = INFO_LINE.matcher(line);
+            if (m.matches()) {
+                String key = m.group(1);
+                Integer value = Integer.parseInt(m.group(2));
+                output.put(key, value);
+            } else {
+                CLog.w("Failed to parse line '%s'", line);
+            }
+        }
+        itemlist.addItem(output);
+    }
+}
+
diff --git a/src/com/android/tradefed/util/brillopad/section/NoopSectionParser.java b/src/com/android/tradefed/util/brillopad/section/NoopSectionParser.java
new file mode 100644
index 0000000..e66f061
--- /dev/null
+++ b/src/com/android/tradefed/util/brillopad/section/NoopSectionParser.java
@@ -0,0 +1,35 @@
+/*
+ * 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.section;
+
+import com.android.tradefed.util.brillopad.ItemList;
+import com.android.tradefed.util.brillopad.IBlockParser;
+
+import java.util.List;
+
+/**
+ * A Section Parser intended to consume unknown sections of the bugreport
+ */
+public class NoopSectionParser implements IBlockParser {
+    public static final String SECTION_NAME = "unknown";
+    public static final String SECTION_REGEX = "------ .*";
+
+    @Override
+    public void parseBlock(List<String> block, ItemList itemlist) {
+        // ignore
+    }
+}
+
diff --git a/src/com/android/tradefed/util/brillopad/section/ProcRankParser.java b/src/com/android/tradefed/util/brillopad/section/ProcRankParser.java
new file mode 100644
index 0000000..ad88156
--- /dev/null
+++ b/src/com/android/tradefed/util/brillopad/section/ProcRankParser.java
@@ -0,0 +1,99 @@
+/*
+ * 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.section;
+
+import com.android.tradefed.util.brillopad.ItemList;
+import com.android.tradefed.util.brillopad.IBlockParser;
+import com.android.tradefed.util.brillopad.item.GenericMapItem;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A {@link ILineParser} to handle the Procrank section of the bugreport.  Memory values returned
+ * are in units of kiloBytes
+ */
+public class ProcRankParser implements IBlockParser {
+    public static final String SECTION_NAME = "PROCRANK";
+    public static final String SECTION_REGEX = "------ PROCRANK .*";
+
+    private int mNumFields = -1;
+    private String[] mFieldNames = null;
+
+    /** Match a memory amount, such as "12345K" */
+    private static final Pattern NUMBER_PAT = Pattern.compile("(\\d+)([BKMGbkmg])?");
+
+    /**
+     * A utility function to parse a memory amount, such as "12345K", and return the number of
+     * kiloBytes that the amount represents.
+     */
+    private static Integer parseMem(String val) {
+        Integer count = null;
+        Matcher m = NUMBER_PAT.matcher(val);
+        if (m.matches()) {
+            count = Integer.parseInt(m.group(1));
+            String suffix = m.group(2);
+            if (suffix == null) {
+                return count;
+            }
+            suffix = suffix.toLowerCase();
+            if ("b".equals(suffix)) {
+                count /= 1024;
+            } else if ("k".equals(suffix)) {
+                // nothing to do
+            } else if ("m".equals(suffix)) {
+                count *= 1024;
+            } else if ("g".equals(suffix)) {
+                count *= 1024 * 1024;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void parseBlock(List<String> block, ItemList itemlist) {
+        GenericMapItem<String, Map<String, Integer>> output =
+                new GenericMapItem<String, Map<String, Integer>>(SECTION_NAME);
+
+        for (String line : block) {
+            // Trim leading whitespace so that split() works properly
+            line = line.replaceFirst("^\\s+", "");
+            if (mFieldNames == null) {
+                // try to parse a header
+                mFieldNames = line.split("\\s+");
+                mNumFields = mFieldNames.length;
+                continue;
+            }
+
+            String[] fields = line.split("\\s+", mNumFields);
+            String cmdline = fields[fields.length - 1];
+            Map<String, Integer> valueMap = new HashMap<String, Integer>();
+            for (int i = 0; i < mNumFields - 1 && i < fields.length; ++i) {
+                // FIXME: it's not correct to send PID through this, but in practice it works
+                valueMap.put(mFieldNames[i], parseMem(fields[i]));
+            }
+            output.put(cmdline, valueMap);
+        }
+        itemlist.addItem(output);
+    }
+}
+
diff --git a/src/com/android/tradefed/util/brillopad/section/SystemLogParser.java b/src/com/android/tradefed/util/brillopad/section/SystemLogParser.java
new file mode 100644
index 0000000..03da047
--- /dev/null
+++ b/src/com/android/tradefed/util/brillopad/section/SystemLogParser.java
@@ -0,0 +1,110 @@
+/*
+ * 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.section;
+
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.brillopad.ItemList;
+import com.android.tradefed.util.brillopad.IBlockParser;
+import com.android.tradefed.util.brillopad.section.syslog.AnrParser;
+import com.android.tradefed.util.brillopad.section.syslog.ISyslogParser;
+import com.android.tradefed.util.brillopad.section.syslog.JavaCrashParser;
+import com.android.tradefed.util.brillopad.section.syslog.NativeCrashParser;
+
+import java.util.List;
+import java.util.ListIterator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * An {@link IBlockParser} to handle the System Log section of the bugreport
+ */
+public class SystemLogParser implements IBlockParser {
+    public static final String SECTION_NAME = "SYSTEM LOG";
+    public static final String SECTION_REGEX = "------ SYSTEM LOG .*";
+
+    private ISyslogParser mJava = new JavaCrashParser();
+    private ISyslogParser mNative = new NativeCrashParser();
+    private ISyslogParser mAnr = new AnrParser();
+
+    /**
+     * Match a single line of `logcat -v threadtime`, such as:
+     * 05-26 11:02:36.886  5689  5689 D AndroidRuntime: CheckJNI is OFF
+     */
+    private static final Pattern THREADTIME_LINE = Pattern.compile(
+            "^(\\d{2})-(\\d{2}) (\\d{2}:\\d{2}:\\d{2}.\\d{3})\\s+" +  /* timestamp [1-3] */
+                "(\\d+)\\s+(\\d+)\\s+([A-Z])\\s+" +  /* pid/tid and log level [4-6] */
+                "(.+?)\\s*: (.*)$" /* tag and message [7-8]*/);
+
+    /**
+     * Match a single line of `logcat -v time`, such as:
+     * 06-04 02:32:14.002 D/dalvikvm(  236): GC_CONCURRENT freed 580K, 51% free [...]
+     */
+    private static final Pattern TIME_LINE = Pattern.compile(
+            "^(\\d{2})-(\\d{2}) (\\d{2}:\\d{2}:\\d{2}.\\d{3})\\s+" +  /* timestamp [1-3] */
+                "(\\w)/(.+?)\\(\\s*(\\d+)\\): (.*)$");  /* level, tag, pid, msg [4-7] */
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void parseBlock(List<String> block, ItemList itemlist) {
+        ListIterator<String> iter = block.listIterator();
+
+        while (iter.hasNext()) {
+            String line = iter.next();
+            int pid = 0;
+            int tid = 0;
+            String level = null;
+            String tag = null;
+            String msg = null;
+
+            // FIXME: do something with timestamps
+            Matcher m = THREADTIME_LINE.matcher(line);
+            Matcher tm = TIME_LINE.matcher(line);
+            if (m.matches()) {
+                pid = Integer.parseInt(m.group(4));
+                tid = Integer.parseInt(m.group(5));
+                level = m.group(6);
+                tag = m.group(7);
+                msg = m.group(8);
+            } else if (tm.matches()) {
+                level = tm.group(4);
+                tag = tm.group(5);
+                pid = Integer.parseInt(tm.group(6));
+                msg = tm.group(7);
+            } else {
+                CLog.w("Failed to parse line '%s'", line);
+                continue;
+            }
+
+            Class nextParserClass = null;
+            if ("I".equals(level) && "DEBUG".equals(tag)) {
+                // Native crash
+                mNative.parseLine(tid, pid, msg, itemlist);
+            } else if ("E".equals(level) && "AndroidRuntime".equals(tag)) {
+                // Java crash
+                mJava.parseLine(tid, pid, msg, itemlist);
+            } else if ("ActivityManager".equals(tag)) {
+                // Message from ActivityManager; potentially an ANR
+                mAnr.parseLine(tid, pid, msg, itemlist);
+            }
+        }
+
+        mJava.commit(itemlist);
+        mNative.commit(itemlist);
+        mAnr.commit(itemlist);
+    }
+}
diff --git a/src/com/android/tradefed/util/brillopad/section/SystemPropParser.java b/src/com/android/tradefed/util/brillopad/section/SystemPropParser.java
new file mode 100644
index 0000000..d3d24a0
--- /dev/null
+++ b/src/com/android/tradefed/util/brillopad/section/SystemPropParser.java
@@ -0,0 +1,56 @@
+/*
+ * 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.section;
+
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.brillopad.ItemList;
+import com.android.tradefed.util.brillopad.IBlockParser;
+import com.android.tradefed.util.brillopad.item.GenericMapItem;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A {@link ILineParser} to handle the System Properties section of the bugreport
+ */
+public class SystemPropParser implements IBlockParser {
+    public static final String SECTION_NAME = "SYSTEM PROPERTIES";
+    public static final String SECTION_REGEX = "------ SYSTEM PROPERTIES .*";
+
+    /** Match a single property line, such as "[gsm.sim.operator.numeric]: []" */
+    private static final Pattern PROP_LINE = Pattern.compile("^\\[(.*)\\]: \\[(.*)\\]$");
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void parseBlock(List<String> block, ItemList itemlist) {
+        GenericMapItem<String, String> output =
+                new GenericMapItem<String, String>(SECTION_NAME);
+
+        for (String line : block) {
+            Matcher m = PROP_LINE.matcher(line);
+            if (m.matches()) {
+                output.put(m.group(1), m.group(2));
+            } else {
+                CLog.w("Failed to parse line '%s'", line);
+            }
+        }
+        itemlist.addItem(output);
+    }
+}
+
diff --git a/src/com/android/tradefed/util/brillopad/section/syslog/AnrParser.java b/src/com/android/tradefed/util/brillopad/section/syslog/AnrParser.java
new file mode 100644
index 0000000..6109f17
--- /dev/null
+++ b/src/com/android/tradefed/util/brillopad/section/syslog/AnrParser.java
@@ -0,0 +1,104 @@
+/*
+ * 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.section.syslog;
+
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.brillopad.ItemList;
+import com.android.tradefed.util.brillopad.item.GenericMapItem;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * An {@link ISyslogParser} to handle ANRs.  Since ANRs are all reported by the ActivityManager,
+ * they are guaranteed to be fully serialized and thus to not be interleaved.  Also for the same
+ * reason, the pid and tid fields are useless.
+ */
+public class AnrParser implements ISyslogParser {
+    public static final String SECTION_NAME = "ANR";
+
+    /**
+     * Matches: ANR (application not responding) in process: app
+     * Matches: ANR in app
+     * Matches: ANR in app (class/package)
+     */
+    private static final Pattern START = Pattern.compile(
+            "ANR (?:\\(application not responding\\) )?in (?:process: )?(\\S+).*");
+    private static final Pattern END = Pattern.compile(".*TOTAL: .*?\\d+% user + \\d+% kernel.*");
+    // FIXME: unit test: /TOTAL: .*/ vs. /TOTAL: .*?/
+
+    private GenericMapItem<String, String> mItem = null;
+    private int mPID = -1;
+    private int mTID = -1;
+
+    /**
+     * We store the stack messages separately so that we avoid creating a new stack String object
+     * for each new line.  In this case, we just keep a single StringBuilder for the entire thing.
+     */
+    private StringBuilder mStack = new StringBuilder();
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void parseLine(int tid, int pid, String line, ItemList itemlist) {
+        Matcher m = START.matcher(line);
+        if (m.matches()) {
+            // start of new ANR.  Out with the old, in with the new
+            CLog.v("Matched ANR start: %s", line);
+            commit(itemlist);
+            mItem = new GenericMapItem<String, String>(SECTION_NAME);
+            mItem.put("app", m.group(1));
+            mPID = pid;
+            mTID = tid;
+        } else if (mItem == null) {
+            //CLog.w("Ignoring unexpected line from pid %d, tid %d: %s", pid, tid, line);
+            return;
+        } else if (pid != mPID || tid != mTID) {
+            CLog.w("Expected pid %d, tid %d, but got line from pid %d, tid %d; committing...",
+                mPID, mTID, pid, tid);
+            commit(itemlist);
+            return;
+        }
+
+        mStack.append(line);
+        mStack.append("\n");
+
+        // FIXME: is there a way to guarantee that an ANR is completed?
+        m = END.matcher(line);
+        if (m.matches()) {
+            CLog.v("Matched ANR end: %s", line);
+            commit(itemlist);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void commit(ItemList itemlist) {
+        if (mItem != null) {
+            mItem.put("stack", mStack.toString());
+            itemlist.addItem(mItem);
+
+            mItem = null;
+            mStack = new StringBuilder();
+            mPID = -1;
+            mTID = -1;
+        }
+    }
+}
+
diff --git a/src/com/android/tradefed/util/brillopad/section/syslog/ISyslogParser.java b/src/com/android/tradefed/util/brillopad/section/syslog/ISyslogParser.java
new file mode 100644
index 0000000..c2780fc
--- /dev/null
+++ b/src/com/android/tradefed/util/brillopad/section/syslog/ISyslogParser.java
@@ -0,0 +1,37 @@
+/*
+ * 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.section.syslog;
+
+import com.android.tradefed.util.brillopad.ItemList;
+
+import java.util.List;
+
+/**
+ * Attempt to parse a line of the logcat
+ */
+public interface ISyslogParser {
+    // FIXME: ILogcatLineParser is a better name
+    /**
+     * Parse of line of logcat
+     */
+    public void parseLine(int tid, int pid, String msg, ItemList itemlist);
+
+    /**
+     * A signal that input is done for this parser
+     */
+    public void commit(ItemList itemlist);
+}
+
diff --git a/src/com/android/tradefed/util/brillopad/section/syslog/JavaCrashParser.java b/src/com/android/tradefed/util/brillopad/section/syslog/JavaCrashParser.java
new file mode 100644
index 0000000..eeaa255
--- /dev/null
+++ b/src/com/android/tradefed/util/brillopad/section/syslog/JavaCrashParser.java
@@ -0,0 +1,111 @@
+/*
+ * 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.section.syslog;
+
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.brillopad.ItemList;
+import com.android.tradefed.util.brillopad.item.GenericMapItem;
+
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * An {@link ISyslogParser} to handle Java crashes.  We parse line-by-line since messages from
+ * different crashes may be interleaved -- the only guarantee we have is that a single line of
+ * logcat does not contain portions of some other line.
+ */
+public class JavaCrashParser implements ISyslogParser {
+    public static final String SECTION_NAME = "JAVA CRASH";
+
+    /**
+     * Matches: java.lang.Exception
+     * Matches: java.lang.Exception: reason
+     */
+    private static final Pattern EXCEPTION = Pattern.compile("^([^\\s:]+)(?:: (.*))$");
+
+    private Set<Integer> mKeys = new LinkedHashSet<Integer>();
+    private Map<Integer, GenericMapItem<String, String>> mMaps =
+            new HashMap<Integer, GenericMapItem<String, String>>();
+    /**
+     * We store the stack messages separately so that we avoid creating a new stack String object
+     * for each new line.  In this case, we just keep a single StringBuilder for the entire thing.
+     */
+    private Map<Integer, StringBuilder> mStacks = new HashMap<Integer, StringBuilder>();
+
+    /**
+     * Hash the PID and TID to create an identifier that "should" be unique for a given logcat.
+     * In practice, we do use it as a unique identifier.
+     */
+    private static int encodePidTid(Integer pid, Integer tid) {
+        // Assume an unreachable max pid of 65536 == 16 bits
+        return (pid << 16) | tid;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void parseLine(int tid, int pid, String line, ItemList itemlist) {
+        int key = encodePidTid(pid, tid);
+        //CLog.w("Got tid %d, pid %d, encoded key %d, line %s", tid, pid, key, line);
+
+        mKeys.add(key);
+
+        Matcher m = EXCEPTION.matcher(line);
+        if (m.matches()) {
+            GenericMapItem<String, String> item = new GenericMapItem<String, String>(SECTION_NAME);
+            item.put("exception", m.group(1));
+            String reason = m.group(2);
+            if (reason != null) {
+                item.put("reason", m.group(2));
+            }
+            mMaps.put(key, item);
+        }
+
+        StringBuilder stack;
+        if (mStacks.containsKey(key)) {
+            stack = mStacks.get(key);
+        } else {
+            stack = new StringBuilder();
+            mStacks.put(key, stack);
+        }
+        stack.append(line);
+        stack.append("\n");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void commit(ItemList itemlist) {
+        for (int key : mKeys) {
+            GenericMapItem<String, String> item = mMaps.get(key);
+            if (item == null) {
+                item = new GenericMapItem<String, String>(SECTION_NAME);
+            }
+
+            if (mStacks.containsKey(key)) {
+                item.put("stack", mStacks.get(key).toString());
+            }
+            itemlist.addItem(item);
+        }
+    }
+}
+
diff --git a/src/com/android/tradefed/util/brillopad/section/syslog/NativeCrashParser.java b/src/com/android/tradefed/util/brillopad/section/syslog/NativeCrashParser.java
new file mode 100644
index 0000000..fc324c5
--- /dev/null
+++ b/src/com/android/tradefed/util/brillopad/section/syslog/NativeCrashParser.java
@@ -0,0 +1,111 @@
+/*
+ * 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.section.syslog;
+
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.brillopad.ItemList;
+import com.android.tradefed.util.brillopad.item.GenericMapItem;
+
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * An {@link ISyslogParser} to handle Native crashes.  We parse line-by-line since messages from
+ * different crashes may be interleaved -- the only guarantee we have is that a single line of
+ * logcat does not contain portions of some other line.
+ */
+public class NativeCrashParser implements ISyslogParser {
+    public static final String SECTION_NAME = "NATIVE CRASH";
+
+    /** Matches: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** */
+    private static final Pattern START = Pattern.compile("^(?:\\*\\*\\* ){15}\\*\\*\\*$");
+    /** Matches: Build fingerprint: 'fingerprint' */
+    private static final Pattern FINGERPRINT = Pattern.compile("^Build fingerprint: '(.*)'$");
+    /** Matches: pid: 957, tid: 963  >>> com.android.camera <<< */
+    private static final Pattern APP = Pattern.compile("^pid: \\d+, tid: \\d+  >>> (\\S+) <<<$");
+
+    private Set<Integer> mKeys = new LinkedHashSet<Integer>();
+    private Map<Integer, GenericMapItem<String, String>> mMaps =
+            new HashMap<Integer, GenericMapItem<String, String>>();
+    /**
+     * We store the stack messages separately so that we avoid creating a new stack String object
+     * for each new line.  In this case, we just keep a single StringBuilder for the entire thing.
+     */
+    private Map<Integer, StringBuilder> mStacks = new HashMap<Integer, StringBuilder>();
+
+    /**
+     * Hash the PID and TID to create an identifier that "should" be unique for a given logcat.
+     * In practice, we do use it as a unique identifier.
+     */
+    private static int encodePidTid(Integer pid, Integer tid) {
+        // Assume an unreachable max pid of 65536 == 16 bits
+        return (pid << 16) | tid;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void parseLine(int tid, int pid, String line, ItemList itemlist) {
+        int key = encodePidTid(pid, tid);
+        //CLog.w("Got tid %d, pid %d, encoded key %d, line %s", tid, pid, key, line);
+
+        if (!mKeys.contains(key)) {
+            // potentially new entry.  Ignore unless we see a start line.
+            Matcher m = START.matcher(line);
+            if (m.matches()) {
+                mKeys.add(key);
+                mMaps.put(key, new GenericMapItem<String, String>(SECTION_NAME));
+                mStacks.put(key, new StringBuilder());
+            } else {
+                CLog.w("Ignoring unexpected line from pid %d, tid %d: %s", pid, tid, line);
+                return;
+            }
+        }
+
+        // by virtue of getting this far, the entries in mMaps and mStacks should exist
+        GenericMapItem<String, String> item = mMaps.get(key);
+        Matcher m = FINGERPRINT.matcher(line);
+        if (m.matches()) {
+            item.put("fingerprint", m.group(1));
+        }
+        m = APP.matcher(line);
+        if (m.matches()) {
+            item.put("app", m.group(1));
+        }
+
+        StringBuilder stack = mStacks.get(key);
+        stack.append(line);
+        stack.append("\n");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void commit(ItemList itemlist) {
+        for (int key : mKeys) {
+            GenericMapItem<String, String> item = new GenericMapItem<String, String>(SECTION_NAME);
+            item.put("stack", mStacks.get(key).toString());
+            itemlist.addItem(item);
+        }
+    }
+}
+
diff --git a/tests/src/com/android/tradefed/util/brillopad/AbstractBlockParserTest.java b/tests/src/com/android/tradefed/util/brillopad/AbstractBlockParserTest.java
new file mode 100644
index 0000000..37e5e73
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/brillopad/AbstractBlockParserTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.util.brillopad.ItemList;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link AbstractBlockParser}
+ */
+public class AbstractBlockParserTest extends TestCase {
+    private static final String COMMIT_MSG = "COMMITTERS! COMMITTERS! COMMITTERS!";
+
+    private List<String> mExpectedLines = new ArrayList<String>();
+    private ItemList mItemList = null;
+
+    private class BlockParser extends AbstractBlockParser {
+        @Override
+        public void parseLine(String line, ItemList itemlist) {
+            String expectedLine = mExpectedLines.remove(0);
+            assertEquals(expectedLine, line);
+            assertEquals(mItemList, itemlist);
+        }
+
+        @Override
+        public void commit(ItemList itemlist) {
+            String expectedLine = mExpectedLines.remove(0);
+            assertEquals(COMMIT_MSG, expectedLine);
+            assertEquals(mItemList, itemlist);
+        }
+    }
+
+    /**
+     * Simply verify that the AbstractBlockParser turns a single parseBlock call into the
+     * appropriate sequence of parseLine calls, followed by a commit() call
+     */
+    public void testSimple() {
+        List<String> lines = list("alpha", "beta", "gamma", "delta");
+        mItemList = new ItemList();
+        BlockParser parser = new BlockParser();
+        mExpectedLines.addAll(lines);
+        mExpectedLines.add(COMMIT_MSG);
+
+        parser.parseBlock(lines, mItemList);
+        assertEquals(0, mExpectedLines.size());
+    }
+
+    private static List<String> list(String... strings) {
+        List<String> retList = new ArrayList<String>(strings.length);
+        for (String str : strings) {
+            retList.add(str);
+        }
+        return retList;
+    }
+}
+
diff --git a/tests/src/com/android/tradefed/util/brillopad/BugreportParserFuncTest.java b/tests/src/com/android/tradefed/util/brillopad/BugreportParserFuncTest.java
new file mode 100644
index 0000000..9ab6d1e
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/brillopad/BugreportParserFuncTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.util.brillopad.item.IItem;
+
+import org.easymock.EasyMock;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * 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);
+    }
+*/
+
+    /**
+     * 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.
+     */
+    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));
+        }
+
+        BufferedReader reader = new BufferedReader(new FileReader(BR_FILE));
+        ItemList bugreport = new ItemList();
+        parser.parse(reader, bugreport);
+
+        List<IItem> jc = bugreport.getByType("JAVA CRASH");
+        List<IItem> nc = bugreport.getByType("NATIVE CRASH");
+        List<IItem> anr = bugreport.getByType("ANR");
+        System.err.format("Got %d items for JAVA CRASH, %d for NATIVE CRASH, and %d for ANR\n",
+                jc.size(), nc.size(), anr.size());
+        for (IItem item : bugreport.getItems()) {
+            System.err.format("Got item with type %s\n", item.getType());
+        }
+    }
+}
+
diff --git a/tests/src/com/android/tradefed/util/brillopad/BugreportParserTest.java b/tests/src/com/android/tradefed/util/brillopad/BugreportParserTest.java
new file mode 100644
index 0000000..c764d5a
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/brillopad/BugreportParserTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.util.brillopad.ItemList;
+import com.android.tradefed.util.brillopad.item.GenericMapItem;
+import com.android.tradefed.util.brillopad.item.IItem;
+import com.android.tradefed.util.brillopad.section.MemInfoParser;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link BugreportParser}
+ */
+public class BugreportParserTest extends TestCase {
+    public void testMemInfoParser() {
+        List<String> inputBlock = list(
+                "MemTotal:         353332 kB",
+                "MemFree:           65420 kB",
+                "Buffers:           20800 kB",
+                "Cached:            86204 kB",
+                "SwapCached:            0 kB");
+        MemInfoParser parser = new MemInfoParser();
+        ItemList br = new ItemList();
+
+        parser.parseBlock(inputBlock, br);
+        List<IItem> items = br.getItems();
+        assertNotNull(items);
+        assertEquals(1, items.size());
+        assertTrue("Expected item of type GenericMapItem!", items.get(0) instanceof GenericMapItem);
+        assertEquals(MemInfoParser.SECTION_NAME, items.get(0).getType());
+
+        Map<String, Integer> output = (GenericMapItem<String, Integer>)items.get(0);
+        assertEquals(5, output.size());
+        assertEquals((Integer)353332, output.get("MemTotal"));
+        assertEquals((Integer)65420, output.get("MemFree"));
+        assertEquals((Integer)20800, output.get("Buffers"));
+        assertEquals((Integer)86204, output.get("Cached"));
+        assertEquals((Integer)0, output.get("SwapCached"));
+    }
+
+    private static List<String> list(String... strings) {
+        List<String> retList = new ArrayList<String>(strings.length);
+        for (String str : strings) {
+            retList.add(str);
+        }
+        return retList;
+    }
+
+    // FIXME: testcase: log tag with embedded colon
+    // FIXME: '05-25 15:04:46.900  2793  2793 I DataGauge:DataUsageService: Custom datagauge boot service destroyed'
+}
+
diff --git a/tests/src/com/android/tradefed/util/brillopad/ItemListTest.java b/tests/src/com/android/tradefed/util/brillopad/ItemListTest.java
new file mode 100644
index 0000000..2295157
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/brillopad/ItemListTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.util.brillopad.item.IItem;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link ItemList}
+ */
+public class ItemListTest extends TestCase {
+    private ItemList mList = null;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mList = new ItemList();
+    }
+
+    private static class FakeItem implements IItem {
+        private String mType = null;
+        public FakeItem(String type) {
+            mType = type;
+        }
+        @Override
+        public String getType() {
+            return mType;
+        }
+        @Override
+        public IItem merge(IItem other) {
+            return this;
+        }
+        @Override
+        public boolean isConsistent(IItem other) {
+            return true;
+        }
+    }
+
+    /**
+     * Verify that if you add some {@link IItem}s, you can get them back out as well.
+     */
+    public void testAdd() {
+        List<IItem> iitems = new ArrayList<IItem>(5);
+        for (Integer i = 0; i < 5; ++i) {
+            IItem item = new FakeItem(i.toString());
+            iitems.add(item);
+            mList.addItem(item);
+        }
+
+        assertEquals("Wrong number of iitems!", iitems.size(), mList.getItems().size());
+    }
+
+    /**
+     * Verify that searching works properly
+     */
+    public void testSearch() {
+        final String namePat = "fake %d";
+        List<IItem> iitems = new ArrayList<IItem>(5);
+        for (Integer i = 0; i < 5; ++i) {
+            IItem item = new FakeItem(String.format("fake %d", i));
+            iitems.add(item);
+            mList.addItem(item);
+        }
+
+        for (Integer i = 0; i < 5; ++i) {
+            List<IItem> searchList = mList.getByType(String.format(".*%d.*", i));
+            assertEquals(1, searchList.size());
+            assertEquals(String.format(namePat, i), searchList.get(0).getType());
+        }
+    }
+}
+
diff --git a/tests/src/com/android/tradefed/util/brillopad/section/AbstractSectionParserTest.java b/tests/src/com/android/tradefed/util/brillopad/section/AbstractSectionParserTest.java
new file mode 100644
index 0000000..b57ee27
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/brillopad/section/AbstractSectionParserTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.section;
+
+import com.android.tradefed.util.RegexTrie;
+import com.android.tradefed.util.brillopad.IBlockParser;
+import com.android.tradefed.util.brillopad.ItemList;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link AbstractSectionParser}
+ */
+public class AbstractSectionParserTest extends TestCase {
+    AbstractSectionParser mParser = null;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mParser = new AbstractSectionParser() {
+            @Override
+            public void addDefaultSectionParsers(RegexTrie<IBlockParser> parsers) {
+                // ignore
+            }
+        };
+    }
+
+    private static class FakeBlockParser implements IBlockParser {
+        private String mExpected = null;
+        private int mCalls = 0;
+
+        public FakeBlockParser(String expected) {
+            mExpected = expected;
+        }
+
+        public int getCalls() {
+            return mCalls;
+        }
+
+        @Override
+        public void parseBlock(List<String> input, ItemList itemlist) {
+            assertEquals(1, input.size());
+            assertEquals("parseBlock() got unexpected input!", mExpected, input.get(0));
+            mCalls += 1;
+        }
+    }
+
+    /**
+     * Verifies that {@link AbstractSectionParser} switches between parsers as expected
+     */
+    public void testSwitchParsers() {
+        final String lineFormat = "howdy, parser %d!";
+        final String linePattern = "I spy %d candles";
+        final int nParsers = 4;
+        FakeBlockParser[] parsers = new FakeBlockParser[nParsers];
+        final List<String> lines = new ArrayList<String>(2*nParsers);
+
+        for (int i = 0; i < nParsers; ++i) {
+            String line = String.format(lineFormat, i);
+            FakeBlockParser parser = new FakeBlockParser(line);
+            mParser.addSectionParser(parser, String.format(linePattern, i));
+            parsers[i] = parser;
+
+            // add the parser trigger
+            lines.add(String.format(linePattern, i));
+            // and then add the line that the parser is expecting
+            lines.add(String.format(lineFormat, i));
+        }
+
+        mParser.parseBlock(lines, null);
+
+        // Verify that all the parsers were run
+        for (int i = 0; i < nParsers; ++i) {
+            assertEquals(String.format("Parser %d has wrong call count!", i),
+                    1, parsers[i].getCalls());
+        }
+    }
+
+    private static List<String> list(String... strings) {
+        List<String> retList = new ArrayList<String>(strings.length);
+        for (String str : strings) {
+            retList.add(str);
+        }
+        return retList;
+    }
+}
+
diff --git a/tests/src/com/android/tradefed/util/brillopad/section/MemInfoParserTest.java b/tests/src/com/android/tradefed/util/brillopad/section/MemInfoParserTest.java
new file mode 100644
index 0000000..1890899
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/brillopad/section/MemInfoParserTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.section;
+
+import com.android.tradefed.util.brillopad.ItemList;
+import com.android.tradefed.util.brillopad.item.GenericMapItem;
+import com.android.tradefed.util.brillopad.item.IItem;
+import com.android.tradefed.util.brillopad.section.MemInfoParser;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link MemInfoParser}
+ */
+public class MemInfoParserTest extends TestCase {
+    public void testMemInfoParser() {
+        List<String> inputBlock = list(
+                "MemTotal:         353332 kB",
+                "MemFree:           65420 kB",
+                "Buffers:           20800 kB",
+                "Cached:            86204 kB",
+                "SwapCached:            0 kB");
+        MemInfoParser parser = new MemInfoParser();
+        ItemList br = new ItemList();
+
+        parser.parseBlock(inputBlock, br);
+        List<IItem> items = br.getItems();
+        assertNotNull(items);
+        assertEquals(1, items.size());
+        assertTrue("Expected item of type GenericMapItem!", items.get(0) instanceof GenericMapItem);
+        assertEquals(MemInfoParser.SECTION_NAME, items.get(0).getType());
+
+        Map<String, Integer> output = (GenericMapItem<String, Integer>)items.get(0);
+        assertEquals(5, output.size());
+        assertEquals((Integer)353332, output.get("MemTotal"));
+        assertEquals((Integer)65420, output.get("MemFree"));
+        assertEquals((Integer)20800, output.get("Buffers"));
+        assertEquals((Integer)86204, output.get("Cached"));
+        assertEquals((Integer)0, output.get("SwapCached"));
+    }
+
+    private static List<String> list(String... strings) {
+        List<String> retList = new ArrayList<String>(strings.length);
+        for (String str : strings) {
+            retList.add(str);
+        }
+        return retList;
+    }
+}
+
diff --git a/tests/src/com/android/tradefed/util/brillopad/section/ProcRankParserTest.java b/tests/src/com/android/tradefed/util/brillopad/section/ProcRankParserTest.java
new file mode 100644
index 0000000..7024ae7
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/brillopad/section/ProcRankParserTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.section;
+
+import com.android.tradefed.util.brillopad.ItemList;
+import com.android.tradefed.util.brillopad.item.GenericMapItem;
+import com.android.tradefed.util.brillopad.item.IItem;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link ProcRankParser}
+ */
+public class ProcRankParserTest extends TestCase {
+    public void testProcRankParser() {
+        List<String> inputBlock = list(
+                "  PID      Vss      Rss      Pss      Uss  cmdline",
+                "  178   87136K   81684K   52829K   50012K  system_server",
+                " 1313   78128K   77996K   48603K   45812K  com.google.android.apps.maps",
+                " 3247   61652K   61492K   33122K   30972K  com.android.browser",
+                "  334   55740K   55572K   29629K   28360K  com.android.launcher",
+                " 2072   51348K   51172K   24263K   22812K  android.process.acore",
+                " 1236   51440K   51312K   22911K   20608K  com.android.settings");
+        ProcRankParser parser = new ProcRankParser();
+        ItemList br = new ItemList();
+
+        parser.parseBlock(inputBlock, br);
+        List<IItem> items = br.getItems();
+        assertNotNull(items);
+        assertEquals(1, items.size());
+        assertTrue("Expected item of type GenericMapItem!", items.get(0) instanceof GenericMapItem);
+        assertEquals(ProcRankParser.SECTION_NAME, items.get(0).getType());
+
+        Map<String, Integer> map;
+        Map<String, Map<String, Integer>> output =
+                (GenericMapItem<String, Map<String, Integer>>)items.get(0);
+        assertEquals(6, output.size());
+        // Make sure all expected rows are present, and do a diagonal check of values
+        map = output.get("system_server");
+        assertNotNull(map);
+        assertEquals((Integer)178, map.get("PID"));
+
+        map = output.get("com.google.android.apps.maps");
+        assertNotNull(map);
+        assertEquals((Integer)78128, map.get("Vss"));
+
+        map = output.get("com.android.browser");
+        assertNotNull(map);
+        assertEquals((Integer)61492, map.get("Rss"));
+
+        map = output.get("com.android.launcher");
+        assertNotNull(map);
+        assertEquals((Integer)29629, map.get("Pss"));
+
+        map = output.get("android.process.acore");
+        assertNotNull(map);
+        assertEquals((Integer)22812, map.get("Uss"));
+
+        map = output.get("com.android.settings");
+        assertNotNull(map);
+        assertEquals((Integer)1236, map.get("PID"));
+    }
+
+    private static List<String> list(String... strings) {
+        List<String> retList = new ArrayList<String>(strings.length);
+        for (String str : strings) {
+            retList.add(str);
+        }
+        return retList;
+    }
+}
+
diff --git a/tests/src/com/android/tradefed/util/brillopad/section/SystemPropParserTest.java b/tests/src/com/android/tradefed/util/brillopad/section/SystemPropParserTest.java
new file mode 100644
index 0000000..d5f6e2f
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/brillopad/section/SystemPropParserTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.section;
+
+import com.android.tradefed.util.brillopad.ItemList;
+import com.android.tradefed.util.brillopad.item.GenericMapItem;
+import com.android.tradefed.util.brillopad.item.IItem;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link SystemPropParser}
+ */
+public class SystemPropParserTest extends TestCase {
+    public void testSimpleParse() {
+        List<String> inputBlock = list(
+                "[dalvik.vm.dexopt-flags]: [m=y]",
+                "[dalvik.vm.heapgrowthlimit]: [48m]",
+                "[dalvik.vm.heapsize]: [256m]",
+                "[gsm.version.ril-impl]: [android moto-ril-multimode 1.0]");
+        SystemPropParser parser = new SystemPropParser();
+        ItemList br = new ItemList();
+
+        parser.parseBlock(inputBlock, br);
+        List<IItem> items = br.getItems();
+        assertNotNull(items);
+        assertEquals(1, items.size());
+        assertTrue("Expected item of type GenericMapItem!", items.get(0) instanceof GenericMapItem);
+        assertEquals(SystemPropParser.SECTION_NAME, items.get(0).getType());
+
+        Map<String, String> map = (Map<String, String>)items.get(0);
+        assertEquals(4, map.size());
+        assertEquals("m=y", map.get("dalvik.vm.dexopt-flags"));
+        assertEquals("48m", map.get("dalvik.vm.heapgrowthlimit"));
+        assertEquals("256m", map.get("dalvik.vm.heapsize"));
+        assertEquals("android moto-ril-multimode 1.0", map.get("gsm.version.ril-impl"));
+    }
+
+    /**
+     * Make sure that a parse error on one line doesn't prevent the rest of the lines from being
+     * parsed
+     */
+    public void testParseError() {
+        List<String> inputBlock = list(
+                "[dalvik.vm.dexopt-flags]: [m=y]",
+                "[ends with newline]: [yup",
+                "]",
+                "[dalvik.vm.heapsize]: [256m]");
+        SystemPropParser parser = new SystemPropParser();
+        ItemList br = new ItemList();
+
+        parser.parseBlock(inputBlock, br);
+        List<IItem> items = br.getItems();
+        assertNotNull(items);
+        assertEquals(1, items.size());
+        assertTrue("Expected item of type GenericMapItem!", items.get(0) instanceof GenericMapItem);
+        assertEquals(SystemPropParser.SECTION_NAME, items.get(0).getType());
+
+        Map<String, String> map = (Map<String, String>)items.get(0);
+        assertEquals(2, map.size());
+        assertEquals("m=y", map.get("dalvik.vm.dexopt-flags"));
+        assertEquals("256m", map.get("dalvik.vm.heapsize"));
+    }
+
+    private static List<String> list(String... strings) {
+        List<String> retList = new ArrayList<String>(strings.length);
+        for (String str : strings) {
+            retList.add(str);
+        }
+        return retList;
+    }
+}
+