summaryrefslogtreecommitdiff
path: root/javatests/com/android/loganalysis
diff options
context:
space:
mode:
Diffstat (limited to 'javatests/com/android/loganalysis')
-rw-r--r--javatests/com/android/loganalysis/item/BatteryDischargeItemTest.java56
-rw-r--r--javatests/com/android/loganalysis/item/BatteryUsageItemTest.java53
-rw-r--r--javatests/com/android/loganalysis/item/DumpsysPackageStatsItemTest.java52
-rw-r--r--javatests/com/android/loganalysis/item/DvmLockSampleItemTest.java57
-rw-r--r--javatests/com/android/loganalysis/item/GenericItemTest.java266
-rw-r--r--javatests/com/android/loganalysis/item/InterruptItemTest.java53
-rw-r--r--javatests/com/android/loganalysis/item/LocationDumpsItemTest.java81
-rw-r--r--javatests/com/android/loganalysis/item/MemInfoItemTest.java52
-rw-r--r--javatests/com/android/loganalysis/item/MonkeyLogItemTest.java67
-rw-r--r--javatests/com/android/loganalysis/item/ProcrankItemTest.java60
-rw-r--r--javatests/com/android/loganalysis/item/SmartMonkeyLogItemTest.java52
-rw-r--r--javatests/com/android/loganalysis/item/SystemPropsItemTest.java52
-rw-r--r--javatests/com/android/loganalysis/item/TopItemTest.java41
-rw-r--r--javatests/com/android/loganalysis/item/WakelockItemTest.java63
-rw-r--r--javatests/com/android/loganalysis/parser/AbstractSectionParserTest.java98
-rw-r--r--javatests/com/android/loganalysis/parser/ActivityServiceParserTest.java59
-rw-r--r--javatests/com/android/loganalysis/parser/AnrParserTest.java164
-rw-r--r--javatests/com/android/loganalysis/parser/BatteryDischargeStatsInfoParserTest.java164
-rw-r--r--javatests/com/android/loganalysis/parser/BatteryStatsDetailedInfoParserTest.java131
-rw-r--r--javatests/com/android/loganalysis/parser/BatteryStatsSummaryInfoParserTest.java109
-rw-r--r--javatests/com/android/loganalysis/parser/BatteryUsageParserTest.java56
-rw-r--r--javatests/com/android/loganalysis/parser/BugreportParserTest.java766
-rw-r--r--javatests/com/android/loganalysis/parser/CompactMemInfoParserTest.java205
-rw-r--r--javatests/com/android/loganalysis/parser/CpuInfoParserTest.java54
-rw-r--r--javatests/com/android/loganalysis/parser/DmesgParserTest.java317
-rw-r--r--javatests/com/android/loganalysis/parser/DumpsysBatteryStatsParserTest.java153
-rw-r--r--javatests/com/android/loganalysis/parser/DumpsysPackageStatsParserTest.java60
-rw-r--r--javatests/com/android/loganalysis/parser/DumpsysParserTest.java143
-rw-r--r--javatests/com/android/loganalysis/parser/DumpsysProcStatsParserTest.java48
-rw-r--r--javatests/com/android/loganalysis/parser/DumpsysProcessMeminfoParserTest.java104
-rw-r--r--javatests/com/android/loganalysis/parser/DumpsysWifiStatsParserTest.java103
-rw-r--r--javatests/com/android/loganalysis/parser/DvmLockSampleParserTest.java46
-rw-r--r--javatests/com/android/loganalysis/parser/EventsLogParserTest.java260
-rw-r--r--javatests/com/android/loganalysis/parser/GfxInfoParserTest.java358
-rw-r--r--javatests/com/android/loganalysis/parser/InterruptParserTest.java62
-rw-r--r--javatests/com/android/loganalysis/parser/JavaCrashParserTest.java151
-rw-r--r--javatests/com/android/loganalysis/parser/KernelLogParserTest.java342
-rw-r--r--javatests/com/android/loganalysis/parser/LocationServiceParserTest.java89
-rw-r--r--javatests/com/android/loganalysis/parser/LogcatParserTest.java845
-rw-r--r--javatests/com/android/loganalysis/parser/MemHealthParserTest.java184
-rw-r--r--javatests/com/android/loganalysis/parser/MemInfoParserTest.java64
-rw-r--r--javatests/com/android/loganalysis/parser/MonkeyLogParserTest.java786
-rw-r--r--javatests/com/android/loganalysis/parser/NativeCrashParserTest.java192
-rw-r--r--javatests/com/android/loganalysis/parser/ProcessUsageParserTest.java78
-rw-r--r--javatests/com/android/loganalysis/parser/ProcrankParserTest.java105
-rw-r--r--javatests/com/android/loganalysis/parser/QtaguidParserTest.java73
-rw-r--r--javatests/com/android/loganalysis/parser/SmartMonkeyLogParserTest.java147
-rw-r--r--javatests/com/android/loganalysis/parser/SystemPropsParserTest.java75
-rw-r--r--javatests/com/android/loganalysis/parser/TimingsLogParserTest.java354
-rw-r--r--javatests/com/android/loganalysis/parser/TopParserTest.java83
-rw-r--r--javatests/com/android/loganalysis/parser/TraceFormatParserTest.java227
-rw-r--r--javatests/com/android/loganalysis/parser/TracesParserTest.java167
-rw-r--r--javatests/com/android/loganalysis/parser/WakelockParserTest.java127
-rw-r--r--javatests/com/android/loganalysis/rule/InterruptRuleTest.java101
-rw-r--r--javatests/com/android/loganalysis/rule/LocationUsageRuleTest.java100
-rw-r--r--javatests/com/android/loganalysis/rule/ProcessUsageRuleTest.java111
-rw-r--r--javatests/com/android/loganalysis/rule/WakelockRuleTest.java108
-rw-r--r--javatests/com/android/loganalysis/rule/WifiStatsRuleTest.java103
-rw-r--r--javatests/com/android/loganalysis/util/ArrayUtilTest.java68
-rw-r--r--javatests/com/android/loganalysis/util/LogPatternUtilTest.java57
-rw-r--r--javatests/com/android/loganalysis/util/LogTailUtilTest.java66
-rw-r--r--javatests/com/android/loganalysis/util/RegexTrieTest.java284
-rw-r--r--javatests/com/android/loganalysis/util/config/ArgsOptionParserTest.java629
-rw-r--r--javatests/com/android/loganalysis/util/config/OptionSetterTest.java828
-rw-r--r--javatests/com/android/loganalysis/util/config/OptionUpdateRuleTest.java88
65 files changed, 11097 insertions, 0 deletions
diff --git a/javatests/com/android/loganalysis/item/BatteryDischargeItemTest.java b/javatests/com/android/loganalysis/item/BatteryDischargeItemTest.java
new file mode 100644
index 0000000..5e27eb2
--- /dev/null
+++ b/javatests/com/android/loganalysis/item/BatteryDischargeItemTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2015 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.loganalysis.item;
+
+import com.android.loganalysis.item.BatteryDischargeItem.BatteryDischargeInfoItem;
+
+import junit.framework.TestCase;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Calendar;
+
+/**
+ * Unit test for {@link BatteryDischargeItem}.
+ */
+public class BatteryDischargeItemTest extends TestCase {
+
+ /**
+ * Test that {@link BatteryDischargeItem#toJson()} returns correctly.
+ */
+ public void testToJson() throws JSONException {
+ BatteryDischargeItem item = new BatteryDischargeItem();
+ item.addBatteryDischargeInfo(Calendar.getInstance(),25, 95);
+
+ // Convert to JSON string and back again
+ JSONObject output = new JSONObject(item.toJson().toString());
+
+ assertTrue(output.has(BatteryDischargeItem.BATTERY_DISCHARGE));
+ assertTrue(output.get(BatteryDischargeItem.BATTERY_DISCHARGE) instanceof JSONArray);
+
+ JSONArray dischargeInfo = output.getJSONArray(BatteryDischargeItem.BATTERY_DISCHARGE);
+
+ assertEquals(1, dischargeInfo.length());
+ assertTrue(dischargeInfo.getJSONObject(0).has(BatteryDischargeInfoItem.BATTERY_LEVEL));
+ assertTrue(dischargeInfo.getJSONObject(0).has(
+ BatteryDischargeInfoItem.DISCHARGE_ELAPSED_TIME));
+ assertTrue(dischargeInfo.getJSONObject(0).has(
+ BatteryDischargeInfoItem.CLOCK_TIME_OF_DISCHARGE));
+
+ }
+}
diff --git a/javatests/com/android/loganalysis/item/BatteryUsageItemTest.java b/javatests/com/android/loganalysis/item/BatteryUsageItemTest.java
new file mode 100644
index 0000000..b44a432
--- /dev/null
+++ b/javatests/com/android/loganalysis/item/BatteryUsageItemTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 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.loganalysis.item;
+
+import com.android.loganalysis.item.BatteryUsageItem.BatteryUsageInfoItem;
+
+import junit.framework.TestCase;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Unit test for {@link BatteryUsageItem}.
+ */
+public class BatteryUsageItemTest extends TestCase {
+
+ /**
+ * Test that {@link BatteryUsageItem#toJson()} returns correctly.
+ */
+ public void testToJson() throws JSONException {
+ BatteryUsageItem item = new BatteryUsageItem();
+ item.addBatteryUsage("Cell standby", 2925);
+ item.addBatteryUsage("Uid u0a71", 68.1);
+ // Convert to JSON string and back again
+ JSONObject output = new JSONObject(item.toJson().toString());
+
+ assertTrue(output.has(BatteryUsageItem.BATTERY_CAPACITY));
+ assertTrue(output.get(BatteryUsageItem.BATTERY_USAGE) instanceof JSONArray);
+
+ JSONArray usage = output.getJSONArray(BatteryUsageItem.BATTERY_USAGE);
+
+ assertEquals(2, usage.length());
+ assertTrue(usage.getJSONObject(0).has(BatteryUsageInfoItem.NAME));
+ assertTrue(usage.getJSONObject(0).has(BatteryUsageInfoItem.USAGE));
+
+ assertTrue(usage.getJSONObject(1).has(BatteryUsageInfoItem.NAME));
+ assertTrue(usage.getJSONObject(1).has(BatteryUsageInfoItem.USAGE));
+ }
+}
diff --git a/javatests/com/android/loganalysis/item/DumpsysPackageStatsItemTest.java b/javatests/com/android/loganalysis/item/DumpsysPackageStatsItemTest.java
new file mode 100644
index 0000000..dd60500
--- /dev/null
+++ b/javatests/com/android/loganalysis/item/DumpsysPackageStatsItemTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 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.loganalysis.item;
+
+import junit.framework.TestCase;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/** Unit test for {@link DumpsysPackageStatsItem}. */
+public class DumpsysPackageStatsItemTest extends TestCase {
+
+ /** Test that {@link DumpsysPackageStatsItem#toJson()} returns correctly. */
+ public void testToJson() throws JSONException {
+ DumpsysPackageStatsItem item = new DumpsysPackageStatsItem();
+
+ item.put("com.google.android.calculator", new AppVersionItem(73000302, "7.3 (3821978)"));
+ item.put(
+ "com.google.android.googlequicksearchbox",
+ new AppVersionItem(300734793, "6.16.35.26.arm64"));
+
+ // Convert to JSON string and back again
+ JSONObject output = new JSONObject(item.toJson().toString());
+
+ assertTrue(output.has(DumpsysPackageStatsItem.APP_VERSIONS));
+
+ JSONObject appVersionsJson = output.getJSONObject(DumpsysPackageStatsItem.APP_VERSIONS);
+
+ assertEquals(2, appVersionsJson.length());
+ final JSONObject calcAppVersionJson =
+ appVersionsJson.getJSONObject("com.google.android.calculator");
+ assertEquals(73000302, calcAppVersionJson.getInt(AppVersionItem.VERSION_CODE));
+ assertEquals("7.3 (3821978)", calcAppVersionJson.getString(AppVersionItem.VERSION_NAME));
+ final JSONObject gsaAppVersionJson =
+ appVersionsJson.getJSONObject("com.google.android.googlequicksearchbox");
+ assertEquals(300734793, gsaAppVersionJson.getInt(AppVersionItem.VERSION_CODE));
+ assertEquals("6.16.35.26.arm64", gsaAppVersionJson.getString(AppVersionItem.VERSION_NAME));
+ }
+}
diff --git a/javatests/com/android/loganalysis/item/DvmLockSampleItemTest.java b/javatests/com/android/loganalysis/item/DvmLockSampleItemTest.java
new file mode 100644
index 0000000..5d6054e
--- /dev/null
+++ b/javatests/com/android/loganalysis/item/DvmLockSampleItemTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2013 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.loganalysis.item;
+
+import junit.framework.TestCase;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Unit test for {@link DvmLockSampleItem}.
+ */
+public class DvmLockSampleItemTest extends TestCase {
+ /**
+ * Test that {@link DvmLockSampleItem#toJson()} returns correctly.
+ */
+ public void testToJson() throws JSONException {
+ DvmLockSampleItem item = new DvmLockSampleItem();
+
+ item.setAttribute(DvmLockSampleItem.PROCESS_NAME, "android.support.test.aupt");
+ item.setAttribute(DvmLockSampleItem.SENSITIVITY_FLAG, false);
+ item.setAttribute(DvmLockSampleItem.WAITING_THREAD_NAME, "Instr: android.support.test.aupt");
+ item.setAttribute(DvmLockSampleItem.WAIT_TIME, 75);
+ item.setAttribute(DvmLockSampleItem.WAITING_SOURCE_FILE, "AccessibilityCache.java");
+ item.setAttribute(DvmLockSampleItem.WAITING_SOURCE_LINE, 256);
+ item.setAttribute(DvmLockSampleItem.OWNER_FILE_NAME, "-");
+ item.setAttribute(DvmLockSampleItem.OWNER_ACQUIRE_SOURCE_LINE, 96);
+ item.setAttribute(DvmLockSampleItem.SAMPLE_PERCENTAGE, 15);
+
+ // Convert to JSON string and back again
+ JSONObject output = new JSONObject(item.toJson().toString());
+
+ // Assert that each field is the expected value
+ assertEquals("android.support.test.aupt", output.get(DvmLockSampleItem.PROCESS_NAME));
+ assertEquals(false, output.get(DvmLockSampleItem.SENSITIVITY_FLAG));
+ assertEquals("Instr: android.support.test.aupt", output.get(DvmLockSampleItem.WAITING_THREAD_NAME));
+ assertEquals(75, output.get(DvmLockSampleItem.WAIT_TIME));
+ assertEquals("AccessibilityCache.java", output.get(DvmLockSampleItem.WAITING_SOURCE_FILE));
+ assertEquals(256, output.get(DvmLockSampleItem.WAITING_SOURCE_LINE));
+ assertEquals("-", output.get(DvmLockSampleItem.OWNER_FILE_NAME));
+ assertEquals(96, output.get(DvmLockSampleItem.OWNER_ACQUIRE_SOURCE_LINE));
+ assertEquals(15, output.get(DvmLockSampleItem.SAMPLE_PERCENTAGE));
+ }
+}
diff --git a/javatests/com/android/loganalysis/item/GenericItemTest.java b/javatests/com/android/loganalysis/item/GenericItemTest.java
new file mode 100644
index 0000000..e9ea31f
--- /dev/null
+++ b/javatests/com/android/loganalysis/item/GenericItemTest.java
@@ -0,0 +1,266 @@
+/*
+ * 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.loganalysis.item;
+
+import junit.framework.TestCase;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Unit test for {@link GenericItem}.
+ */
+public class GenericItemTest extends TestCase {
+ private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList(
+ "integer", "string"));
+
+ private String mStringAttribute = "String";
+ private Integer mIntegerAttribute = 1;
+
+ /** Empty item with no attributes set */
+ private GenericItem mEmptyItem1;
+ /** Empty item with no attributes set */
+ private GenericItem mEmptyItem2;
+ /** Item with only the string attribute set */
+ private GenericItem mStringItem;
+ /** Item with only the integer attribute set */
+ private GenericItem mIntegerItem;
+ /** Item with both attributes set, product of mStringItem and mIntegerItem */
+ private GenericItem mFullItem1;
+ /** Item with both attributes set, product of mStringItem and mIntegerItem */
+ private GenericItem mFullItem2;
+ /** Item that is inconsistent with the others */
+ private GenericItem mInconsistentItem;
+
+ @Override
+ public void setUp() {
+ mEmptyItem1 = new GenericItem(ATTRIBUTES);
+ mEmptyItem2 = new GenericItem(ATTRIBUTES);
+ mStringItem = new GenericItem(ATTRIBUTES);
+ mStringItem.setAttribute("string", mStringAttribute);
+ mIntegerItem = new GenericItem(ATTRIBUTES);
+ mIntegerItem.setAttribute("integer", mIntegerAttribute);
+ mFullItem1 = new GenericItem(ATTRIBUTES);
+ mFullItem1.setAttribute("string", mStringAttribute);
+ mFullItem1.setAttribute("integer", mIntegerAttribute);
+ mFullItem2 = new GenericItem(ATTRIBUTES);
+ mFullItem2.setAttribute("string", mStringAttribute);
+ mFullItem2.setAttribute("integer", mIntegerAttribute);
+ mInconsistentItem = new GenericItem(ATTRIBUTES);
+ mInconsistentItem.setAttribute("string", "gnirts");
+ mInconsistentItem.setAttribute("integer", 2);
+ }
+
+ /**
+ * Test for {@link GenericItem#mergeAttributes(IItem, Set)}.
+ */
+ public void testMergeAttributes() throws ConflictingItemException {
+ Map<String, Object> attributes;
+
+ attributes = mEmptyItem1.mergeAttributes(mEmptyItem1, ATTRIBUTES);
+ assertNull(attributes.get("string"));
+ assertNull(attributes.get("integer"));
+
+ attributes = mEmptyItem1.mergeAttributes(mEmptyItem2, ATTRIBUTES);
+ assertNull(attributes.get("string"));
+ assertNull(attributes.get("integer"));
+
+ attributes = mEmptyItem2.mergeAttributes(mEmptyItem1, ATTRIBUTES);
+ assertNull(attributes.get("string"));
+ assertNull(attributes.get("integer"));
+
+ attributes = mEmptyItem1.mergeAttributes(mStringItem, ATTRIBUTES);
+ assertEquals(mStringAttribute, attributes.get("string"));
+ assertNull(attributes.get("integer"));
+
+ attributes = mStringItem.mergeAttributes(mEmptyItem1, ATTRIBUTES);
+ assertEquals(mStringAttribute, attributes.get("string"));
+ assertNull(attributes.get("integer"));
+
+ attributes = mIntegerItem.mergeAttributes(mStringItem, ATTRIBUTES);
+ assertEquals(mStringAttribute, attributes.get("string"));
+ assertEquals(mIntegerAttribute, attributes.get("integer"));
+
+ attributes = mEmptyItem1.mergeAttributes(mFullItem1, ATTRIBUTES);
+ assertEquals(mStringAttribute, attributes.get("string"));
+ assertEquals(mIntegerAttribute, attributes.get("integer"));
+
+ attributes = mFullItem1.mergeAttributes(mEmptyItem1, ATTRIBUTES);
+ assertEquals(mStringAttribute, attributes.get("string"));
+ assertEquals(mIntegerAttribute, attributes.get("integer"));
+
+ attributes = mFullItem1.mergeAttributes(mFullItem2, ATTRIBUTES);
+ assertEquals(mStringAttribute, attributes.get("string"));
+ assertEquals(mIntegerAttribute, attributes.get("integer"));
+
+ try {
+ mFullItem1.mergeAttributes(mInconsistentItem, ATTRIBUTES);
+ fail("Expecting a ConflictingItemException");
+ } catch (ConflictingItemException e) {
+ // Expected
+ }
+ }
+
+ /**
+ * Test for {@link GenericItem#isConsistent(IItem)}.
+ */
+ public void testIsConsistent() {
+ assertTrue(mEmptyItem1.isConsistent(mEmptyItem1));
+ assertFalse(mEmptyItem1.isConsistent(null));
+ assertTrue(mEmptyItem1.isConsistent(mEmptyItem2));
+ assertTrue(mEmptyItem2.isConsistent(mEmptyItem1));
+ assertTrue(mEmptyItem1.isConsistent(mStringItem));
+ assertTrue(mStringItem.isConsistent(mEmptyItem1));
+ assertTrue(mIntegerItem.isConsistent(mStringItem));
+ assertTrue(mEmptyItem1.isConsistent(mFullItem1));
+ assertTrue(mFullItem1.isConsistent(mEmptyItem1));
+ assertTrue(mFullItem1.isConsistent(mFullItem2));
+ assertFalse(mFullItem1.isConsistent(mInconsistentItem));
+ }
+
+ /** Test {@link GenericItem#equals(Object)}. */
+ @SuppressWarnings("SelfEquals")
+ public void testEquals() {
+ assertTrue(mEmptyItem1.equals(mEmptyItem1));
+ assertFalse(mEmptyItem1.equals(null));
+ assertTrue(mEmptyItem1.equals(mEmptyItem2));
+ assertTrue(mEmptyItem2.equals(mEmptyItem1));
+ assertFalse(mEmptyItem1.equals(mStringItem));
+ assertFalse(mStringItem.equals(mEmptyItem1));
+ assertFalse(mIntegerItem.equals(mStringItem));
+ assertFalse(mEmptyItem1.equals(mFullItem1));
+ assertFalse(mFullItem1.equals(mEmptyItem1));
+ assertTrue(mFullItem1.equals(mFullItem2));
+ assertFalse(mFullItem1.equals(mInconsistentItem));
+ }
+
+ /**
+ * Test for {@link GenericItem#setAttribute(String, Object)} and
+ * {@link GenericItem#getAttribute(String)}.
+ */
+ public void testAttributes() {
+ GenericItem item = new GenericItem(ATTRIBUTES);
+
+ assertNull(item.getAttribute("string"));
+ assertNull(item.getAttribute("integer"));
+
+ item.setAttribute("string", mStringAttribute);
+ item.setAttribute("integer", mIntegerAttribute);
+
+ assertEquals(mStringAttribute, item.getAttribute("string"));
+ assertEquals(mIntegerAttribute, item.getAttribute("integer"));
+
+ item.setAttribute("string", null);
+ item.setAttribute("integer", null);
+
+ assertNull(item.getAttribute("string"));
+ assertNull(item.getAttribute("integer"));
+
+ try {
+ item.setAttribute("object", new Object());
+ fail("Failed to throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // Expected because "object" is not "string" or "integer".
+ }
+ }
+
+ /**
+ * Test for {@link GenericItem#areEqual(Object, Object)}
+ */
+ public void testAreEqual() {
+ assertTrue(GenericItem.areEqual(null, null));
+ assertTrue(GenericItem.areEqual("test", "test"));
+ assertFalse(GenericItem.areEqual(null, "test"));
+ assertFalse(GenericItem.areEqual("test", null));
+ assertFalse(GenericItem.areEqual("test", ""));
+ }
+
+ /**
+ * Test for {@link GenericItem#areConsistent(Object, Object)}
+ */
+ public void testAreConsistent() {
+ assertTrue(GenericItem.areConsistent(null, null));
+ assertTrue(GenericItem.areConsistent("test", "test"));
+ assertTrue(GenericItem.areConsistent(null, "test"));
+ assertTrue(GenericItem.areConsistent("test", null));
+ assertFalse(GenericItem.areConsistent("test", ""));
+ }
+
+ /**
+ * Test for {@link GenericItem#mergeObjects(Object, Object)}
+ */
+ public void testMergeObjects() throws ConflictingItemException {
+ assertNull(GenericItem.mergeObjects(null, null));
+ assertEquals("test", GenericItem.mergeObjects("test", "test"));
+ assertEquals("test", GenericItem.mergeObjects(null, "test"));
+ assertEquals("test", GenericItem.mergeObjects("test", null));
+
+ try {
+ assertEquals("test", GenericItem.mergeObjects("test", ""));
+ fail("Expected ConflictingItemException to be thrown");
+ } catch (ConflictingItemException e) {
+ // Expected because "test" conflicts with "".
+ }
+ }
+
+ /**
+ * Test that {@link GenericItem#toJson()} returns correctly.
+ */
+ public void testToJson() throws JSONException {
+ GenericItem item = new GenericItem(new HashSet<String>(Arrays.asList(
+ "string", "date", "object", "integer", "long", "float", "double", "item", "null")));
+ Date date = new Date();
+ Object object = new Object();
+ NativeCrashItem subItem = new NativeCrashItem();
+
+ item.setAttribute("string", "foo");
+ item.setAttribute("date", date);
+ item.setAttribute("object", object);
+ item.setAttribute("integer", 0);
+ item.setAttribute("long", 1L);
+ item.setAttribute("float", 2.5f);
+ item.setAttribute("double", 3.5);
+ item.setAttribute("item", subItem);
+ item.setAttribute("null", null);
+
+ // Convert to JSON string and back again
+ JSONObject output = new JSONObject(item.toJson().toString());
+
+ assertTrue(output.has("string"));
+ assertEquals("foo", output.get("string"));
+ assertTrue(output.has("date"));
+ assertEquals(date.toString(), output.get("date"));
+ assertTrue(output.has("object"));
+ assertEquals(object.toString(), output.get("object"));
+ assertTrue(output.has("integer"));
+ assertEquals(0, output.get("integer"));
+ assertTrue(output.has("long"));
+ assertEquals(1, output.get("long"));
+ assertTrue(output.has("float"));
+ assertEquals(2.5, output.get("float"));
+ assertTrue(output.has("double"));
+ assertEquals(3.5, output.get("double"));
+ assertTrue(output.has("item"));
+ assertTrue(output.get("item") instanceof JSONObject);
+ assertFalse(output.has("null"));
+ }
+}
diff --git a/javatests/com/android/loganalysis/item/InterruptItemTest.java b/javatests/com/android/loganalysis/item/InterruptItemTest.java
new file mode 100644
index 0000000..9e9df61
--- /dev/null
+++ b/javatests/com/android/loganalysis/item/InterruptItemTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 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.loganalysis.item;
+
+
+import com.android.loganalysis.item.InterruptItem.InterruptInfoItem;
+
+import junit.framework.TestCase;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Unit test for {@link InterruptItem}.
+ */
+public class InterruptItemTest extends TestCase {
+
+ /**
+ * Test that {@link InterruptItem#toJson()} returns correctly.
+ */
+ public void testToJson() throws JSONException {
+ InterruptItem item = new InterruptItem();
+ item.addInterrupt("smd-modem",25, InterruptItem.InterruptCategory.ALARM_INTERRUPT);
+
+ // Convert to JSON string and back again
+ JSONObject output = new JSONObject(item.toJson().toString());
+
+ assertTrue(output.has(InterruptItem.INTERRUPTS));
+ assertTrue(output.get(InterruptItem.INTERRUPTS) instanceof JSONArray);
+
+ JSONArray interruptsInfo = output.getJSONArray(InterruptItem.INTERRUPTS);
+
+ assertEquals(1, interruptsInfo.length());
+ assertTrue(interruptsInfo.getJSONObject(0).has(InterruptInfoItem.NAME));
+ assertTrue(interruptsInfo.getJSONObject(0).has(InterruptInfoItem.CATEGORY));
+ assertTrue(interruptsInfo.getJSONObject(0).has(InterruptInfoItem.INTERRUPT_COUNT));
+
+ }
+}
diff --git a/javatests/com/android/loganalysis/item/LocationDumpsItemTest.java b/javatests/com/android/loganalysis/item/LocationDumpsItemTest.java
new file mode 100644
index 0000000..a96bc0d
--- /dev/null
+++ b/javatests/com/android/loganalysis/item/LocationDumpsItemTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2016 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.loganalysis.item;
+
+import com.android.loganalysis.item.LocationDumpsItem.LocationInfoItem;
+
+import junit.framework.TestCase;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Unit test for {@link LocationDumpsItem}.
+ */
+public class LocationDumpsItemTest extends TestCase {
+
+ /**
+ * Test that {@link LocationDumpsItem#toJson()} returns correctly.
+ */
+ public void testToJson() throws JSONException {
+ LocationDumpsItem item = new LocationDumpsItem();
+ item.addLocationClient("com.google.android.gms", 500, 60, 1000, "PRIORITY_ACCURACY", 45);
+ item.addLocationClient("com.google.android.maps", 0, 0, 0, "PRIORITY_ACCURACY", 55);
+
+ // Convert to JSON string and back again
+ JSONObject output = new JSONObject(item.toJson().toString());
+
+ assertTrue(output.has(LocationDumpsItem.LOCATION_CLIENTS));
+ assertTrue(output.get(LocationDumpsItem.LOCATION_CLIENTS) instanceof JSONArray);
+
+ JSONArray locationClients = output.getJSONArray(LocationDumpsItem.LOCATION_CLIENTS);
+
+ assertEquals(2, locationClients.length());
+ assertTrue(locationClients.getJSONObject(0).has(LocationInfoItem.PACKAGE));
+ assertTrue(locationClients.getJSONObject(0).has(LocationInfoItem.EFFECTIVE_INTERVAL));
+ assertTrue(locationClients.getJSONObject(0).has(LocationInfoItem.MIN_INTERVAL));
+ assertTrue(locationClients.getJSONObject(0).has(LocationInfoItem.MAX_INTERVAL));
+ assertTrue(locationClients.getJSONObject(0).has(LocationInfoItem.REQUEST_PRIORITY));
+ assertTrue(locationClients.getJSONObject(0).has(LocationInfoItem.LOCATION_DURATION));
+
+ assertTrue(locationClients.getJSONObject(1).has(LocationInfoItem.PACKAGE));
+ assertTrue(locationClients.getJSONObject(1).has(LocationInfoItem.EFFECTIVE_INTERVAL));
+ assertTrue(locationClients.getJSONObject(1).has(LocationInfoItem.MIN_INTERVAL));
+ assertTrue(locationClients.getJSONObject(1).has(LocationInfoItem.MAX_INTERVAL));
+ assertTrue(locationClients.getJSONObject(1).has(LocationInfoItem.REQUEST_PRIORITY));
+ assertTrue(locationClients.getJSONObject(1).has(LocationInfoItem.LOCATION_DURATION));
+ }
+
+ /**
+ * Test that {@link LocationDumpsItem#getLocationClients()} returns correctly.
+ */
+ public void testGetLocationDumps() {
+ LocationDumpsItem item = new LocationDumpsItem();
+ item.addLocationClient("com.google.android.gms", 500, 60, 1000, "PRIORITY_ACCURACY", 45);
+
+ assertEquals(item.getLocationClients().size(), 1);
+ LocationInfoItem client = item.getLocationClients().iterator().next();
+ assertNotNull(client);
+ assertEquals(client.getPackage(), "com.google.android.gms");
+ assertEquals(client.getEffectiveInterval(), 500);
+ assertEquals(client.getMinInterval(), 60);
+ assertEquals(client.getMaxInterval(), 1000);
+ assertEquals(client.getPriority(), "PRIORITY_ACCURACY");
+ assertEquals(client.getDuration(), 45);
+ }
+
+}
diff --git a/javatests/com/android/loganalysis/item/MemInfoItemTest.java b/javatests/com/android/loganalysis/item/MemInfoItemTest.java
new file mode 100644
index 0000000..3a8f307
--- /dev/null
+++ b/javatests/com/android/loganalysis/item/MemInfoItemTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2013 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.loganalysis.item;
+
+import junit.framework.TestCase;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Unit test for {@link MemInfoItem}.
+ */
+public class MemInfoItemTest extends TestCase {
+
+ /**
+ * Test that {@link MemInfoItem#toJson()} returns correctly.
+ */
+ public void testToJson() throws JSONException {
+ MemInfoItem item = new MemInfoItem();
+ item.put("foo", 123L);
+ item.put("bar", 456L);
+ item.setText("foo: 123 kB\nbar: 456 kB");
+
+ // Convert to JSON string and back again
+ JSONObject output = new JSONObject(item.toJson().toString());
+
+ assertTrue(output.has(MemInfoItem.LINES));
+ assertTrue(output.get(MemInfoItem.LINES) instanceof JSONObject);
+ assertTrue(output.has(MemInfoItem.TEXT));
+ assertEquals("foo: 123 kB\nbar: 456 kB", output.get(MemInfoItem.TEXT));
+
+ JSONObject lines = output.getJSONObject(MemInfoItem.LINES);
+
+ assertEquals(2, lines.length());
+
+ assertEquals(123, lines.get("foo"));
+ assertEquals(456, lines.get("bar"));
+ }
+}
diff --git a/javatests/com/android/loganalysis/item/MonkeyLogItemTest.java b/javatests/com/android/loganalysis/item/MonkeyLogItemTest.java
new file mode 100644
index 0000000..cc2867c
--- /dev/null
+++ b/javatests/com/android/loganalysis/item/MonkeyLogItemTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2013 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.loganalysis.item;
+
+import junit.framework.TestCase;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Unit test for {@link MonkeyLogItem}.
+ */
+public class MonkeyLogItemTest extends TestCase {
+ /**
+ * Test that {@link MonkeyLogItem#toJson()} returns correctly.
+ */
+ public void testToJson() throws JSONException {
+ MonkeyLogItem item = new MonkeyLogItem();
+ item.addCategory("category1");
+ item.addCategory("category2");
+ item.addPackage("package1");
+ item.addPackage("package2");
+ item.addPackage("package3");
+
+ // Convert to JSON string and back again
+ JSONObject output = new JSONObject(item.toJson().toString());
+
+ assertTrue(output.has(MonkeyLogItem.CATEGORIES));
+ assertTrue(output.get(MonkeyLogItem.CATEGORIES) instanceof JSONArray);
+
+ JSONArray categories = output.getJSONArray(MonkeyLogItem.CATEGORIES);
+
+ assertEquals(2, categories.length());
+ assertTrue(in("category1", categories));
+ assertTrue(in("category2", categories));
+
+ JSONArray packages = output.getJSONArray(MonkeyLogItem.PACKAGES);
+
+ assertEquals(3, packages.length());
+ assertTrue(in("package1", packages));
+ assertTrue(in("package2", packages));
+ assertTrue(in("package3", packages));
+ }
+
+ private boolean in(String value, JSONArray array) throws JSONException {
+ for (int i = 0; i < array.length(); i++) {
+ if (value.equals(array.get(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/javatests/com/android/loganalysis/item/ProcrankItemTest.java b/javatests/com/android/loganalysis/item/ProcrankItemTest.java
new file mode 100644
index 0000000..7f5d309
--- /dev/null
+++ b/javatests/com/android/loganalysis/item/ProcrankItemTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2013 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.loganalysis.item;
+
+import junit.framework.TestCase;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Unit test for {@link ProcrankItem}.
+ */
+public class ProcrankItemTest extends TestCase {
+
+ /**
+ * Test that {@link ProcrankItem#toJson()} returns correctly.
+ */
+ public void testToJson() throws JSONException {
+ ProcrankItem item = new ProcrankItem();
+ item.addProcrankLine(0, "process0", 1, 2, 3, 4);
+ item.addProcrankLine(5, "process1", 6, 7, 8, 9);
+ item.setText("foo\nbar");
+
+ // Convert to JSON string and back again
+ JSONObject output = new JSONObject(item.toJson().toString());
+
+ assertTrue(output.has(ProcrankItem.LINES));
+ assertTrue(output.get(ProcrankItem.LINES) instanceof JSONArray);
+ assertTrue(output.has(ProcrankItem.TEXT));
+ assertEquals("foo\nbar", output.get(ProcrankItem.TEXT));
+
+ JSONArray lines = output.getJSONArray(ProcrankItem.LINES);
+
+ assertEquals(2, lines.length());
+ assertTrue(lines.get(0) instanceof JSONObject);
+
+ JSONObject line = lines.getJSONObject(0);
+
+ assertEquals(0, line.get(ProcrankItem.PID));
+ assertEquals("process0", line.get(ProcrankItem.PROCESS_NAME));
+ assertEquals(1, line.get(ProcrankItem.VSS));
+ assertEquals(2, line.get(ProcrankItem.RSS));
+ assertEquals(3, line.get(ProcrankItem.PSS));
+ assertEquals(4, line.get(ProcrankItem.USS));
+ }
+}
diff --git a/javatests/com/android/loganalysis/item/SmartMonkeyLogItemTest.java b/javatests/com/android/loganalysis/item/SmartMonkeyLogItemTest.java
new file mode 100644
index 0000000..4dd5597
--- /dev/null
+++ b/javatests/com/android/loganalysis/item/SmartMonkeyLogItemTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2013 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.loganalysis.item;
+
+import junit.framework.TestCase;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Date;
+
+/**
+ * Unit test for {@link SmartMonkeyLogItem}.
+ */
+public class SmartMonkeyLogItemTest extends TestCase {
+ /**
+ * Test that {@link SmartMonkeyLogItem#toJson()} returns correctly.
+ */
+ public void testToJson() throws JSONException {
+ SmartMonkeyLogItem item = new SmartMonkeyLogItem();
+ item.addApplication("application1");
+ item.addPackage("package1");
+ item.addAnrTime(new Date());
+ item.addCrashTime(new Date());
+
+ // Convert to JSON string and back again
+ JSONObject output = new JSONObject(item.toJson().toString());
+
+ assertTrue(output.has(SmartMonkeyLogItem.APPLICATIONS));
+ assertTrue(output.get(SmartMonkeyLogItem.APPLICATIONS) instanceof JSONArray);
+ assertTrue(output.has(SmartMonkeyLogItem.PACKAGES));
+ assertTrue(output.get(SmartMonkeyLogItem.PACKAGES) instanceof JSONArray);
+ assertTrue(output.has(SmartMonkeyLogItem.ANR_TIMES));
+ assertTrue(output.get(SmartMonkeyLogItem.ANR_TIMES) instanceof JSONArray);
+ assertTrue(output.has(SmartMonkeyLogItem.CRASH_TIMES));
+ assertTrue(output.get(SmartMonkeyLogItem.CRASH_TIMES) instanceof JSONArray);
+ }
+}
diff --git a/javatests/com/android/loganalysis/item/SystemPropsItemTest.java b/javatests/com/android/loganalysis/item/SystemPropsItemTest.java
new file mode 100644
index 0000000..b9b6675
--- /dev/null
+++ b/javatests/com/android/loganalysis/item/SystemPropsItemTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2013 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.loganalysis.item;
+
+import junit.framework.TestCase;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Unit test for {@link SystemPropsItem}.
+ */
+public class SystemPropsItemTest extends TestCase {
+
+ /**
+ * Test that {@link SystemPropsItem#toJson()} returns correctly.
+ */
+ public void testToJson() throws JSONException {
+ SystemPropsItem item = new SystemPropsItem();
+ item.put("foo", "123");
+ item.put("bar", "456");
+ item.setText("[foo]: [123]\n[bar]: [456]");
+
+ // Convert to JSON string and back again
+ JSONObject output = new JSONObject(item.toJson().toString());
+
+ assertTrue(output.has(SystemPropsItem.LINES));
+ assertTrue(output.get(SystemPropsItem.LINES) instanceof JSONObject);
+ assertTrue(output.has(SystemPropsItem.TEXT));
+ assertEquals("[foo]: [123]\n[bar]: [456]", output.get(SystemPropsItem.TEXT));
+
+ JSONObject lines = output.getJSONObject(SystemPropsItem.LINES);
+
+ assertEquals(2, lines.length());
+
+ assertEquals("123", lines.get("foo"));
+ assertEquals("456", lines.get("bar"));
+ }
+}
diff --git a/javatests/com/android/loganalysis/item/TopItemTest.java b/javatests/com/android/loganalysis/item/TopItemTest.java
new file mode 100644
index 0000000..2df01d0
--- /dev/null
+++ b/javatests/com/android/loganalysis/item/TopItemTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2013 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.loganalysis.item;
+
+import junit.framework.TestCase;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Unit test for {@link TopItem}.
+ */
+public class TopItemTest extends TestCase {
+
+ /**
+ * Test that {@link TopItem#toJson()} returns correctly.
+ */
+ public void testToJson() throws JSONException {
+ TopItem item = new TopItem();
+ item.setText("User 20%, System 20%, IOW 5%, IRQ 3%");
+
+ // Convert to JSON string and back again
+ JSONObject output = new JSONObject(item.toJson().toString());
+
+ assertTrue(output.has(TopItem.TEXT));
+ assertEquals("User 20%, System 20%, IOW 5%, IRQ 3%", output.get(TopItem.TEXT));
+ }
+}
diff --git a/javatests/com/android/loganalysis/item/WakelockItemTest.java b/javatests/com/android/loganalysis/item/WakelockItemTest.java
new file mode 100644
index 0000000..f570a7b
--- /dev/null
+++ b/javatests/com/android/loganalysis/item/WakelockItemTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 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.loganalysis.item;
+
+import com.android.loganalysis.item.WakelockItem.WakelockInfoItem;
+
+import junit.framework.TestCase;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Unit test for {@link WakelockItem}.
+ */
+public class WakelockItemTest extends TestCase {
+
+ /**
+ * Test that {@link WakelockItem#toJson()} returns correctly.
+ */
+ public void testToJson() throws JSONException {
+ WakelockItem item = new WakelockItem();
+ item.addWakeLock("screen","u100", 150000, 25,
+ WakelockItem.WakeLockCategory.PARTIAL_WAKELOCK);
+ item.addWakeLock("wlan_rx", 150000, 25,
+ WakelockItem.WakeLockCategory.KERNEL_WAKELOCK);
+
+ // Convert to JSON string and back again
+ JSONObject output = new JSONObject(item.toJson().toString());
+
+ assertTrue(output.has(WakelockItem.WAKELOCKS));
+ assertTrue(output.get(WakelockItem.WAKELOCKS) instanceof JSONArray);
+
+ JSONArray wakelockInfo = output.getJSONArray(WakelockItem.WAKELOCKS);
+
+ assertEquals(2, wakelockInfo.length());
+ assertTrue(wakelockInfo.getJSONObject(0).has(WakelockInfoItem.NAME));
+ assertTrue(wakelockInfo.getJSONObject(0).has(WakelockInfoItem.PROCESS_UID));
+ assertTrue(wakelockInfo.getJSONObject(0).has(WakelockInfoItem.HELD_TIME));
+ assertTrue(wakelockInfo.getJSONObject(0).has(WakelockInfoItem.LOCKED_COUNT));
+ assertTrue(wakelockInfo.getJSONObject(0).has(WakelockInfoItem.CATEGORY));
+
+ assertTrue(wakelockInfo.getJSONObject(1).has(WakelockInfoItem.NAME));
+ assertFalse(wakelockInfo.getJSONObject(1).has(WakelockInfoItem.PROCESS_UID));
+ assertTrue(wakelockInfo.getJSONObject(1).has(WakelockInfoItem.HELD_TIME));
+ assertTrue(wakelockInfo.getJSONObject(1).has(WakelockInfoItem.LOCKED_COUNT));
+ assertTrue(wakelockInfo.getJSONObject(1).has(WakelockInfoItem.CATEGORY));
+
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/AbstractSectionParserTest.java b/javatests/com/android/loganalysis/parser/AbstractSectionParserTest.java
new file mode 100644
index 0000000..1092d0e
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/AbstractSectionParserTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.loganalysis.parser;
+
+import com.android.loganalysis.item.IItem;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 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 IItem parse(List<String> lines) {
+ for (String line : lines) {
+ parseLine(line);
+ }
+ commit();
+ return null;
+ }
+ };
+ }
+
+ private static class FakeBlockParser implements IParser {
+ private String mExpected = null;
+ private int mCalls = 0;
+
+ public FakeBlockParser(String expected) {
+ mExpected = expected;
+ }
+
+ public int getCalls() {
+ return mCalls;
+ }
+
+ @Override
+ public IItem parse(List<String> input) {
+ assertEquals(1, input.size());
+ assertEquals("parseBlock() got unexpected input!", mExpected, input.get(0));
+ mCalls += 1;
+ return null;
+ }
+ }
+
+ /**
+ * 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.parse(lines);
+
+ // 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());
+ }
+ }
+}
+
diff --git a/javatests/com/android/loganalysis/parser/ActivityServiceParserTest.java b/javatests/com/android/loganalysis/parser/ActivityServiceParserTest.java
new file mode 100644
index 0000000..57aff9a
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/ActivityServiceParserTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 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.loganalysis.parser;
+
+import com.android.loganalysis.item.ActivityServiceItem;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link ActivityServiceParser}
+ */
+public class ActivityServiceParserTest extends TestCase {
+
+ /**
+ * Test that normal input is parsed.
+ */
+ public void testActivityServiceParser() {
+ List<String> inputBlock = Arrays.asList(
+ "SERVICE com.google.android.gms/"
+ + "com.google.android.location.internal.GoogleLocationManagerService f4c9e9d "
+ + "pid=1494",
+ "Client:",
+ " nothing to dump",
+ "Location Request History By Package:",
+ "Interval effective/min/max 1/0/0[s] Duration: 140[minutes] "
+ + "[com.google.android.gms, PRIORITY_NO_POWER, UserLocationProducer] "
+ + "Num requests: 2 Active: true",
+ "Interval effective/min/max 284/285/3600[s] Duration: 140[minutes] "
+ + "[com.google.android.googlequicksearchbox, PRIORITY_BALANCED_POWER_ACCURACY] "
+ + "Num requests: 5 Active: true",
+ "FLP WakeLock Count:",
+ "SERVICE com.android.server.telecom/.components.BluetoothPhoneService 98ab pid=802",
+ "Interval effective/min/max 1/0/0[s] Duration: 140[minutes] "
+ + "[com.google.android.gms, PRIORITY_NO_POWER, UserLocationProducer] "
+ + "Num requests: 2 Active: true",
+ "");
+
+ ActivityServiceItem activityService = new ActivityServiceParser().parse(inputBlock);
+ assertNotNull(activityService.getLocationDumps());
+ assertEquals(activityService.getLocationDumps().getLocationClients().size(), 2);
+ }
+}
+
diff --git a/javatests/com/android/loganalysis/parser/AnrParserTest.java b/javatests/com/android/loganalysis/parser/AnrParserTest.java
new file mode 100644
index 0000000..e6afe80
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/AnrParserTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2012 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.loganalysis.parser;
+
+import com.android.loganalysis.item.AnrItem;
+import com.android.loganalysis.util.ArrayUtil;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link AnrParser}.
+ */
+public class AnrParserTest extends TestCase {
+
+ /**
+ * Test that ANRs are parsed for the header "ANR (application not responding) in process: app"
+ */
+ public void testParse_application_not_responding() {
+ List<String> lines = Arrays.asList(
+ "ANR (application not responding) in process: com.android.package",
+ "Reason: keyDispatchingTimedOut",
+ "Load: 0.71 / 0.83 / 0.51",
+ "CPU usage from 4357ms to -1434ms ago:",
+ " 22% 3378/com.android.package: 19% user + 3.6% kernel / faults: 73 minor 1 major",
+ " 16% 312/system_server: 12% user + 4.1% kernel / faults: 1082 minor 6 major",
+ "33% TOTAL: 21% user + 11% kernel + 0.3% iowait",
+ "CPU usage from 907ms to 1431ms later:",
+ " 14% 121/mediaserver: 11% user + 3.7% kernel / faults: 17 minor",
+ " 3.7% 183/AudioOut_2: 3.7% user + 0% kernel",
+ " 12% 312/system_server: 5.5% user + 7.4% kernel / faults: 6 minor",
+ " 5.5% 366/InputDispatcher: 0% user + 5.5% kernel",
+ "18% TOTAL: 11% user + 7.5% kernel");
+
+ AnrItem anr = new AnrParser().parse(lines);
+ assertNotNull(anr);
+ assertEquals("com.android.package", anr.getApp());
+ assertEquals("keyDispatchingTimedOut", anr.getReason());
+ assertEquals(0.71, anr.getLoad(AnrItem.LoadCategory.LOAD_1));
+ assertEquals(0.83, anr.getLoad(AnrItem.LoadCategory.LOAD_5));
+ assertEquals(0.51, anr.getLoad(AnrItem.LoadCategory.LOAD_15));
+ assertEquals(33.0, anr.getCpuUsage(AnrItem.CpuUsageCategory.TOTAL));
+ assertEquals(21.0, anr.getCpuUsage(AnrItem.CpuUsageCategory.USER));
+ assertEquals(11.0, anr.getCpuUsage(AnrItem.CpuUsageCategory.KERNEL));
+ assertEquals(0.3, anr.getCpuUsage(AnrItem.CpuUsageCategory.IOWAIT));
+ assertEquals(ArrayUtil.join("\n", lines), anr.getStack());
+ }
+
+ /**
+ * Test that ANRs are parsed for the header "ANR in app"
+ */
+ public void testParse_anr_in_app() {
+ List<String> lines = Arrays.asList(
+ "ANR in com.android.package",
+ "Reason: keyDispatchingTimedOut",
+ "Load: 0.71 / 0.83 / 0.51",
+ "CPU usage from 4357ms to -1434ms ago:",
+ " 22% 3378/com.android.package: 19% user + 3.6% kernel / faults: 73 minor 1 major",
+ " 16% 312/system_server: 12% user + 4.1% kernel / faults: 1082 minor 6 major",
+ "33% TOTAL: 21% user + 11% kernel + 0.3% iowait",
+ "CPU usage from 907ms to 1431ms later:",
+ " 14% 121/mediaserver: 11% user + 3.7% kernel / faults: 17 minor",
+ " 3.7% 183/AudioOut_2: 3.7% user + 0% kernel",
+ " 12% 312/system_server: 5.5% user + 7.4% kernel / faults: 6 minor",
+ " 5.5% 366/InputDispatcher: 0% user + 5.5% kernel",
+ "18% TOTAL: 11% user + 7.5% kernel");
+
+ AnrItem anr = new AnrParser().parse(lines);
+ assertNotNull(anr);
+ assertEquals("com.android.package", anr.getApp());
+ assertEquals("keyDispatchingTimedOut", anr.getReason());
+ assertEquals(0.71, anr.getLoad(AnrItem.LoadCategory.LOAD_1));
+ assertEquals(0.83, anr.getLoad(AnrItem.LoadCategory.LOAD_5));
+ assertEquals(0.51, anr.getLoad(AnrItem.LoadCategory.LOAD_15));
+ assertEquals(33.0, anr.getCpuUsage(AnrItem.CpuUsageCategory.TOTAL));
+ assertEquals(21.0, anr.getCpuUsage(AnrItem.CpuUsageCategory.USER));
+ assertEquals(11.0, anr.getCpuUsage(AnrItem.CpuUsageCategory.KERNEL));
+ assertEquals(0.3, anr.getCpuUsage(AnrItem.CpuUsageCategory.IOWAIT));
+ assertEquals(ArrayUtil.join("\n", lines), anr.getStack());
+ }
+
+ /**
+ * Test that ANRs are parsed for the header "ANR in app (class/package)"
+ */
+ public void testParse_anr_in_app_class_package() {
+ List<String> lines = Arrays.asList(
+ "ANR in com.android.package (com.android.package/.Activity)",
+ "Reason: keyDispatchingTimedOut",
+ "Load: 0.71 / 0.83 / 0.51",
+ "CPU usage from 4357ms to -1434ms ago:",
+ " 22% 3378/com.android.package: 19% user + 3.6% kernel / faults: 73 minor 1 major",
+ " 16% 312/system_server: 12% user + 4.1% kernel / faults: 1082 minor 6 major",
+ "33% TOTAL: 21% user + 11% kernel + 0.3% iowait",
+ "CPU usage from 907ms to 1431ms later:",
+ " 14% 121/mediaserver: 11% user + 3.7% kernel / faults: 17 minor",
+ " 3.7% 183/AudioOut_2: 3.7% user + 0% kernel",
+ " 12% 312/system_server: 5.5% user + 7.4% kernel / faults: 6 minor",
+ " 5.5% 366/InputDispatcher: 0% user + 5.5% kernel",
+ "18% TOTAL: 11% user + 7.5% kernel");
+
+ AnrItem anr = new AnrParser().parse(lines);
+ assertNotNull(anr);
+ assertEquals("com.android.package", anr.getApp());
+ assertEquals("keyDispatchingTimedOut", anr.getReason());
+ assertEquals(0.71, anr.getLoad(AnrItem.LoadCategory.LOAD_1));
+ assertEquals(0.83, anr.getLoad(AnrItem.LoadCategory.LOAD_5));
+ assertEquals(0.51, anr.getLoad(AnrItem.LoadCategory.LOAD_15));
+ assertEquals(33.0, anr.getCpuUsage(AnrItem.CpuUsageCategory.TOTAL));
+ assertEquals(21.0, anr.getCpuUsage(AnrItem.CpuUsageCategory.USER));
+ assertEquals(11.0, anr.getCpuUsage(AnrItem.CpuUsageCategory.KERNEL));
+ assertEquals(0.3, anr.getCpuUsage(AnrItem.CpuUsageCategory.IOWAIT));
+ assertEquals(ArrayUtil.join("\n", lines), anr.getStack());
+ }
+
+ /**
+ * Test that ANRs with PID are parsed.
+ */
+ public void testParse_anr_in_app_class_package_pid() {
+ List<String> lines = Arrays.asList(
+ "ANR in com.android.package (com.android.package/.Activity)",
+ "PID: 1234",
+ "Reason: keyDispatchingTimedOut",
+ "Load: 0.71 / 0.83 / 0.51",
+ "CPU usage from 4357ms to -1434ms ago:",
+ " 22% 3378/com.android.package: 19% user + 3.6% kernel / faults: 73 minor 1 major",
+ " 16% 312/system_server: 12% user + 4.1% kernel / faults: 1082 minor 6 major",
+ "33% TOTAL: 21% user + 11% kernel + 0.3% iowait",
+ "CPU usage from 907ms to 1431ms later:",
+ " 14% 121/mediaserver: 11% user + 3.7% kernel / faults: 17 minor",
+ " 3.7% 183/AudioOut_2: 3.7% user + 0% kernel",
+ " 12% 312/system_server: 5.5% user + 7.4% kernel / faults: 6 minor",
+ " 5.5% 366/InputDispatcher: 0% user + 5.5% kernel",
+ "18% TOTAL: 11% user + 7.5% kernel");
+
+ AnrItem anr = new AnrParser().parse(lines);
+ assertNotNull(anr);
+ assertEquals("com.android.package", anr.getApp());
+ assertEquals("keyDispatchingTimedOut", anr.getReason());
+ assertEquals(1234, anr.getPid().intValue());
+ assertEquals(0.71, anr.getLoad(AnrItem.LoadCategory.LOAD_1));
+ assertEquals(0.83, anr.getLoad(AnrItem.LoadCategory.LOAD_5));
+ assertEquals(0.51, anr.getLoad(AnrItem.LoadCategory.LOAD_15));
+ assertEquals(33.0, anr.getCpuUsage(AnrItem.CpuUsageCategory.TOTAL));
+ assertEquals(21.0, anr.getCpuUsage(AnrItem.CpuUsageCategory.USER));
+ assertEquals(11.0, anr.getCpuUsage(AnrItem.CpuUsageCategory.KERNEL));
+ assertEquals(0.3, anr.getCpuUsage(AnrItem.CpuUsageCategory.IOWAIT));
+ assertEquals(ArrayUtil.join("\n", lines), anr.getStack());
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/BatteryDischargeStatsInfoParserTest.java b/javatests/com/android/loganalysis/parser/BatteryDischargeStatsInfoParserTest.java
new file mode 100644
index 0000000..691810c
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/BatteryDischargeStatsInfoParserTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2016 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.loganalysis.parser;
+
+import com.android.loganalysis.item.BatteryDischargeStatsInfoItem;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link BatteryDischargeStatsInfoParser}
+ */
+public class BatteryDischargeStatsInfoParserTest extends TestCase {
+
+ /**
+ * Test that normal input is parsed correctly.
+ */
+ public void testBatteryDischargeStats() {
+ List<String> input = Arrays.asList(
+ " #0: +4m53s738ms to 0 (screen-on, power-save-off, device-idle-off)",
+ " #1: +4m5s586ms to 1 (screen-on, power-save-off, device-idle-off)",
+ " #2: +3m0s157ms to 2 (screen-on, power-save-off, device-idle-off)",
+ " #3: +2m52s243ms to 3 (screen-on, power-save-off, device-idle-off)",
+ " #4: +2m27s599ms to 4 (screen-on, power-save-off, device-idle-off)",
+ " #5: +5m0s172ms to 5 (screen-on, power-save-off, device-idle-off)",
+ " #6: +2m21s664ms to 6 (screen-on, power-save-off, device-idle-off)",
+ " #7: +5m18s811ms to 7 (screen-on, power-save-off, device-idle-off)",
+ " #8: +3m35s622ms to 8 (screen-on, power-save-off, device-idle-off)",
+ " #9: +4m52s605ms to 9 (screen-on, power-save-off, device-idle-off)",
+ " #10: +4m46s779ms to 10 (screen-on, power-save-off, device-idle-off)",
+ " #11: +4m0s200ms to 11 (screen-on, power-save-off, device-idle-off)",
+ " #12: +4m44s941ms to 12 (screen-on, power-save-off, device-idle-off)",
+ " #13: +3m31s163ms to 13 (screen-on, power-save-off, device-idle-off)",
+ " #14: +4m17s293ms to 14 (screen-on, power-save-off, device-idle-off)",
+ " #15: +3m11s584ms to 15 (screen-on, power-save-off, device-idle-off)",
+ " #16: +5m52s923ms to 16 (screen-on, power-save-off, device-idle-off)",
+ " #17: +3m7s34ms to 17 (screen-on, power-save-off, device-idle-off)",
+ " #18: +5m59s810ms to 18 (screen-on, power-save-off, device-idle-off)",
+ " #19: +6m15s275ms to 19 (screen-on, power-save-off, device-idle-off)",
+ " #20: +4m0s55ms to 20 (screen-on, power-save-off, device-idle-off)",
+ " #21: +5m21s911ms to 21 (screen-on, power-save-off, device-idle-off)",
+ " #22: +4m0s171ms to 22 (screen-on, power-save-off, device-idle-off)",
+ " #23: +5m22s820ms to 23 (screen-on, power-save-off, device-idle-off)",
+ " #24: +3m44s752ms to 24 (screen-on, power-save-off, device-idle-off)",
+ " #25: +4m15s130ms to 25 (screen-on, power-save-off, device-idle-off)",
+ " #26: +3m48s654ms to 26 (screen-on, power-save-off, device-idle-off)",
+ " #27: +5m0s294ms to 27 (screen-on, power-save-off, device-idle-off)",
+ " #28: +3m11s169ms to 28 (screen-on, power-save-off, device-idle-off)",
+ " #29: +4m48s194ms to 29 (screen-on, power-save-off, device-idle-off)",
+ " #30: +5m0s319ms to 30 (screen-on, power-save-off, device-idle-off)",
+ " #31: +2m42s209ms to 31 (screen-on, power-save-off, device-idle-off)",
+ " #32: +5m29s187ms to 32 (screen-on, power-save-off, device-idle-off)",
+ " #33: +3m32s392ms to 33 (screen-on, power-save-off, device-idle-off)",
+ " #34: +5m27s578ms to 34 (screen-on, power-save-off, device-idle-off)",
+ " #35: +3m47s37ms to 35 (screen-on, power-save-off, device-idle-off)",
+ " #36: +5m18s916ms to 36 (screen-on, power-save-off, device-idle-off)",
+ " #37: +2m54s111ms to 37 (screen-on, power-save-off, device-idle-off)",
+ " #38: +6m32s480ms to 38 (screen-on, power-save-off, device-idle-off)",
+ " #39: +5m24s906ms to 39 (screen-on, power-save-off, device-idle-off)",
+ " #40: +3m2s451ms to 40 (screen-on, power-save-off, device-idle-off)",
+ " #41: +6m29s762ms to 41 (screen-on, power-save-off, device-idle-off)",
+ " #42: +3m31s933ms to 42 (screen-on, power-save-off, device-idle-off)",
+ " #43: +4m58s520ms to 43 (screen-on, power-save-off, device-idle-off)",
+ " #44: +4m31s130ms to 44 (screen-on, power-save-off, device-idle-off)",
+ " #45: +5m28s870ms to 45 (screen-on, power-save-off, device-idle-off)",
+ " #46: +3m54s809ms to 46 (screen-on, power-save-off, device-idle-off)",
+ " #47: +5m5s105ms to 47 (screen-on, power-save-off, device-idle-off)",
+ " #48: +3m50s427ms to 48 (screen-on, power-save-off, device-idle-off)",
+ " #49: +6m0s344ms to 49 (screen-on, power-save-off, device-idle-off)",
+ " #50: +5m2s952ms to 50 (screen-on, power-save-off, device-idle-off)",
+ " #51: +3m6s120ms to 51 (screen-on, power-save-off, device-idle-off)",
+ " #52: +5m34s839ms to 52 (screen-on, power-save-off, device-idle-off)",
+ " #53: +2m33s473ms to 53 (screen-on, power-save-off, device-idle-off)",
+ " #54: +4m51s873ms to 54 (screen-on, power-save-off, device-idle-off)",
+ " #55: +3m30s41ms to 55 (screen-on, power-save-off, device-idle-off)",
+ " #56: +4m29s879ms to 56 (screen-on, power-save-off, device-idle-off)",
+ " #57: +3m41s722ms to 57 (screen-on, power-save-off, device-idle-off)",
+ " #58: +4m29s72ms to 58 (screen-on, power-save-off, device-idle-off)",
+ " #59: +4m49s351ms to 59 (screen-on, power-save-off, device-idle-off)",
+ " #60: +3m51s605ms to 60 (screen-on, power-save-off, device-idle-off)",
+ " #61: +5m8s334ms to 61 (screen-on, power-save-off, device-idle-off)",
+ " #62: +2m53s153ms to 62 (screen-on, power-save-off, device-idle-off)",
+ " #63: +6m0s234ms to 63 (screen-on, power-save-off, device-idle-off)",
+ " #64: +3m20s345ms to 64 (screen-on, power-save-off, device-idle-off)",
+ " #65: +5m46s211ms to 65 (screen-on, power-save-off, device-idle-off)",
+ " #66: +3m40s147ms to 66 (screen-on, power-save-off, device-idle-off)",
+ " #67: +5m14s559ms to 67 (screen-on, power-save-off, device-idle-off)",
+ " #68: +4m0s183ms to 68 (screen-on, power-save-off, device-idle-off)",
+ " #69: +5m23s334ms to 69 (screen-on, power-save-off, device-idle-off)",
+ " #70: +5m45s493ms to 70 (screen-on, power-save-off, device-idle-off)",
+ " #71: +4m0s179ms to 71 (screen-on, power-save-off, device-idle-off)",
+ " #72: +5m45s462ms to 72 (screen-on, power-save-off, device-idle-off)",
+ " #73: +3m10s449ms to 73 (screen-on, power-save-off, device-idle-off)",
+ " #74: +6m29s370ms to 74 (screen-on, power-save-off, device-idle-off)",
+ " #75: +3m20s414ms to 75 (screen-on, power-save-off, device-idle-off)",
+ " #76: +5m10s462ms to 76 (screen-on, power-save-off, device-idle-off)",
+ " #77: +4m20s500ms to 77 (screen-on, power-save-off, device-idle-off)",
+ " #78: +5m39s504ms to 78 (screen-on, power-save-off, device-idle-off)",
+ " #79: +5m59s819ms to 79 (screen-on, power-save-off, device-idle-off)",
+ " #80: +3m0s126ms to 80 (screen-on, power-save-off, device-idle-off)",
+ " #81: +6m20s912ms to 81 (screen-on, power-save-off, device-idle-off)",
+ " #82: +4m0s199ms to 82 (screen-on, power-save-off, device-idle-off)",
+ " #83: +5m23s470ms to 83 (screen-on, power-save-off, device-idle-off)",
+ " #84: +3m15s368ms to 84 (screen-on, power-save-off, device-idle-off)",
+ " #85: +6m18s625ms to 85 (screen-on, power-save-off, device-idle-off)",
+ " #86: +3m41s417ms to 86 (screen-on, power-save-off, device-idle-off)",
+ " #87: +5m32s257ms to 87 (screen-on, power-save-off, device-idle-off)",
+ " #88: +4m0s212ms to 88 (screen-on, power-save-off, device-idle-off)",
+ " #89: +5m41s218ms to 89 (screen-on, power-save-off, device-idle-off)",
+ " #90: +5m46s333ms to 90 (screen-on, power-save-off, device-idle-off)",
+ " #91: +3m58s362ms to 91 (screen-on, power-save-off, device-idle-off)",
+ " #92: +5m1s593ms to 92 (screen-on, power-save-off, device-idle-off)",
+ " #93: +4m47s33ms to 93 (screen-on, power-save-off, device-idle-off)",
+ " #94: +6m0s417ms to 94 (screen-on, power-save-off, device-idle-off)",
+ " #95: +3m9s77ms to 95 (screen-on, power-save-off, device-idle-off)",
+ " #96: +7m0s308ms to 96 (screen-on, power-save-off, device-idle-off)",
+ " #97: +3m29s741ms to 97 (screen-on, power-save-off, device-idle-off)",
+ " #98: +7m12s748ms to 98 (screen-on, power-save-off, device-idle-off)",
+ " Estimated screen on time: 7h 36m 13s 0ms ");
+
+ BatteryDischargeStatsInfoItem infoItem = new BatteryDischargeStatsInfoParser().parse(input);
+ assertEquals(99, infoItem.getDischargePercentage());
+ assertEquals(27099330, infoItem.getDischargeDuration());
+ assertEquals(27207848, infoItem.getProjectedBatteryLife());
+ }
+
+ /**
+ * Test that input with only a few discharge stats.
+ */
+ public void testBatteryDischargeStatsWithTop5Percentages() {
+ List<String> input = Arrays.asList(
+ " #95: +3m9s77ms to 95 (screen-on, power-save-off, device-idle-off)",
+ " #96: +7m0s308ms to 96 (screen-on, power-save-off, device-idle-off)",
+ " #97: +3m29s741ms to 97 (screen-on, power-save-off, device-idle-off)",
+ " #98: +7m12s748ms to 98 (screen-on, power-save-off, device-idle-off)",
+ " Estimated screen on time: 7h 36m 13s 0ms ");
+
+ BatteryDischargeStatsInfoItem infoItem = new BatteryDischargeStatsInfoParser().parse(input);
+
+ try {
+ infoItem.getProjectedBatteryLife();
+ fail("Projected battery life is expected to be undefined when there are not enough" +
+ " samples of battery discharge below 95 percent.");
+ } catch (NullPointerException e) {
+ // NullPointerException expected.
+ }
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/BatteryStatsDetailedInfoParserTest.java b/javatests/com/android/loganalysis/parser/BatteryStatsDetailedInfoParserTest.java
new file mode 100644
index 0000000..b176fa2
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/BatteryStatsDetailedInfoParserTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2015 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.loganalysis.parser;
+
+import com.android.loganalysis.item.BatteryStatsDetailedInfoItem;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link BatteryStatsDetailedInfoParser}
+ */
+public class BatteryStatsDetailedInfoParserTest extends TestCase {
+
+ /**
+ * Test that normal input is parsed.
+ */
+ public void testBatteryStatsDetailedInfoParser() {
+ List<String> inputBlock = Arrays.asList(
+ " Time on battery: 2h 21m 5s 622ms (12.0%) realtime, 7m 54s 146ms (0.7%) uptime",
+ " Time on battery screen off: 2h 5m 55s 3ms (1%) realtime, 7m 4s 5ms (7%) uptime",
+ " Total run time: 19h 38m 43s 650ms realtime, 17h 25m 32s 175ms uptime",
+ " All kernel wake locks:",
+ " Kernel Wake lock PowerManagerService.WakeLocks: 1h 3m 50s 5ms (8 times) realtime",
+ " Kernel Wake lock event0-2656 : 3m 49s 268ms (2399 times) realtime",
+ " Kernel Wake lock wlan_wd_wake: 3m 34s 639ms (1751 times) realtime",
+ " Kernel Wake lock wlan_rx_wake: 3m 19s 887ms (225 times) realtime",
+ " Kernel Wake lock wlan_tx_wake: 2m 19s 887ms (225 times) realtime",
+ " Kernel Wake lock tx_wake: 1m 19s 887ms (225 times) realtime",
+ " ",
+ " All partial wake locks:",
+ " Wake lock u0a7 NlpWakeLock: 8m 13s 203ms (1479 times) realtime",
+ " Wake lock u0a7 NlpCollectorWakeLock: 6m 29s 18ms (238 times) realtime",
+ " Wake lock u0a7 GCM_CONN_ALARM: 6m 8s 587ms (239 times) realtime",
+ " Wake lock 1000 *alarm*: 5m 11s 316ms (1469 times) realtime",
+ " Wake lock u10 xxx: 4m 11s 316ms (1469 times) realtime",
+ " Wake lock u30 cst: 2m 11s 316ms (1469 times) realtime",
+ " ",
+ " All wakeup reasons:",
+ " Wakeup reason 200:qcom,smd-rpm:222:fc4: 11m 49s 332ms (0 times) realtime",
+ " Wakeup reason 200:qcom,smd-rpm: 48s 45ms (0 times) realtime",
+ " Wakeup reason 2:qcom,smd-rpm:2:f0.qm,mm:22:fc4mi: 3s 417ms (0 times) realtime",
+ " Wakeup reason 188:qcom,smd-adsp:200:qcom,smd-rpm: 1s 656ms (0 times) realtime",
+ " Wakeup reason 58:qcom,smsm-modem:2:qcom,smd-rpm: 6m 16s 1ms (5 times) realtime",
+ " Wakeup reason 57:qcom,smd-modem:200:qcom,smd-rpm: 40s 995ms (0 times) realtime",
+ " Wakeup reason unknown: 8s 455ms (0 times) realtime",
+ " Wakeup reason 9:bcmsdh_sdmmc:2:qcomd-rpm:240:mso: 8m 5s 9ms (0 times) realtime",
+ " ",
+ " 0:",
+ " User activity: 2 other",
+ " Wake lock SCREEN_FROZEN realtime",
+ " Sensor 0: 9s 908ms realtime (1 times)",
+ " Sensor 1: 9s 997ms realtime (1 times)",
+ " Foreground for: 2h 21m 5s 622ms",
+ " Apk android:",
+ " 24 wakeup alarms",
+ " u0a9:",
+ " Mobile network: 8.1KB received, 1.6KB sent (packets 291 received, 342 sent)",
+ " Mobile radio active: 3m 43s 890ms (34.2%) 39x @ 354 mspp",
+ " Sensor 2: 12m 13s 15ms realtime (5 times)",
+ " Sensor 32: (not used)",
+ " Sensor 35: (not used)");
+
+ BatteryStatsDetailedInfoItem stats = new BatteryStatsDetailedInfoParser().parse(inputBlock);
+
+ assertEquals(8465622, stats.getTimeOnBattery());
+ assertEquals(910619, stats.getScreenOnTime());
+ assertNotNull(stats.getWakelockItem());
+ assertNotNull(stats.getInterruptItem());
+ assertNotNull(stats.getProcessUsageItem());
+ }
+
+ /**
+ * Test with missing wakelock section
+ */
+ public void testMissingWakelockSection() {
+ List<String> inputBlock = Arrays.asList(
+ " Time on battery: 2h 21m 5s 622ms (12.0%) realtime, 7m 54s 146ms (0.7%) uptime",
+ " Time on battery screen off: 2h 5m 55s 3ms (1%) realtime, 7m 4s 5ms (7%) uptime",
+ " Total run time: 19h 38m 43s 650ms realtime, 17h 25m 32s 175ms uptime",
+ " All wakeup reasons:",
+ " Wakeup reason 200:qcom,smd-rpm:222:fc4: 11m 49s 332ms (0 times) realtime",
+ " Wakeup reason 200:qcom,smd-rpm: 48s 45ms (0 times) realtime",
+ " Wakeup reason 2:qcom,smd-rpm:2:f0.qm,mm:22:fc4mi: 3s 417ms (0 times) realtime",
+ " Wakeup reason 188:qcom,smd-adsp:200:qcom,smd-rpm: 1s 656ms (0 times) realtime",
+ " Wakeup reason 58:qcom,smsm-modem:2:qcom,smd-rpm: 6m 16s 1ms (5 times) realtime",
+ " Wakeup reason 57:qcom,smd-modem:200:qcom,smd-rpm: 40s 995ms (0 times) realtime",
+ " Wakeup reason unknown: 8s 455ms (0 times) realtime",
+ " Wakeup reason 9:bcmsdh_sdmmc:2:qcomd-rpm:240:mso: 8m 5s 9ms (0 times) realtime",
+ " ",
+ " 0:",
+ " User activity: 2 other",
+ " Wake lock SCREEN_FROZEN realtime",
+ " Sensor 0: 9s 908ms realtime (1 times)",
+ " Sensor 1: 9s 997ms realtime (1 times)",
+ " Foreground for: 2h 21m 5s 622ms",
+ " Apk android:",
+ " 24 wakeup alarms",
+ " u0a9:",
+ " Mobile network: 8.1KB received, 1.6KB sent (packets 291 received, 342 sent)",
+ " Mobile radio active: 3m 43s 890ms (34.2%) 39x @ 354 mspp",
+ " Sensor 2: 12m 13s 15ms realtime (5 times)",
+ " Sensor 32: (not used)",
+ " Sensor 35: (not used)");
+ BatteryStatsDetailedInfoItem stats = new BatteryStatsDetailedInfoParser().parse(inputBlock);
+
+ assertEquals(8465622, stats.getTimeOnBattery());
+ assertEquals(910619, stats.getScreenOnTime());
+
+ assertNull(stats.getWakelockItem());
+
+ assertNotNull(stats.getInterruptItem());
+ assertNotNull(stats.getProcessUsageItem());
+ }
+}
+
diff --git a/javatests/com/android/loganalysis/parser/BatteryStatsSummaryInfoParserTest.java b/javatests/com/android/loganalysis/parser/BatteryStatsSummaryInfoParserTest.java
new file mode 100644
index 0000000..c6ba381
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/BatteryStatsSummaryInfoParserTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2015 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.loganalysis.parser;
+
+import com.android.loganalysis.item.BatteryStatsSummaryInfoItem;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.TimeZone;
+
+/**
+ * Unit tests for {@link BatteryStatsSummaryInfoParser}
+ */
+public class BatteryStatsSummaryInfoParserTest extends TestCase {
+
+ /**
+ * Test that normal input is parsed.
+ */
+ public void testBatteryStatsSummaryInfoParser() {
+ List<String> inputBlock = Arrays.asList(
+ "Battery History (37% used, 95KB used of 256KB, 166 strings using 15KB):",
+ " 0 (9) RESET:TIME: 2014-12-09-11-33-29",
+ " +1s067ms (1) 100 c0500020 -wifi_full_lock -wifi_scan",
+ " +3s297ms (2) 100 80400020 -wake_lock -screen",
+ " +30m02s075ms (1) 100 c0500020 wifi_signal_strength=4 wifi_suppl=completed",
+ " +30m03s012ms (2) 099 c0500020 temp=306 volt=4217",
+ " +33m48s967ms (1) 099 f8400020 +wifi_scan",
+ " +33m49s335ms (2) 098 f0400020 temp=324 -wifi_scan",
+ " +1h07m27s735ms (1) 098 80400020 -wake_lock",
+ " +1h07m27s950ms (2) 097 c0400020",
+ " +1h07m29s000ms (2) 097 c0400020 -sync=u0a41:\"gmail-ls/com.google/a@g",
+ " +1h25m34s877ms (2) 097 00400020 -running wake_reason=0:200:qcom,smd-rpm",
+ " +1h25m41s948ms (2) 096 80400020 wifi_suppl=associated",
+ " +2h13m40s055ms (1) 096 00400018 -running",
+ " +2h13m40s570ms (2) 095 c0400008 temp=304 volt=4167",
+ " +2h56m50s792ms (1) 095 80400020 -wake_lock",
+ " +2h56m50s967ms (2) 094 00400020 temp=317 -running",
+ " +3h38m57s986ms (2) 094 80400020 +running wake_reason=0:289:bcmsdh_sdmmc",
+ " +3h38m58s241ms (2) 093 00400020 temp=327 -running",
+ " +3h56m33s329ms (1) 093 00400020 -running -wake_lock",
+ " +3h56m43s245ms (2) 092 00400020 -running",
+ " +4h13m00s551ms (1) 092 00400020 -running -wake_lock",
+ " +4h13m24s250ms (2) 091 00400020 -running",
+ " +4h34m52s233ms (2) 091 80400020 +running wake_reason=0:289:bcmsdh_sdmmc",
+ " +4h34m52s485ms (3) 090 00400020 -running wake_reason=0:200:qcom,smd-rpm",
+ " +4h57m20s644ms (1) 090 00400020 -running",
+ " +4h57m38s484ms (2) 089 00400020 -running",
+ " +5h20m58s226ms (1) 089 80400020 +running wifi_suppl=associated",
+ " +5h21m03s909ms (1) 088 80400020 -wake_lock -wifi_full_lock",
+ " +5h40m38s169ms (2) 088 c0500020 +top=u0a19:com.google.android.googlequick",
+ " +5h40m38s479ms (2) 087 c0500020 volt=4036",
+ " +6h16m45s248ms (2) 087 d0440020 -sync=u0a41:gmail-ls/com.google/avellore@go",
+ " +6h16m45s589ms (2) 086 d0440020 volt=4096",
+ " +6h52m43s316ms (1) 086 80400020 -wake_lock",
+ " +6h53m18s952ms (2) 085 c0400020",
+ " +7h24m02s415ms (1) 085 80400020 -wake_lock",
+ " +7h24m02s904ms (3) 084 c0400020 volt=4105 +wake_lock=u0a7:NlpWakeLock",
+ " +7h29m10s379ms (1) 084 00400020 -running -wake_lock",
+ " +7h29m11s841ms (2) 083 00400020 temp=317 volt=4047 -running",
+ " +7h41m08s963ms (1) 083 00400020 -running",
+ " +7h41m20s494ms (2) 082 00400020 temp=300 -running",
+ " +7h54m57s288ms (1) 082 52441420 -running",
+ " +7h55m00s801ms (1) 081 52441420 -running",
+ " +8h02m18s594ms (1) 081 50440020 -running",
+ " +8h02m23s493ms (2) 080 50440020 temp=313 -running");
+
+ BatteryStatsSummaryInfoItem summary = new BatteryStatsSummaryInfoParser().parse(inputBlock);
+
+ assertEquals("The battery dropped a level 24 mins on average",
+ summary.getBatteryDischargeRate());
+
+ // Get the current timezone short name (PST, GMT) to properly output the time as expected.
+ String timezone =
+ new GregorianCalendar().getTimeZone().getDisplayName(false, TimeZone.SHORT);
+ assertEquals(
+ String.format(
+ "The peak discharge time was during Tue Dec 09 16:31:07 %s 2014 to "
+ + "Tue Dec 09 19:35:52 %s 2014 where battery dropped from 89 to 80",
+ timezone, timezone),
+ summary.getPeakDischargeTime());
+ }
+
+ public void testNoBatteryDischarge() {
+ List<String> inputBlock = Arrays.asList(
+ "Battery History (37% used, 95KB used of 256KB, 166 strings using 15KB):",
+ " 0 (9) RESET:TIME: 2014-12-09-11-33-29");
+ BatteryStatsSummaryInfoItem summary = new BatteryStatsSummaryInfoParser().parse(inputBlock);
+
+ assertEquals("The battery did not discharge", summary.getBatteryDischargeRate());
+ assertEquals("The battery did not discharge", summary.getPeakDischargeTime());
+ }
+}
+
diff --git a/javatests/com/android/loganalysis/parser/BatteryUsageParserTest.java b/javatests/com/android/loganalysis/parser/BatteryUsageParserTest.java
new file mode 100644
index 0000000..20a9a2e
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/BatteryUsageParserTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2015 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.loganalysis.parser;
+
+import com.android.loganalysis.item.BatteryUsageItem;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link BatteryUsageParser}
+ */
+public class BatteryUsageParserTest extends TestCase {
+
+ private static final double EPSILON = 1e-3;
+ /**
+ * Test that normal input is parsed.
+ */
+ public void testBatteryUsageParser() {
+ List<String> inputBlock = Arrays.asList(
+ " Capacity: 3220, Computed drain: 11.0, actual drain: 0",
+ " Screen: 8.93",
+ " Idle: 1.23",
+ " Uid 0: 0.281",
+ " Uid u0a36: 0.200",
+ " Uid 1000: 0.165",
+ " Uid 1013: 0.0911",
+ " Uid u0a16: 0.0441");
+
+ BatteryUsageItem usage = new BatteryUsageParser().parse(inputBlock);
+
+ assertEquals(3220, usage.getBatteryCapacity());
+ assertEquals(7, usage.getBatteryUsage().size());
+
+ assertEquals("Screen", usage.getBatteryUsage().get(0).getName());
+ assertEquals(8.93, usage.getBatteryUsage().get(0).getUsage(), EPSILON);
+ assertEquals("Uid u0a16", usage.getBatteryUsage().get(6).getName());
+ assertEquals(0.0441, usage.getBatteryUsage().get(6).getUsage());
+ }
+}
+
diff --git a/javatests/com/android/loganalysis/parser/BugreportParserTest.java b/javatests/com/android/loganalysis/parser/BugreportParserTest.java
new file mode 100644
index 0000000..5f3fc10
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/BugreportParserTest.java
@@ -0,0 +1,766 @@
+/*
+ * 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.loganalysis.parser;
+
+import com.android.loganalysis.item.BugreportItem;
+import com.android.loganalysis.item.IItem;
+import com.android.loganalysis.item.MiscKernelLogItem;
+import com.android.loganalysis.util.ArrayUtil;
+
+import junit.framework.TestCase;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Unit tests for {@link BugreportParser}
+ */
+public class BugreportParserTest extends TestCase {
+
+ /**
+ * Test that a bugreport can be parsed.
+ */
+ public void testParse() throws ParseException {
+ List<String> lines =
+ Arrays.asList(
+ "========================================================",
+ "== dumpstate: 2012-04-25 20:45:10",
+ "========================================================",
+ "------ SECTION ------",
+ "",
+ "------ MEMORY INFO (/proc/meminfo) ------",
+ "MemTotal: 353332 kB",
+ "MemFree: 65420 kB",
+ "Buffers: 20800 kB",
+ "Cached: 86204 kB",
+ "SwapCached: 0 kB",
+ "",
+ "------ CPU INFO (top -n 1 -d 1 -m 30 -t) ------",
+ "",
+ "User 3%, System 3%, IOW 0%, IRQ 0%",
+ "User 33 + Nice 0 + Sys 32 + Idle 929 + IOW 0 + IRQ 0 + SIRQ 0 = 994",
+ "",
+ "------ PROCRANK (procrank) ------",
+ " 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",
+ " ------ ------ ------",
+ " 203624K 163604K TOTAL",
+ "RAM: 731448K total, 415804K free, 9016K buffers, 108548K cached",
+ "[procrank: 1.6s elapsed]",
+ "",
+ "------ KERNEL LOG (dmesg) ------",
+ "<6>[ 0.000000] Initializing cgroup subsys cpu",
+ "<3>[ 1.000000] benign message",
+ "",
+ "",
+ "------ SYSTEM LOG (logcat -v threadtime -d *:v) ------",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: java.lang.Exception",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)",
+ "04-25 17:17:08.445 312 366 E ActivityManager: ANR (application not responding) in process: com.android.package",
+ "04-25 17:17:08.445 312 366 E ActivityManager: Reason: keyDispatchingTimedOut",
+ "04-25 17:17:08.445 312 366 E ActivityManager: Load: 0.71 / 0.83 / 0.51",
+ "04-25 17:17:08.445 312 366 E ActivityManager: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait",
+ "04-25 18:33:27.273 115 115 I DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***",
+ "04-25 18:33:27.273 115 115 I DEBUG : Build fingerprint: 'product:build:target'",
+ "04-25 18:33:27.273 115 115 I DEBUG : pid: 3112, tid: 3112 >>> com.google.android.browser <<<",
+ "04-25 18:33:27.273 115 115 I DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000",
+ "",
+ "------ SYSTEM PROPERTIES ------",
+ "[dalvik.vm.dexopt-flags]: [m=y]",
+ "[dalvik.vm.heapgrowthlimit]: [48m]",
+ "[dalvik.vm.heapsize]: [256m]",
+ "[gsm.version.ril-impl]: [android moto-ril-multimode 1.0]",
+ "",
+ "------ LAST KMSG (/proc/last_kmsg) ------",
+ "[ 0.000000] Initializing cgroup subsys cpu",
+ "[ 16.203491] benign message",
+ "",
+ "------ SECTION ------",
+ "",
+ "------ VM TRACES AT LAST ANR (/data/anr/traces.txt: 2012-04-25 17:17:08) ------",
+ "",
+ "",
+ "----- pid 2887 at 2012-04-25 17:17:08 -----",
+ "Cmd line: com.android.package",
+ "",
+ "DALVIK THREADS:",
+ "(mutexes: tll=0 tsl=0 tscl=0 ghl=0)",
+ "",
+ "\"main\" prio=5 tid=1 SUSPENDED",
+ " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001",
+ " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001",
+ " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0",
+ " at class.method1(Class.java:1)",
+ " at class.method2(Class.java:2)",
+ " at class.method2(Class.java:2)",
+ "",
+ "----- end 2887 -----",
+ "",
+ "------ SECTION ------",
+ "",
+ "------ DUMPSYS (dumpsys) ------",
+ "DUMP OF SERVICE batterystats:",
+ "Battery History (0% used, 1636 used of 256KB, 15 strings using 794):",
+ " 0 (15) RESET:TIME: 1970-01-10-06-23-28",
+ " +45s702ms (2) 001 80080000 volt=4187",
+ " +1m15s525ms (2) 001 80080000 temp=299 volt=4155",
+ "Statistics since last charged:",
+ " Time on battery: 1h 5m 2s 4ms (9%) realtime, 1h 5m 2s 4ms (9%) uptime",
+ " Time on battery screen off: 1h 4m 5s 8ms (9%) realtime, 1h 4m 5s 8ms (9%) uptime",
+ " All kernel wake locks:",
+ " Kernel Wake lock PowerManagerService.WakeLocks: 5m 10s 6ms (2 times) realtime",
+ " Kernel Wake lock msm_serial_hs_rx: 2m 13s 612ms (258 times) realtime",
+ "",
+ " All partial wake locks:",
+ " Wake lock 1001 ProxyController: 1h 4m 47s 565ms (4 times) realtime",
+ " Wake lock 1013 AudioMix: 1s 979ms (3 times) realtime",
+ "",
+ " All wakeup reasons:",
+ " Wakeup reason 2:bcmsdh_sdmmc:2:qcom,smd:2:msmgio: 1m 5s 4ms (2 times) realtime",
+ " Wakeup reason 2:qcom,smd-rpm:2:fc4c.qcom,spmi: 7m 1s 914ms (7 times) realtime",
+ "",
+ "DUMP OF SERVICE package:",
+ "Package [com.google.android.calculator] (e075c9d):",
+ " userId=10071",
+ " secondaryCpuAbi=null",
+ " versionCode=73000302 minSdk=10000 targetSdk=10000",
+ " versionName=7.3 (3821978)",
+ " splits=[base]",
+ "========================================================",
+ "== Running Application Services",
+ "========================================================",
+ "------ APP SERVICES (dumpsys activity service all) ------",
+ "SERVICE com.google.android.gms/"
+ + "com.google.android.location.internal.GoogleLocationManagerService f4c9d pid=14",
+ " Location Request History By Package:",
+ "Interval effective/min/max 1/0/0[s] Duration: 140[minutes] ["
+ + "com.google.android.gms, PRIORITY_NO_POWER, UserLocationProducer] "
+ + "Num requests: 2 Active: true",
+ "Interval effective/min/max 284/285/3600[s] Duration: 140[minutes] "
+ + "[com.google.android.googlequicksearchbox, PRIORITY_BALANCED_POWER_ACCURACY] "
+ + "Num requests: 5 Active: true");
+
+ BugreportItem bugreport = new BugreportParser().parse(lines);
+ assertNotNull(bugreport);
+ assertEquals(parseTime("2012-04-25 20:45:10.000"), bugreport.getTime());
+
+ assertNotNull(bugreport.getMemInfo());
+ assertEquals(5, bugreport.getMemInfo().size());
+
+ assertNotNull(bugreport.getTop());
+ assertEquals(994, bugreport.getTop().getTotal());
+
+ assertNotNull(bugreport.getProcrank());
+ assertEquals(3, bugreport.getProcrank().getPids().size());
+
+ assertNotNull(bugreport.getKernelLog());
+ assertEquals(1.0, bugreport.getKernelLog().getStopTime(), 0.000005);
+
+ assertNotNull(bugreport.getSystemLog());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"), bugreport.getSystemLog().getStartTime());
+ assertEquals(parseTime("2012-04-25 18:33:27.273"), bugreport.getSystemLog().getStopTime());
+ assertEquals(3, bugreport.getSystemLog().getEvents().size());
+ assertEquals(1, bugreport.getSystemLog().getAnrs().size());
+ assertNotNull(bugreport.getSystemLog().getAnrs().get(0).getTrace());
+
+ assertNotNull(bugreport.getLastKmsg());
+ assertEquals(16.203491, bugreport.getLastKmsg().getStopTime(), 0.000005);
+
+ assertNotNull(bugreport.getSystemProps());
+ assertEquals(4, bugreport.getSystemProps().size());
+
+ assertNotNull(bugreport.getDumpsys());
+ assertNotNull(bugreport.getDumpsys().getBatteryStats());
+ assertNotNull(bugreport.getDumpsys().getPackageStats());
+
+ assertNotNull(bugreport.getActivityService());
+ assertNotNull(bugreport.getActivityService().getLocationDumps().getLocationClients());
+ assertEquals(bugreport.getActivityService().getLocationDumps().getLocationClients().size(),
+ 2);
+ }
+
+ /**
+ * Test that the logcat year is set correctly from the bugreport timestamp.
+ */
+ public void testParse_set_logcat_year() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "========================================================",
+ "== dumpstate: 1999-01-01 02:03:04",
+ "========================================================",
+ "------ SYSTEM LOG (logcat -v threadtime -d *:v) ------",
+ "01-01 01:02:03.000 1 1 I TAG : message",
+ "01-01 01:02:04.000 1 1 I TAG : message",
+ "");
+
+ BugreportItem bugreport = new BugreportParser().parse(lines);
+ assertNotNull(bugreport);
+ assertEquals(parseTime("1999-01-01 02:03:04.000"), bugreport.getTime());
+ assertNotNull(bugreport.getSystemLog());
+ assertEquals(parseTime("1999-01-01 01:02:03.000"), bugreport.getSystemLog().getStartTime());
+ assertEquals(parseTime("1999-01-01 01:02:04.000"), bugreport.getSystemLog().getStopTime());
+ }
+
+ /**
+ * Test that the command line is parsed
+ */
+ public void testParse_command_line() {
+ List<String> lines = Arrays.asList("Command line:");
+ BugreportItem bugreport = new BugreportParser().parse(lines);
+ assertTrue(bugreport.getCommandLine().isEmpty());
+
+ lines = Arrays.asList("Command line: key=value");
+ bugreport = new BugreportParser().parse(lines);
+ assertEquals(1, bugreport.getCommandLine().size());
+ assertEquals("value", bugreport.getCommandLine().get("key"));
+
+ lines = Arrays.asList("Command line: key1=value1 key2=value2");
+ bugreport = new BugreportParser().parse(lines);
+ assertEquals(2, bugreport.getCommandLine().size());
+ assertEquals("value1", bugreport.getCommandLine().get("key1"));
+ assertEquals("value2", bugreport.getCommandLine().get("key2"));
+
+ // Test a bad strings
+ lines = Arrays.asList("Command line: key1=value=withequals key2= ");
+ bugreport = new BugreportParser().parse(lines);
+ assertEquals(2, bugreport.getCommandLine().size());
+ assertEquals("value=withequals", bugreport.getCommandLine().get("key1"));
+ assertEquals("", bugreport.getCommandLine().get("key2"));
+
+ lines = Arrays.asList("Command line: key1=value1 nonkey key2=");
+ bugreport = new BugreportParser().parse(lines);
+ assertEquals(3, bugreport.getCommandLine().size());
+ assertEquals("value1", bugreport.getCommandLine().get("key1"));
+ assertEquals("", bugreport.getCommandLine().get("key2"));
+ assertNull(bugreport.getCommandLine().get("nonkey"));
+ }
+
+ /**
+ * Test that a normal boot triggers a normal boot event and no unknown reason.
+ */
+ public void testParse_bootreason_kernel_good() {
+ List<String> lines = Arrays.asList(
+ "========================================================",
+ "== dumpstate: 1999-01-01 02:03:04",
+ "========================================================",
+ "Command line: androidboot.bootreason=reboot",
+ "");
+ BugreportItem bugreport = new BugreportParser().parse(lines);
+ assertNotNull(bugreport.getLastKmsg());
+ assertEquals(1, bugreport.getLastKmsg().getEvents().size());
+ assertEquals("Last boot reason: reboot",
+ bugreport.getLastKmsg().getEvents().get(0).getStack());
+ assertEquals("NORMAL_REBOOT", bugreport.getLastKmsg().getEvents().get(0).getCategory());
+ }
+
+ /**
+ * Test that a kernel reset boot triggers a kernel reset event and no unknown reason.
+ */
+ public void testParse_bootreason_kernel_bad() {
+ List<String> lines = Arrays.asList(
+ "========================================================",
+ "== dumpstate: 1999-01-01 02:03:04",
+ "========================================================",
+ "Command line: androidboot.bootreason=hw_reset",
+ "");
+ BugreportItem bugreport = new BugreportParser().parse(lines);
+ assertNotNull(bugreport.getLastKmsg());
+ assertEquals(1, bugreport.getLastKmsg().getEvents().size());
+ assertEquals("Last boot reason: hw_reset",
+ bugreport.getLastKmsg().getEvents().get(0).getStack());
+ assertEquals("KERNEL_RESET", bugreport.getLastKmsg().getEvents().get(0).getCategory());
+ }
+
+ /**
+ * Test that a normal boot triggers a normal boot event and no unknown reason.
+ */
+ public void testParse_bootreason_prop_good() {
+ List<String> lines = Arrays.asList(
+ "========================================================",
+ "== dumpstate: 1999-01-01 02:03:04",
+ "========================================================",
+ "------ SYSTEM PROPERTIES ------",
+ "[ro.boot.bootreason]: [reboot]",
+ "");
+ BugreportItem bugreport = new BugreportParser().parse(lines);
+ assertNotNull(bugreport.getLastKmsg());
+ assertEquals(1, bugreport.getLastKmsg().getEvents().size());
+ assertEquals("Last boot reason: reboot",
+ bugreport.getLastKmsg().getEvents().get(0).getStack());
+ assertEquals("NORMAL_REBOOT", bugreport.getLastKmsg().getEvents().get(0).getCategory());
+ }
+
+ /**
+ * Test that a kernel reset boot triggers a kernel reset event and no unknown reason.
+ */
+ public void testParse_bootreason_prop_bad() {
+ List<String> lines = Arrays.asList(
+ "========================================================",
+ "== dumpstate: 1999-01-01 02:03:04",
+ "========================================================",
+ "------ SYSTEM PROPERTIES ------",
+ "[ro.boot.bootreason]: [hw_reset]",
+ "");
+ BugreportItem bugreport = new BugreportParser().parse(lines);
+ assertNotNull(bugreport.getLastKmsg());
+ assertEquals(1, bugreport.getLastKmsg().getEvents().size());
+ assertEquals("Last boot reason: hw_reset",
+ bugreport.getLastKmsg().getEvents().get(0).getStack());
+ assertEquals("KERNEL_RESET", bugreport.getLastKmsg().getEvents().get(0).getCategory());
+ assertEquals("", bugreport.getLastKmsg().getEvents().get(0).getPreamble());
+ assertEquals(0.0, bugreport.getLastKmsg().getEvents().get(0).getEventTime());
+ assertEquals(0.0, bugreport.getLastKmsg().getStartTime());
+ assertEquals(0.0, bugreport.getLastKmsg().getStopTime());
+ }
+
+ /**
+ * Test that a kernel panic in the last kmsg and a kernel panic only triggers one kernel reset.
+ */
+ public void testParse_bootreason_duplicate() {
+ List<String> lines = Arrays.asList(
+ "========================================================",
+ "== dumpstate: 1999-01-01 02:03:04",
+ "========================================================",
+ "Command line: androidboot.bootreason=hw_reset",
+ "------ SYSTEM PROPERTIES ------",
+ "[ro.boot.bootreason]: [hw_reset]",
+ "",
+ "------ LAST KMSG (/proc/last_kmsg) ------",
+ "[ 0.000000] Initializing cgroup subsys cpu",
+ "[ 16.203491] Kernel panic",
+ "");
+ BugreportItem bugreport = new BugreportParser().parse(lines);
+ assertNotNull(bugreport.getLastKmsg());
+ assertEquals(1, bugreport.getLastKmsg().getEvents().size());
+ assertEquals("Kernel panic", bugreport.getLastKmsg().getEvents().get(0).getStack());
+ assertEquals("KERNEL_RESET", bugreport.getLastKmsg().getEvents().get(0).getCategory());
+ }
+
+ /**
+ * Test that the trace is set correctly if there is only one ANR.
+ */
+ public void testSetAnrTrace_single() {
+ List<String> lines = Arrays.asList(
+ "========================================================",
+ "== dumpstate: 2012-04-25 20:45:10",
+ "========================================================",
+ "------ SYSTEM LOG (logcat -v threadtime -d *:v) ------",
+ "04-25 17:17:08.445 312 366 E ActivityManager: ANR (application not responding) in process: com.android.package",
+ "04-25 17:17:08.445 312 366 E ActivityManager: Reason: keyDispatchingTimedOut",
+ "04-25 17:17:08.445 312 366 E ActivityManager: Load: 0.71 / 0.83 / 0.51",
+ "04-25 17:17:08.445 312 366 E ActivityManager: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait",
+ "",
+ "------ VM TRACES AT LAST ANR (/data/anr/traces.txt: 2012-04-25 17:17:08) ------",
+ "",
+ "----- pid 2887 at 2012-04-25 17:17:08 -----",
+ "Cmd line: com.android.package",
+ "",
+ "DALVIK THREADS:",
+ "(mutexes: tll=0 tsl=0 tscl=0 ghl=0)",
+ "",
+ "\"main\" prio=5 tid=1 SUSPENDED",
+ " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001",
+ " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001",
+ " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0",
+ " at class.method1(Class.java:1)",
+ " at class.method2(Class.java:2)",
+ " at class.method2(Class.java:2)",
+ "",
+ "----- end 2887 -----",
+ "");
+
+ List<String> expectedStack = Arrays.asList(
+ "\"main\" prio=5 tid=1 SUSPENDED",
+ " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001",
+ " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001",
+ " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0",
+ " at class.method1(Class.java:1)",
+ " at class.method2(Class.java:2)",
+ " at class.method2(Class.java:2)");
+
+ BugreportItem bugreport = new BugreportParser().parse(lines);
+
+ assertNotNull(bugreport.getSystemLog());
+ assertEquals(1, bugreport.getSystemLog().getAnrs().size());
+ assertEquals(ArrayUtil.join("\n", expectedStack),
+ bugreport.getSystemLog().getAnrs().get(0).getTrace());
+ }
+
+ /**
+ * Test that the trace is set correctly if there are multiple ANRs.
+ */
+ public void testSetAnrTrace_multiple() {
+ List<String> lines = Arrays.asList(
+ "========================================================",
+ "== dumpstate: 2012-04-25 20:45:10",
+ "========================================================",
+ "------ SYSTEM LOG (logcat -v threadtime -d *:v) ------",
+ "04-25 17:17:08.445 312 366 E ActivityManager: ANR (application not responding) in process: com.android.package",
+ "04-25 17:17:08.445 312 366 E ActivityManager: Reason: keyDispatchingTimedOut",
+ "04-25 17:17:08.445 312 366 E ActivityManager: Load: 0.71 / 0.83 / 0.51",
+ "04-25 17:17:08.445 312 366 E ActivityManager: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait",
+ "04-25 17:18:08.445 312 366 E ActivityManager: ANR (application not responding) in process: com.android.package",
+ "04-25 17:18:08.445 312 366 E ActivityManager: Reason: keyDispatchingTimedOut",
+ "04-25 17:18:08.445 312 366 E ActivityManager: Load: 0.71 / 0.83 / 0.51",
+ "04-25 17:18:08.445 312 366 E ActivityManager: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait",
+ "04-25 17:19:08.445 312 366 E ActivityManager: ANR (application not responding) in process: com.android.different.pacakge",
+ "04-25 17:19:08.445 312 366 E ActivityManager: Reason: keyDispatchingTimedOut",
+ "04-25 17:19:08.445 312 366 E ActivityManager: Load: 0.71 / 0.83 / 0.51",
+ "04-25 17:19:08.445 312 366 E ActivityManager: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait",
+ "",
+ "------ VM TRACES AT LAST ANR (/data/anr/traces.txt: 2012-04-25 17:18:08) ------",
+ "",
+ "----- pid 2887 at 2012-04-25 17:17:08 -----",
+ "Cmd line: com.android.package",
+ "",
+ "DALVIK THREADS:",
+ "(mutexes: tll=0 tsl=0 tscl=0 ghl=0)",
+ "",
+ "\"main\" prio=5 tid=1 SUSPENDED",
+ " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001",
+ " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001",
+ " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0",
+ " at class.method1(Class.java:1)",
+ " at class.method2(Class.java:2)",
+ " at class.method2(Class.java:2)",
+ "",
+ "----- end 2887 -----",
+ "");
+
+ List<String> expectedStack = Arrays.asList(
+ "\"main\" prio=5 tid=1 SUSPENDED",
+ " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001",
+ " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001",
+ " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0",
+ " at class.method1(Class.java:1)",
+ " at class.method2(Class.java:2)",
+ " at class.method2(Class.java:2)");
+
+ BugreportItem bugreport = new BugreportParser().parse(lines);
+
+ assertNotNull(bugreport.getSystemLog());
+ assertEquals(3, bugreport.getSystemLog().getAnrs().size());
+ assertNull(bugreport.getSystemLog().getAnrs().get(0).getTrace());
+ assertEquals(ArrayUtil.join("\n", expectedStack),
+ bugreport.getSystemLog().getAnrs().get(1).getTrace());
+ assertNull(bugreport.getSystemLog().getAnrs().get(2).getTrace());
+ }
+
+ /**
+ * Test that the trace is set correctly if there is not traces file.
+ */
+ public void testSetAnrTrace_no_traces() {
+ List<String> lines = Arrays.asList(
+ "========================================================",
+ "== dumpstate: 2012-04-25 20:45:10",
+ "========================================================",
+ "------ SYSTEM LOG (logcat -v threadtime -d *:v) ------",
+ "04-25 17:17:08.445 312 366 E ActivityManager: ANR (application not responding) in process: com.android.package",
+ "04-25 17:17:08.445 312 366 E ActivityManager: Reason: keyDispatchingTimedOut",
+ "04-25 17:17:08.445 312 366 E ActivityManager: Load: 0.71 / 0.83 / 0.51",
+ "04-25 17:17:08.445 312 366 E ActivityManager: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait",
+ "",
+ "*** NO ANR VM TRACES FILE (/data/anr/traces.txt): No such file or directory",
+ "");
+
+ BugreportItem bugreport = new BugreportParser().parse(lines);
+
+ assertNotNull(bugreport.getSystemLog());
+ assertEquals(1, bugreport.getSystemLog().getAnrs().size());
+ assertNull(bugreport.getSystemLog().getAnrs().get(0).getTrace());
+ }
+
+ /**
+ * Add a test that ensures that the "new" style of stack dumping works. Traces aren't written to
+ * a global trace file. Instead, each ANR event is written to a separate trace file (note the
+ * "/data/anr/anr_4376042170248254515" instead of "/data/anr/traces.txt").
+ */
+ public void testAnrTraces_not_global_traceFile() {
+ List<String> lines =
+ Arrays.asList(
+ "========================================================",
+ "== dumpstate: 2017-06-12 16:46:29",
+ "========================================================",
+ "------ SYSTEM LOG (logcat -v threadtime -v printable -v uid -d *:v) ------",
+ "--------- beginning of main ",
+ "02-18 04:26:31.829 logd 468 468 W auditd : type=2000 audit(0.0:1): initialized",
+ "02-18 04:26:33.783 logd 468 468 I auditd : type=1403 audit(0.0:2): policy loaded auid=4294967295 ses=4294967295",
+ "02-18 04:26:33.783 logd 468 468 W auditd : type=1404 audit(0.0:3): enforcing=1 old_enforcing=0 auid=4294967295 ses=4294967295",
+ "06-12 16:45:47.426 1000 1050 1124 E ActivityManager: ANR in com.example.android.helloactivity (com.example.android.helloactivity/.HelloActivity)",
+ "06-12 16:45:47.426 1000 1050 1124 E ActivityManager: PID: 7176",
+ "06-12 16:45:47.426 1000 1050 1124 E ActivityManager: Reason: Input dispatching timed out (Waiting because no window has focus but there is a focused application that may eventually add a window when it finishes starting up.)",
+ "06-12 16:45:47.426 1000 1050 1124 E ActivityManager: Load: 6.85 / 7.07 / 5.31",
+ "06-12 16:45:47.426 1000 1050 1124 E ActivityManager: CPU usage from 235647ms to 0ms ago (2017-06-12 16:41:49.415 to 2017-06-12 16:45:45.062):",
+ "06-12 16:45:47.426 1000 1050 1124 E ActivityManager: 7.7% 1848/com.ustwo.lwp: 4% user + 3.7% kernel / faults: 157 minor",
+ "06-12 16:45:47.426 1000 1050 1124 E ActivityManager: 7.7% 2536/com.google.android.googlequicksearchbox:search: 5.6% user + 2.1% kernel / faults: 195 minor",
+ "06-12 16:45:47.426 1000 1050 1124 E ActivityManager: 7.2% 1050/system_server: 4.5% user + 2.6% kernel / faults: 27117 minor ",
+ "06-12 16:45:47.426 1000 1050 1124 E ActivityManager: 5.3% 489/surfaceflinger: 2.9% user + 2.3% kernel / faults: 15 minor ",
+ "",
+ "------ VM TRACES AT LAST ANR (/data/anr/anr_4376042170248254515: 2017-06-12 16:45:47) ------",
+ "",
+ "----- pid 7176 at 2017-06-12 16:45:45 -----",
+ "Cmd line: com.example.android.helloactivity",
+ "",
+ "DALVIK THREADS:",
+ "(mutexes: tll=0 tsl=0 tscl=0 ghl=0)",
+ "",
+ "\"main\" daemon prio=5 tid=5 Waiting",
+ " | group=\"system\" sCount=1 dsCount=0 flags=1 obj=0x140805e8 self=0x7caf4bf400",
+ " | sysTid=7184 nice=4 cgrp=default sched=0/0 handle=0x7c9b4e44f0",
+ " | state=S schedstat=( 507031 579062 19 ) utm=0 stm=0 core=3 HZ=100",
+ " | stack=0x7c9b3e2000-0x7c9b3e4000 stackSize=1037KB",
+ " | held mutexes=",
+ " at java.lang.Object.wait(Native method)",
+ " - waiting on <0x0281f7b7> (a java.lang.Class<java.lang.ref.ReferenceQueue>)",
+ " at java.lang.Daemons$ReferenceQueueDaemon.runInternal(Daemons.java:178)",
+ " - locked <0x0281f7b7> (a java.lang.Class<java.lang.ref.ReferenceQueue>)",
+ " at java.lang.Daemons$Daemon.run(Daemons.java:103)",
+ " at java.lang.Thread.run(Thread.java:764)",
+ "",
+ "----- end 7176 -----");
+
+ // NOTE: The parser only extracts the main thread from the traces.
+ List<String> expectedStack =
+ Arrays.asList(
+ "\"main\" daemon prio=5 tid=5 Waiting",
+ " | group=\"system\" sCount=1 dsCount=0 flags=1 obj=0x140805e8 self=0x7caf4bf400",
+ " | sysTid=7184 nice=4 cgrp=default sched=0/0 handle=0x7c9b4e44f0",
+ " | state=S schedstat=( 507031 579062 19 ) utm=0 stm=0 core=3 HZ=100",
+ " | stack=0x7c9b3e2000-0x7c9b3e4000 stackSize=1037KB",
+ " | held mutexes=",
+ " at java.lang.Object.wait(Native method)",
+ " - waiting on <0x0281f7b7> (a java.lang.Class<java.lang.ref.ReferenceQueue>)",
+ " at java.lang.Daemons$ReferenceQueueDaemon.runInternal(Daemons.java:178)",
+ " - locked <0x0281f7b7> (a java.lang.Class<java.lang.ref.ReferenceQueue>)",
+ " at java.lang.Daemons$Daemon.run(Daemons.java:103)",
+ " at java.lang.Thread.run(Thread.java:764)");
+
+ BugreportItem bugreport = new BugreportParser().parse(lines);
+ assertNotNull(bugreport.getSystemLog());
+ assertEquals(1, bugreport.getSystemLog().getAnrs().size());
+ assertEquals(
+ ArrayUtil.join("\n", expectedStack),
+ bugreport.getSystemLog().getAnrs().get(0).getTrace());
+ }
+
+ /**
+ * Test that missing sections in bugreport are set to {@code null}, not empty {@link IItem}s.
+ */
+ public void testNoSections() {
+ List<String> lines = Arrays.asList(
+ "========================================================",
+ "== dumpstate: 2012-04-25 20:45:10",
+ "========================================================");
+
+ BugreportItem bugreport = new BugreportParser().parse(lines);
+ assertNotNull(bugreport);
+ assertNull(bugreport.getDumpsys());
+ assertNull(bugreport.getKernelLog());
+ assertNull(bugreport.getMemInfo());
+ assertNull(bugreport.getProcrank());
+ assertNull(bugreport.getSystemLog());
+ assertNull(bugreport.getSystemProps());
+ assertNull(bugreport.getTop());
+ assertNotNull(bugreport.getLastKmsg());
+ List<MiscKernelLogItem> events = bugreport.getLastKmsg().getMiscEvents(
+ KernelLogParser.KERNEL_RESET);
+ assertEquals(events.size(), 1);
+ assertEquals(events.get(0).getStack(), "Unknown reason");
+
+ lines = Arrays.asList(
+ "========================================================",
+ "== dumpstate: 2012-04-25 20:45:10",
+ "========================================================",
+ "",
+ "------ DUMPSYS (dumpsys) ------",
+ "",
+ "------ KERNEL LOG (dmesg) ------",
+ "",
+ "------ LAST KMSG (/proc/last_kmsg) ------",
+ "",
+ "------ MEMORY INFO (/proc/meminfo) ------",
+ "",
+ "------ PROCRANK (procrank) ------",
+ "",
+ "------ SYSTEM LOG (logcat -v threadtime -d *:v) ------",
+ "",
+ "------ SYSTEM PROPERTIES ------",
+ "",
+ "------ CPU INFO (top -n 1 -d 1 -m 30 -t) ------",
+ "");
+
+ bugreport = new BugreportParser().parse(lines);
+ assertNotNull(bugreport);
+ assertNotNull(bugreport.getDumpsys());
+ assertNull(bugreport.getKernelLog());
+ assertNull(bugreport.getMemInfo());
+ assertNull(bugreport.getProcrank());
+ assertNull(bugreport.getSystemLog());
+ assertNull(bugreport.getSystemProps());
+ assertNull(bugreport.getTop());
+ assertNotNull(bugreport.getLastKmsg());
+ events = bugreport.getLastKmsg().getMiscEvents(KernelLogParser.KERNEL_RESET);
+ assertEquals(events.size(), 1);
+ assertEquals(events.get(0).getStack(), "Unknown reason");
+ }
+
+ /**
+ * Test that section headers are correctly parsed.
+ */
+ public void testSectionHeader() {
+ List<String> lines =
+ Arrays.asList(
+ "========================================================",
+ "== dumpstate: 2012-04-25 20:45:10",
+ "========================================================",
+ "------ DUMPSYS (dumpsys) ------",
+ "DUMP OF SERVICE SurfaceFlinger:",
+ "--DrmDisplayCompositor[0]: num_frames=1456 num_ms=475440 fps=3.06243",
+ "---- DrmDisplayCompositor Layers: num=3",
+ "------ DrmDisplayCompositor Layer: plane=17 crtc=20 crtc[x/y/w/h]=0/0/2560/1800",
+ "------ DrmDisplayCompositor Layer: plane=21 disabled",
+ "------ DrmDisplayCompositor Layer: plane=22 disabled",
+ "DUMP OF SERVICE batterystats:",
+ "Battery History (0% used, 1636 used of 256KB, 15 strings using 794):",
+ " 0 (15) RESET:TIME: 1970-01-10-06-23-28",
+ " +45s702ms (2) 001 80080000 volt=4187",
+ " +1m15s525ms (2) 001 80080000 temp=299 volt=4155",
+ "Statistics since last charged:",
+ " Time on battery: 1h 5m 2s 4ms (9%) realtime, 1h 5m 2s 4ms (9%) uptime",
+ " Time on battery screen off: 1h 4m 5s 8ms (9%) realtime, 1h 4m 5s 8ms (9%) uptime",
+ " All kernel wake locks:",
+ " Kernel Wake lock PowerManagerService.WakeLocks: 5m 10s 6ms (2 times) realtime",
+ " Kernel Wake lock msm_serial_hs_rx: 2m 13s 612ms (258 times) realtime",
+ "",
+ " All partial wake locks:",
+ " Wake lock 1001 ProxyController: 1h 4m 47s 565ms (4 times) realtime",
+ " Wake lock 1013 AudioMix: 1s 979ms (3 times) realtime",
+ "",
+ " All wakeup reasons:",
+ " Wakeup reason 2:bcmsdh_sdmmc:2:qcom,smd:2:msmgio: 1m 5s 4ms (2 times) realtime",
+ " Wakeup reason 2:qcom,smd-rpm:2:fc4c.qcom,spmi: 7m 1s 914ms (7 times) realtime",
+ "DUMP OF SERVICE package:",
+ "Package [com.google.android.calculator] (e075c9d):",
+ " use rId=10071",
+ " secondaryCpuAbi=null",
+ " versionCode=73000302 minSdk=10000 targetSdk=10000",
+ " versionName=7.3 (3821978)",
+ " splits=[base]",
+ "DUMP OF SERVICE procstats:",
+ "COMMITTED STATS FROM 2015-09-30-07-44-54:",
+ " * system / 1000 / v23:",
+ " TOTAL: 100% (118MB-118MB-118MB/71MB-71MB-71MB over 1)",
+ " Persistent: 100% (118MB-118MB-118MB/71MB-71MB-71MB over 1)",
+ " * com.android.phone / 1001 / v23:",
+ " TOTAL: 6.7%",
+ " Persistent: 6.7%",
+ "");
+ BugreportItem bugreport = new BugreportParser().parse(lines);
+ assertNotNull(bugreport.getDumpsys());
+ assertNotNull(bugreport.getDumpsys().getBatteryStats());
+ assertNotNull(bugreport.getDumpsys().getPackageStats());
+ assertNotNull(bugreport.getDumpsys().getProcStats());
+ }
+
+ /**
+ * Test that an empty input returns {@code null}.
+ */
+ public void testEmptyInput() {
+ BugreportItem item = new BugreportParser().parse(Arrays.asList(""));
+ assertNull(item);
+ }
+
+ /**
+ * Test that app names from logcat events are populated by matching the logcat PIDs with the
+ * PIDs from the logcat.
+ */
+ public void testSetAppsFromProcrank() {
+ List<String> lines = Arrays.asList(
+ "========================================================",
+ "== dumpstate: 2012-04-25 20:45:10",
+ "========================================================",
+ "------ PROCRANK (procrank) ------",
+ " PID Vss Rss Pss Uss cmdline",
+ " 3064 87136K 81684K 52829K 50012K com.android.package1",
+ " 3066 87136K 81684K 52829K 50012K com.android.package2",
+ " ------ ------ ------",
+ " 203624K 163604K TOTAL",
+ "RAM: 731448K total, 415804K free, 9016K buffers, 108548K cached",
+ "[procrank: 1.6s elapsed]",
+ "------ SYSTEM LOG (logcat -v threadtime -d *:v) ------",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: java.lang.Exception",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)",
+ "04-25 09:55:47.799 3065 3083 E AndroidRuntime: java.lang.Exception",
+ "04-25 09:55:47.799 3065 3083 E AndroidRuntime: \tat class.method1(Class.java:1)",
+ "04-25 09:55:47.799 3065 3083 E AndroidRuntime: \tat class.method2(Class.java:2)",
+ "04-25 09:55:47.799 3065 3083 E AndroidRuntime: \tat class.method3(Class.java:3)",
+ "04-25 09:55:47.799 3065 3084 E AndroidRuntime: FATAL EXCEPTION: main",
+ "04-25 09:55:47.799 3066 3084 E AndroidRuntime: Process: com.android.package3, PID: 1234",
+ "04-25 09:55:47.799 3066 3084 E AndroidRuntime: java.lang.Exception",
+ "04-25 09:55:47.799 3066 3084 E AndroidRuntime: \tat class.method1(Class.java:1)",
+ "04-25 09:55:47.799 3066 3084 E AndroidRuntime: \tat class.method2(Class.java:2)",
+ "04-25 09:55:47.799 3066 3084 E AndroidRuntime: \tat class.method3(Class.java:3)");
+
+ BugreportItem bugreport = new BugreportParser().parse(lines);
+ assertNotNull(bugreport.getSystemLog());
+ assertEquals(3, bugreport.getSystemLog().getJavaCrashes().size());
+ assertEquals("com.android.package1",
+ bugreport.getSystemLog().getJavaCrashes().get(0).getApp());
+ assertNull(bugreport.getSystemLog().getJavaCrashes().get(1).getApp());
+ assertEquals("com.android.package3",
+ bugreport.getSystemLog().getJavaCrashes().get(2).getApp());
+ }
+
+ /**
+ * Some Android devices refer to SYSTEM LOG as MAIN LOG. Check that parser recognizes this
+ * alternate syntax.
+ */
+ public void testSystemLogAsMainLog() {
+ List<String> lines = Arrays.asList(
+ "------ MAIN LOG (logcat -b main -b system -v threadtime -d *:v) ------",
+ "--------- beginning of /dev/log/system",
+ "12-11 19:48:07.945 1484 1508 D BatteryService: update start");
+ BugreportItem bugreport = new BugreportParser().parse(lines);
+ assertNotNull(bugreport.getSystemLog());
+ }
+
+ /**
+ * Some Android devices refer to SYSTEM LOG as MAIN AND SYSTEM LOG. Check that parser
+ * recognizes this alternate syntax.
+ */
+ public void testSystemAndMainLog() {
+ List<String> lines = Arrays.asList(
+ "------ MAIN AND SYSTEM LOG (logcat -b main -b system -v threadtime -d *:v) ------",
+ "--------- beginning of /dev/log/system",
+ "12-17 15:15:12.877 1994 2019 D UiModeManager: updateConfigurationLocked: ");
+ BugreportItem bugreport = new BugreportParser().parse(lines);
+ assertNotNull(bugreport.getSystemLog());
+ }
+
+ private Date parseTime(String timeStr) throws ParseException {
+ DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+ return formatter.parse(timeStr);
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/CompactMemInfoParserTest.java b/javatests/com/android/loganalysis/parser/CompactMemInfoParserTest.java
new file mode 100644
index 0000000..ed4b39b
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/CompactMemInfoParserTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2014 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.loganalysis.parser;
+
+import com.android.loganalysis.item.CompactMemInfoItem;
+
+import junit.framework.TestCase;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class CompactMemInfoParserTest extends TestCase {
+
+ public void testSingleProcLineWithSwapHasActivities() {
+ List<String> input = Arrays.asList("proc,cached,com.google.android.youtube1,2964,19345,1005,a");
+
+ CompactMemInfoItem item = new CompactMemInfoParser().parse(input);
+
+ assertEquals(1, item.getPids().size());
+ assertEquals("com.google.android.youtube1", item.getName(2964));
+ assertEquals(19345, item.getPss(2964));
+ assertEquals(1005, item.getSwap(2964));
+ assertEquals("cached", item.getType(2964));
+ assertEquals(true, item.hasActivities(2964));
+ }
+
+ public void testSingleProcLineWithoutSwapHasActivities() {
+ List<String> input = Arrays.asList("proc,cached,com.google.android.youtube,2964,19345,a");
+
+ CompactMemInfoItem item = new CompactMemInfoParser().parse(input);
+
+ assertEquals(1, item.getPids().size());
+ assertEquals("com.google.android.youtube", item.getName(2964));
+ assertEquals(19345, item.getPss(2964));
+ assertEquals(0, item.getSwap(2964));
+ assertEquals("cached", item.getType(2964));
+ assertEquals(true, item.hasActivities(2964));
+ }
+
+
+ public void testSingleProcLineWithoutSwapNoActivities() {
+ List<String> input = Arrays.asList("proc,cached,com.google.android.youtube,2964,19345,e");
+
+ CompactMemInfoItem item = new CompactMemInfoParser().parse(input);
+
+ assertEquals(1, item.getPids().size());
+ assertEquals("com.google.android.youtube", item.getName(2964));
+ assertEquals(19345, item.getPss(2964));
+ assertEquals(0, item.getSwap(2964));
+ assertEquals("cached", item.getType(2964));
+ assertEquals(false, item.hasActivities(2964));
+ }
+
+ public void testSingleLostRamLine() {
+ List<String> input = Arrays.asList("lostram,1005");
+ CompactMemInfoItem item = new CompactMemInfoParser().parse(input);
+ assertEquals(1005, item.getLostRam());
+ }
+
+ public void testSingleRamLine() {
+ List<String> input = Arrays.asList("ram,2866484,1221694,1112777");
+ CompactMemInfoItem item = new CompactMemInfoParser().parse(input);
+ assertEquals(1221694, item.getFreeRam());
+ }
+
+ public void testSingleZramLine() {
+ List<String> input = Arrays.asList("zram,5800,520908,491632");
+ CompactMemInfoItem item = new CompactMemInfoParser().parse(input);
+ assertEquals(5800, item.getTotalZram());
+ assertEquals(491632, item.getFreeSwapZram());
+ }
+
+ public void testSingleTuningLine() {
+ // With specific configuration
+ List<String> input1 = Arrays.asList("tuning,192,512,322560,high-end-gfx");
+ CompactMemInfoItem item1 = new CompactMemInfoParser().parse(input1);
+ assertEquals(322560, item1.getTuningLevel());
+ // Without specific configuration
+ List<String> input2 = Arrays.asList("tuning,193,513,322561");
+ CompactMemInfoItem item2 = new CompactMemInfoParser().parse(input2);
+ assertEquals(322561, item2.getTuningLevel());
+ }
+
+ public void testSomeMalformedLines() {
+ List<String> input =
+ Arrays.asList(
+ "proc,cached,com.google.android.youtube,a,b,e",
+ "proc,cached,com.google.android.youtube,2964,c,e",
+ "proc,cached,com.google.android.youtube,2964,e",
+ "proc,cached,com.google.android.youtube,2964,19345,a,e",
+ "lostram,a,1000",
+ "lostram,1000,a",
+ "ram,123,345",
+ "ram,123,345,abc",
+ "ram,123,345,456,678",
+ "ram,123,345,456,abc",
+ "zram,123,345",
+ "zram,123,345,abc",
+ "zram,123,345,456,678",
+ "zram,123,345,456,abc",
+ "tuning,123,345",
+ "tuning,123,345,abc");
+
+ CompactMemInfoItem item = new CompactMemInfoParser().parse(input);
+
+ assertEquals(0, item.getPids().size());
+ }
+
+ public void testMultipleLines() {
+ List<String> input =
+ Arrays.asList(
+ "proc,cached,com.google.android.youtube,2964,19345,123,e",
+ "proc,cached,com.google.android.apps.plus,2877,9604,N/A,e",
+ "proc,cached,com.google.android.apps.magazines,2009,20111,N/A,e",
+ "proc,cached,com.google.android.apps.walletnfcrel,10790,11164,100,e",
+ "proc,cached,com.google.android.incallui,3410,9491,N/A,e",
+ "lostram,1005",
+ "ram,2866484,1221694,1112777",
+ "zram,5800,520908,491632",
+ "tuning,193,513,322561");
+
+ CompactMemInfoItem item = new CompactMemInfoParser().parse(input);
+
+ assertEquals(5, item.getPids().size());
+ assertEquals("com.google.android.youtube", item.getName(2964));
+ assertEquals(19345, item.getPss(2964));
+ assertEquals(123, item.getSwap(2964));
+ assertEquals("cached", item.getType(2964));
+ assertEquals(false, item.hasActivities(2964));
+
+ assertEquals(1005, item.getLostRam());
+ assertEquals(1221694, item.getFreeRam());
+ assertEquals(5800, item.getTotalZram());
+ assertEquals(491632, item.getFreeSwapZram());
+ assertEquals(322561, item.getTuningLevel());
+ }
+
+ public void testSkipNonProcLines() {
+ // Skip lines which does not start with proc
+
+ List<String> input = Arrays.asList(
+ "oom,cached,141357",
+ "proc,cached,com.google.android.youtube,2964,19345,54321,e",
+ "proc,cached,com.google.android.apps.plus,2877,9604,4321,e",
+ "proc,cached,com.google.android.apps.magazines,2009,20111,321,e",
+ "proc,cached,com.google.android.apps.walletnfcrel,10790,11164,21,e",
+ "proc,cached,com.google.android.incallui,3410,9491,1,e",
+ "cat,Native,63169");
+
+ CompactMemInfoItem item = new CompactMemInfoParser().parse(input);
+
+ assertEquals(5, item.getPids().size());
+ assertEquals("com.google.android.youtube", item.getName(2964));
+ assertEquals(19345, item.getPss(2964));
+ assertEquals(54321, item.getSwap(2964));
+ assertEquals("cached", item.getType(2964));
+ assertEquals(false, item.hasActivities(2964));
+ }
+
+ public void testJson() throws JSONException {
+ List<String> input =
+ Arrays.asList(
+ "oom,cached,141357",
+ "proc,cached,com.google.android.youtube,2964,19345,N/A,e",
+ "proc,cached,com.google.android.apps.plus,2877,9604,50,e",
+ "proc,cached,com.google.android.apps.magazines,2009,20111,100,e",
+ "proc,cached,com.google.android.apps.walletnfcrel,10790,11164,0,e",
+ "proc,cached,com.google.android.incallui,3410,9491,500,e",
+ "lostram,1005",
+ "ram,2866484,1221694,1112777",
+ "zram,5800,520908,491632",
+ "tuning,193,513,322561",
+ "cat,Native,63169");
+
+ CompactMemInfoItem item = new CompactMemInfoParser().parse(input);
+ JSONObject json = item.toJson();
+ assertNotNull(json);
+
+ JSONArray processes = json.getJSONArray("processes");
+ assertEquals(5, processes.length());
+
+ assertEquals(1005, (long)json.get("lostRam"));
+ assertEquals(1221694, (long) json.get("freeRam"));
+ assertEquals(5800, (long) json.get("totalZram"));
+ assertEquals(491632, (long) json.get("freeSwapZram"));
+ assertEquals(322561, (long) json.get("tuningLevel"));
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/CpuInfoParserTest.java b/javatests/com/android/loganalysis/parser/CpuInfoParserTest.java
new file mode 100644
index 0000000..196f6a2
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/CpuInfoParserTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 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.loganalysis.parser;
+
+import com.android.loganalysis.item.CpuInfoItem;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class CpuInfoParserTest extends TestCase {
+
+ public void testSingleLine() {
+ List<String> input = Arrays.asList(" 0.1% 170/surfaceflinger: 0% user + 0% kernel");
+
+ CpuInfoItem item = new CpuInfoParser().parse(input);
+
+ assertEquals(1, item.getPids().size());
+ assertEquals("surfaceflinger", item.getName(170));
+ assertEquals(0.1, item.getPercent(170), 0.0001);
+ }
+
+ public void testMultipleLines() {
+ List<String> input = Arrays.asList(
+ "CPU usage from 35935ms to 26370ms ago:",
+ " 57% 489/system_server: 37% user + 20% kernel / faults: 39754 minor 57 major",
+ " 34% 853/com.google.android.leanbacklauncher: 30% user + 4.6% kernel / faults: 7838 minor 14 major",
+ " 15% 19463/com.google.android.videos: 11% user + 3.3% kernel / faults: 21603 minor 141 major",
+ " 8.2% 170/surfaceflinger: 3.4% user + 4.8% kernel / faults: 1 minor");
+ CpuInfoItem item = new CpuInfoParser().parse(input);
+
+ assertEquals(4, item.getPids().size());
+ assertEquals("system_server", item.getName(489));
+ assertEquals(57.0, item.getPercent(489), 0.0001);
+ assertEquals("surfaceflinger", item.getName(170));
+ assertEquals(8.2, item.getPercent(170), 0.0001);
+ }
+
+}
+
diff --git a/javatests/com/android/loganalysis/parser/DmesgParserTest.java b/javatests/com/android/loganalysis/parser/DmesgParserTest.java
new file mode 100644
index 0000000..e4ea7af
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/DmesgParserTest.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2016 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.loganalysis.parser;
+
+import com.android.loganalysis.item.DmesgActionInfoItem;
+import com.android.loganalysis.item.DmesgItem;
+import com.android.loganalysis.item.DmesgServiceInfoItem;
+import com.android.loganalysis.item.DmesgStageInfoItem;
+import com.android.loganalysis.item.DmesgModuleInfoItem;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link DmesgParser}.
+ */
+public class DmesgParserTest extends TestCase {
+
+ private static final String BOOT_ANIMATION = "bootanim";
+ private static final String NETD = "netd";
+ private static final String FOO = "foo";
+ private static final String BAR = "bar";
+ private static final String TOTAL_MODULE = "TOTAL_MODULE";
+ private static final String[] LINES =
+ new String[] {
+ "[ 0.370107] init: Loading module /lib/modules/foo.ko with args ''",
+ "[ 0.372497] init: Loaded kernel module /lib/modules/foo.ko",
+ "[ 0.372500] init: Loading module /lib/modules/bar.ko with args ''",
+ "[ 1.115467] init: Loaded 198 kernel modules took 748 ms",
+ "[ 2.471163] init: Wait for property 'apexd.status=ready' took 403ms",
+ "[ 3.786943] ueventd: Coldboot took 0.701291 seconds",
+ "[ 22.962730] init: starting service 'bootanim'...",
+ "[ 23.252321] init: starting service 'netd'...",
+ "[ 29.331069] ipa-wan ipa_wwan_ioctl:1428 dev(rmnet_data0) register to IPA",
+ "[ 32.182592] ueventd: fixup /sys/devices/virtual/input/poll_delay 0 1004 660",
+ "[ 35.642666] SELinux: initialized (dev fuse, type fuse), uses genfs_contexts",
+ "[ 39.855818] init: Service 'bootanim' (pid 588) exited with status 0",
+ "[ 41.665818] init: init first stage started!",
+ "[ 44.942872] init: processing action (early-init) from (/init.rc:13)",
+ "[ 47.233446] init: processing action (set_mmap_rnd_bits) from (<Builtin "
+ + "Action>:0)",
+ "[ 47.240083] init: processing action (set_kptr_restrict) from (<Builtin"
+ + " Action>:0)",
+ "[ 47.245778] init: processing action (keychord_init) from (<Builtin Action>:0)",
+ "[ 52.361049] init: processing action (persist.sys.usb.config=* boot) from"
+ + " (<Builtin Action>:0)",
+ "[ 52.361108] init: processing action (enable_property_trigger) from (<Builtin"
+ + " Action>:0)",
+ "[ 52.361313] init: processing action (security.perf_harden=1) from"
+ + " (/init.rc:677)",
+ "[ 52.361495] init: processing action (ro.debuggable=1) from (/init.rc:700)",
+ "[ 58.298293] init: processing action (sys.boot_completed=1)",
+ "[ 59.331069] ipa-wan ipa_wwan_ioctl:1428 dev(rmnet_data0) register to IPA",
+ "[ 62.182592] ueventd: fixup /sys/devices/virtual/input/poll_delay 0 1004 660",
+ "[ 65.642666] SELinux: initialized (dev fuse, type fuse), uses genfs_contexts",
+ "[ 69.855818] init: Service 'bootanim' (pid 588) exited with status 0"
+ };
+
+ private static final Map<String, DmesgServiceInfoItem> EXPECTED_SERVICE_INFO_ITEMS =
+ getExpectedServiceInfoItems();
+
+ private static final List<DmesgStageInfoItem> EXPECTED_STAGE_INFO_ITEMS =
+ getExpectedStageInfoItems();
+
+ private static final List<DmesgActionInfoItem> EXPECTED_ACTION_INFO_ITEMS =
+ getExpectedActionInfoItems();
+
+ private static final Map<String, DmesgModuleInfoItem> EXPECTED_MODULE_INFO_ITEMS =
+ getExpectedModuleInfoItems();
+
+ /**
+ * Test for empty dmesg logs passed to the DmesgParser
+ */
+ public void testEmptyDmesgLog() throws IOException {
+ String[] lines = new String[] {""};
+ try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
+ new ByteArrayInputStream(String.join("\n", lines).getBytes())))) {
+ DmesgParser dmesgParser = new DmesgParser();
+ dmesgParser.parseInfo(bufferedReader);
+ assertEquals("Service info items list should be empty", 0,
+ dmesgParser.getServiceInfoItems().size());
+ }
+ }
+
+ /**
+ * Test for complete dmesg logs passed as list of strings
+ */
+ public void testCompleteDmesgLog_passedAsList() {
+
+ DmesgParser dmesgParser = new DmesgParser();
+ DmesgItem actualDmesgItem = dmesgParser.parse(Arrays.asList(LINES));
+
+ assertEquals("Service info items list size should be 2", 2,
+ dmesgParser.getServiceInfoItems().size());
+ assertEquals("Stage info items list size should be 3",3,
+ dmesgParser.getStageInfoItems().size());
+ assertEquals("Action info items list size should be 9",9,
+ dmesgParser.getActionInfoItems().size());
+ assertEquals(
+ "Module info items list size should be 3",
+ 3,
+ dmesgParser.getModuleInfoItems().size());
+
+ assertEquals(EXPECTED_SERVICE_INFO_ITEMS, actualDmesgItem.getServiceInfoItems());
+ assertEquals(EXPECTED_STAGE_INFO_ITEMS, actualDmesgItem.getStageInfoItems());
+ assertEquals(EXPECTED_ACTION_INFO_ITEMS, actualDmesgItem.getActionInfoItems());
+ assertEquals(EXPECTED_MODULE_INFO_ITEMS, actualDmesgItem.getModuleInfoItems());
+ }
+
+ /**
+ * Test for complete dmesg logs passed as buffered input
+ */
+ public void testCompleteDmesgLog_passedAsBufferedInput() throws IOException {
+ try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
+ new ByteArrayInputStream(String.join("\n", LINES).getBytes())))) {
+ DmesgParser dmesgParser = new DmesgParser();
+ dmesgParser.parseInfo(bufferedReader);
+ assertEquals("Service info items list size should be 2", 2,
+ dmesgParser.getServiceInfoItems().size());
+ assertEquals("Stage info items list size should be 3", 3,
+ dmesgParser.getStageInfoItems().size());
+ assertEquals("Action info items list size should be 9",9,
+ dmesgParser.getActionInfoItems().size());
+ assertEquals(
+ "Module info items list size should be 3",
+ 3,
+ dmesgParser.getModuleInfoItems().size());
+ }
+ }
+
+ /**
+ * Test service which logs both the start and end time
+ */
+ public void testCompleteServiceInfo() {
+ DmesgParser dmesgParser = new DmesgParser();
+ for (String line : LINES) {
+ dmesgParser.parseServiceInfo(line);
+ }
+
+ assertEquals("There should be two service infos", 2,
+ dmesgParser.getServiceInfoItems().size());
+ assertEquals(EXPECTED_SERVICE_INFO_ITEMS, dmesgParser.getServiceInfoItems());
+ }
+
+ /**
+ * Test service which logs only the start time
+ */
+ public void testStartServiceInfo() {
+ DmesgParser dmesgParser = new DmesgParser();
+ for (String line : LINES) {
+ dmesgParser.parseServiceInfo(line);
+ }
+ List<DmesgServiceInfoItem> serviceInfoItems = new ArrayList<>(
+ dmesgParser.getServiceInfoItems().values());
+ assertEquals("There should be exactly two service infos", 2, serviceInfoItems.size());
+ assertEquals("Service name is not bootanim", BOOT_ANIMATION,
+ serviceInfoItems.get(0).getServiceName());
+ assertEquals("Service name is not netd", NETD, serviceInfoItems.get(1).getServiceName());
+ }
+
+ /**
+ * Test multiple service info parsed correctly and stored in the same order logged in
+ * the file.
+ */
+ public void testMultipleServiceInfo() {
+ DmesgParser dmesgParser = new DmesgParser();
+ for (String line : LINES) {
+ dmesgParser.parseServiceInfo(line);
+ }
+ assertEquals("There should be exactly two service info", 2,
+ dmesgParser.getServiceInfoItems().size());
+ assertEquals(EXPECTED_SERVICE_INFO_ITEMS, dmesgParser.getServiceInfoItems());
+ }
+
+ /**
+ * Test invalid patterns on the start and exit service logs
+ */
+ public void testInvalidServiceLogs() {
+ // Added space at the end of the start and exit of service logs to make it invalid
+ String[] lines = new String[] {
+ "[ 22.962730] init: starting service 'bootanim'... ",
+ "[ 23.252321] init: starting service 'netd'... ",
+ "[ 29.331069] ipa-wan ipa_wwan_ioctl:1428 dev(rmnet_data0) register to IPA",
+ "[ 32.182592] ueventd: fixup /sys/devices/virtual/input/poll_delay 0 1004 660",
+ "[ 35.642666] SELinux: initialized (dev fuse, type fuse), uses genfs_contexts",
+ "[ 39.855818] init: Service 'bootanim' (pid 588) exited with status 0 "};
+ DmesgParser dmesgParser = new DmesgParser();
+ for (String line : lines) {
+ dmesgParser.parseServiceInfo(line);
+ }
+ List<DmesgServiceInfoItem> serviceInfoItems = new ArrayList<>(
+ dmesgParser.getServiceInfoItems().values());
+ assertEquals("No service info should be available", 0, serviceInfoItems.size());
+ }
+
+ /**
+ * Test init stages' start time logs
+ */
+ public void testCompleteStageInfo() {
+ DmesgParser dmesgParser = new DmesgParser();
+ for (String line : LINES) {
+ dmesgParser.parseStageInfo(line);
+ }
+ List<DmesgStageInfoItem> stageInfoItems = dmesgParser.getStageInfoItems();
+ assertEquals(3, stageInfoItems.size());
+ assertEquals(EXPECTED_STAGE_INFO_ITEMS, stageInfoItems);
+ }
+
+ /** Test processing action start time logs */
+ public void testCompleteActionInfo() {
+ DmesgParser dmesgParser = new DmesgParser();
+ for (String line : LINES) {
+ dmesgParser.parseActionInfo(line);
+ }
+ List<DmesgActionInfoItem> actualActionInfoItems = dmesgParser.getActionInfoItems();
+ assertEquals(9, actualActionInfoItems.size());
+ assertEquals(EXPECTED_ACTION_INFO_ITEMS, actualActionInfoItems);
+ }
+
+ /** Test incomplete module loaded pattern */
+ public void testIncompleteModuleInfo() {
+ DmesgParser dmesgParser = new DmesgParser();
+ for (String line : LINES) {
+ dmesgParser.parseModuleInfo(line);
+ }
+ List<DmesgModuleInfoItem> actualModuleInfoItems =
+ new ArrayList<>(dmesgParser.getModuleInfoItems().values());
+ assertEquals(EXPECTED_MODULE_INFO_ITEMS, dmesgParser.getModuleInfoItems());
+ assertEquals(3, actualModuleInfoItems.size());
+ assertEquals(
+ "Duration should be -1L",
+ Long.valueOf(-1L),
+ actualModuleInfoItems.get(0).getModuleDuration());
+ }
+
+ private static List<DmesgActionInfoItem> getExpectedActionInfoItems() {
+ return Arrays.asList(
+ new DmesgActionInfoItem("/init.rc:13", "early-init", 44942L),
+ new DmesgActionInfoItem("<Builtin Action>:0", "set_mmap_rnd_bits", 47233L),
+ new DmesgActionInfoItem("<Builtin Action>:0", "set_kptr_restrict", 47240L),
+ new DmesgActionInfoItem("<Builtin Action>:0", "keychord_init", 47245L),
+ new DmesgActionInfoItem(
+ "<Builtin Action>:0", "persist.sys.usb.config=* boot", 52361L),
+ new DmesgActionInfoItem("<Builtin Action>:0", "enable_property_trigger", 52361L),
+ new DmesgActionInfoItem("/init.rc:677", "security.perf_harden=1", 52361L),
+ new DmesgActionInfoItem("/init.rc:700", "ro.debuggable=1", 52361L),
+ new DmesgActionInfoItem(null, "sys.boot_completed=1", 58298L));
+ }
+
+ private static List<DmesgStageInfoItem> getExpectedStageInfoItems() {
+ return Arrays.asList(
+ new DmesgStageInfoItem("init_Wait for property 'apexd.status=ready'", null, 403L),
+ new DmesgStageInfoItem("ueventd_Coldboot", null, 701L),
+ new DmesgStageInfoItem("first", 41665L, null));
+ }
+
+ private static Map<String, DmesgServiceInfoItem> getExpectedServiceInfoItems() {
+ Map<String, DmesgServiceInfoItem> serviceInfoItemsMap = new HashMap<>();
+ DmesgServiceInfoItem bootanimServiceInfoItem = new DmesgServiceInfoItem();
+ bootanimServiceInfoItem.setServiceName(BOOT_ANIMATION);
+ bootanimServiceInfoItem.setStartTime(22962L);
+ bootanimServiceInfoItem.setEndTime(69855L);
+
+ DmesgServiceInfoItem netdServiceInfoItem = new DmesgServiceInfoItem();
+ netdServiceInfoItem.setServiceName(NETD);
+ netdServiceInfoItem.setStartTime(23252L);
+
+ serviceInfoItemsMap.put(BOOT_ANIMATION, bootanimServiceInfoItem);
+ serviceInfoItemsMap.put(NETD, netdServiceInfoItem);
+
+ return serviceInfoItemsMap;
+ }
+
+ private static Map<String, DmesgModuleInfoItem> getExpectedModuleInfoItems() {
+ Map<String, DmesgModuleInfoItem> moduleInfoItemsMap = new HashMap<>();
+ DmesgModuleInfoItem fooModuleInfo = new DmesgModuleInfoItem();
+ fooModuleInfo.setModuleName(FOO);
+ fooModuleInfo.setStartTime(370L);
+ fooModuleInfo.setEndTime(372L);
+
+ DmesgModuleInfoItem barModuleInfo = new DmesgModuleInfoItem();
+ barModuleInfo.setModuleName(BAR);
+ barModuleInfo.setStartTime(372L);
+
+ DmesgModuleInfoItem totalInfoItem = new DmesgModuleInfoItem();
+ totalInfoItem.setModuleName(TOTAL_MODULE);
+ totalInfoItem.setModuleCount("198");
+ totalInfoItem.setModuleDuration(748L);
+
+ moduleInfoItemsMap.put(FOO, fooModuleInfo);
+ moduleInfoItemsMap.put(BAR, barModuleInfo);
+ moduleInfoItemsMap.put(TOTAL_MODULE, totalInfoItem);
+
+ return moduleInfoItemsMap;
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/DumpsysBatteryStatsParserTest.java b/javatests/com/android/loganalysis/parser/DumpsysBatteryStatsParserTest.java
new file mode 100644
index 0000000..e96491a
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/DumpsysBatteryStatsParserTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2015 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.loganalysis.parser;
+
+import com.android.loganalysis.item.DumpsysBatteryStatsItem;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link DumpsysBatteryStatsParser}
+ */
+public class DumpsysBatteryStatsParserTest extends TestCase {
+
+ /**
+ * Test that normal input is parsed.
+ */
+ public void testBatteryStatsParser() {
+ List<String> inputBlock = Arrays.asList(
+ "Battery History (37% used, 95KB used of 256KB, 166 strings using 15KB):",
+ " 0 (9) RESET:TIME: 2014-12-09-11-33-29",
+ " +1s067ms (1) 100 c0500020 -wifi_full_lock -wifi_scan",
+ " +3s297ms (2) 100 80400020 -wake_lock -screen",
+ " +30m02s075ms (1) 100 c0500020 wifi_signal_strength=4 wifi_suppl=completed",
+ " +30m03s012ms (2) 099 c0500020 temp=306 volt=4217",
+ " +33m48s967ms (1) 099 f8400020 +wifi_scan",
+ " +33m49s335ms (2) 098 f0400020 temp=324 -wifi_scan",
+ "Statistics since last charge:",
+ " Time on battery: 2h 21m 5s 622ms (12.0%) realtime, 7m 54s 146ms (0.7%) uptime",
+ " Time on battery screen off: 2h 5m 55s 3ms (1%) realtime, 7m 4s 5ms (7%) uptime",
+ " Total run time: 19h 38m 43s 650ms realtime, 17h 25m 32s 175ms uptime",
+ " All kernel wake locks:",
+ " Kernel Wake lock PowerManagerService.WakeLocks: 1h 3m 50s 5ms (8 times) realtime",
+ " Kernel Wake lock event0-2656 : 3m 49s 268ms (2399 times) realtime",
+ " Kernel Wake lock wlan_wd_wake: 3m 34s 639ms (1751 times) realtime",
+ " Kernel Wake lock wlan_rx_wake: 3m 19s 887ms (225 times) realtime",
+ " Kernel Wake lock wlan_tx_wake: 2m 19s 887ms (225 times) realtime",
+ " Kernel Wake lock tx_wake: 1m 19s 887ms (225 times) realtime",
+ " ",
+ " All partial wake locks:",
+ " Wake lock u0a7 NlpWakeLock: 8m 13s 203ms (1479 times) realtime",
+ " Wake lock u0a7 NlpCollectorWakeLock: 6m 29s 18ms (238 times) realtime",
+ " Wake lock u0a7 GCM_CONN_ALARM: 6m 8s 587ms (239 times) realtime",
+ " Wake lock 1000 *alarm*: 5m 11s 316ms (1469 times) realtime",
+ " Wake lock u10 xxx: 4m 11s 316ms (1469 times) realtime",
+ " Wake lock u30 cst: 2m 11s 316ms (1469 times) realtime",
+ " ",
+ " All wakeup reasons:",
+ " Wakeup reason 200:qcom,smd-rpm:222:fc4: 11m 49s 332ms (0 times) realtime",
+ " Wakeup reason 200:qcom,smd-rpm: 48s 45ms (0 times) realtime",
+ " Wakeup reason 2:qcom,smd-rpm:2:f0.qm,mm:22:fc4mi: 3s 417ms (0 times) realtime",
+ " Wakeup reason 188:qcom,smd-adsp:200:qcom,smd-rpm: 1s 656ms (0 times) realtime",
+ " Wakeup reason 58:qcom,smsm-modem:2:qcom,smd-rpm: 6m 16s 1ms (5 times) realtime",
+ " Wakeup reason 57:qcom,smd-modem:200:qcom,smd-rpm: 40s 995ms (0 times) realtime",
+ " Wakeup reason unknown: 8s 455ms (0 times) realtime",
+ " Wakeup reason 9:bcmsdh_sdmmc:2:qcomd-rpm:240:mso: 8m 5s 9ms (0 times) realtime",
+ " ",
+ " 0:",
+ " User activity: 2 other",
+ " Wake lock SCREEN_FROZEN realtime",
+ " Sensor 0: 9s 908ms realtime (1 times)",
+ " Sensor 1: 9s 997ms realtime (1 times)",
+ " Foreground for: 2h 21m 5s 622ms",
+ " Apk android:",
+ " 24 wakeup alarms",
+ " u0a9:",
+ " Mobile network: 8.1KB received, 1.6KB sent (packets 291 received, 342 sent)",
+ " Mobile radio active: 3m 43s 890ms (34.2%) 39x @ 354 mspp",
+ " Sensor 2: 12m 13s 15ms realtime (5 times)",
+ " Sensor 32: (not used)",
+ " Sensor 35: (not used)");
+
+ DumpsysBatteryStatsItem batteryStats = new DumpsysBatteryStatsParser().parse(inputBlock);
+ assertNotNull(batteryStats.getBatteryStatsSummaryItem());
+ assertNotNull(batteryStats.getDetailedBatteryStatsItem());
+ }
+
+ public void testBatteryStatsWithNoKB() {
+ List<String> inputBlock = Arrays.asList(
+ "Battery History (37% used, 95 used of 256KB, 166 strings using 15):",
+ " 0 (9) RESET:TIME: 2014-12-09-11-33-29",
+ " +1s067ms (1) 100 c0500020 -wifi_full_lock -wifi_scan",
+ " +3s297ms (2) 100 80400020 -wake_lock -screen",
+ " +30m02s075ms (1) 100 c0500020 wifi_signal_strength=4 wifi_suppl=completed",
+ " +30m03s012ms (2) 099 c0500020 temp=306 volt=4217",
+ " +33m48s967ms (1) 099 f8400020 +wifi_scan",
+ " +33m49s335ms (2) 098 f0400020 temp=324 -wifi_scan",
+ "Statistics since last charge:",
+ " Time on battery: 2h 21m 5s 622ms (12.0%) realtime, 7m 54s 146ms (0.7%) uptime",
+ " Time on battery screen off: 2h 5m 55s 3ms (1%) realtime, 7m 4s 5ms (7%) uptime",
+ " Total run time: 19h 38m 43s 650ms realtime, 17h 25m 32s 175ms uptime",
+ " All kernel wake locks:",
+ " Kernel Wake lock PowerManagerService.WakeLocks: 1h 3m 50s 5ms (8 times) realtime",
+ " Kernel Wake lock event0-2656 : 3m 49s 268ms (2399 times) realtime",
+ " Kernel Wake lock wlan_wd_wake: 3m 34s 639ms (1751 times) realtime",
+ " Kernel Wake lock wlan_rx_wake: 3m 19s 887ms (225 times) realtime",
+ " Kernel Wake lock wlan_tx_wake: 2m 19s 887ms (225 times) realtime",
+ " Kernel Wake lock tx_wake: 1m 19s 887ms (225 times) realtime",
+ " ",
+ " All partial wake locks:",
+ " Wake lock u0a7 NlpWakeLock: 8m 13s 203ms (1479 times) realtime",
+ " Wake lock u0a7 NlpCollectorWakeLock: 6m 29s 18ms (238 times) realtime",
+ " Wake lock u0a7 GCM_CONN_ALARM: 6m 8s 587ms (239 times) realtime",
+ " Wake lock 1000 *alarm*: 5m 11s 316ms (1469 times) realtime",
+ " Wake lock u10 xxx: 4m 11s 316ms (1469 times) realtime",
+ " Wake lock u30 cst: 2m 11s 316ms (1469 times) realtime",
+ " ",
+ " All wakeup reasons:",
+ " Wakeup reason 200:qcom,smd-rpm:222:fc4: 11m 49s 332ms (0 times) realtime",
+ " Wakeup reason 200:qcom,smd-rpm: 48s 45ms (0 times) realtime",
+ " Wakeup reason 2:qcom,smd-rpm:2:f0.qm,mm:22:fc4mi: 3s 417ms (0 times) realtime",
+ " Wakeup reason 188:qcom,smd-adsp:200:qcom,smd-rpm: 1s 656ms (0 times) realtime",
+ " Wakeup reason 58:qcom,smsm-modem:2:qcom,smd-rpm: 6m 16s 1ms (5 times) realtime",
+ " Wakeup reason 57:qcom,smd-modem:200:qcom,smd-rpm: 40s 995ms (0 times) realtime",
+ " Wakeup reason unknown: 8s 455ms (0 times) realtime",
+ " Wakeup reason 9:bcmsdh_sdmmc:2:qcomd-rpm:240:mso: 8m 5s 9ms (0 times) realtime",
+ " ",
+ " 0:",
+ " User activity: 2 other",
+ " Wake lock SCREEN_FROZEN realtime",
+ " Sensor 0: 9s 908ms realtime (1 times)",
+ " Sensor 1: 9s 997ms realtime (1 times)",
+ " Foreground for: 2h 21m 5s 622ms",
+ " Apk android:",
+ " 24 wakeup alarms",
+ " u0a9:",
+ " Mobile network: 8.1KB received, 1.6KB sent (packets 291 received, 342 sent)",
+ " Mobile radio active: 3m 43s 890ms (34.2%) 39x @ 354 mspp",
+ " Sensor 2: 12m 13s 15ms realtime (5 times)",
+ " Sensor 32: (not used)",
+ " Sensor 35: (not used)");
+
+ DumpsysBatteryStatsItem batteryStats = new DumpsysBatteryStatsParser().parse(inputBlock);
+ assertNotNull(batteryStats.getBatteryStatsSummaryItem());
+ assertNotNull(batteryStats.getDetailedBatteryStatsItem());
+ }
+}
+
diff --git a/javatests/com/android/loganalysis/parser/DumpsysPackageStatsParserTest.java b/javatests/com/android/loganalysis/parser/DumpsysPackageStatsParserTest.java
new file mode 100644
index 0000000..ee15b78
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/DumpsysPackageStatsParserTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 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.loganalysis.parser;
+
+import com.android.loganalysis.item.AppVersionItem;
+import com.android.loganalysis.item.DumpsysPackageStatsItem;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** Unit tests for {@link DumpsysPackageStatsParser} */
+public class DumpsysPackageStatsParserTest extends TestCase {
+
+ /** Test that normal input is parsed. */
+ public void testDumpsysPackageStatsParser() {
+ List<String> inputBlock =
+ Arrays.asList(
+ "DUMP OF SERVICE package:",
+ "Package [com.google.android.calculator] (e075c9d):",
+ " userId=10071",
+ " secondaryCpuAbi=null",
+ " versionCode=73000302 minSdk=10000 targetSdk=10000",
+ " versionName=7.3 (3821978)",
+ " splits=[base]",
+ " Package [com.google.android.googlequicksearchbox] (607929e):",
+ " userId=10037",
+ " pkg=Package{af43294 com.google.android.googlequicksearchbox}",
+ " versionCode=300734793 minSdk=10000 targetSdk=10000",
+ " versionName=6.16.35.26.arm64",
+ " apkSigningVersion=2");
+
+ final DumpsysPackageStatsItem packagestats =
+ new DumpsysPackageStatsParser().parse(inputBlock);
+ assertEquals(2, packagestats.size());
+ assertNotNull(packagestats.get("com.google.android.calculator"));
+ final AppVersionItem calculator = packagestats.get("com.google.android.calculator");
+ assertEquals(73000302, calculator.getVersionCode());
+ assertEquals("7.3 (3821978)", calculator.getVersionName());
+ assertNotNull(packagestats.get("com.google.android.googlequicksearchbox"));
+ final AppVersionItem googlequicksearchbox =
+ packagestats.get("com.google.android.googlequicksearchbox");
+ assertEquals(300734793, googlequicksearchbox.getVersionCode());
+ assertEquals("6.16.35.26.arm64", googlequicksearchbox.getVersionName());
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/DumpsysParserTest.java b/javatests/com/android/loganalysis/parser/DumpsysParserTest.java
new file mode 100644
index 0000000..020c724
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/DumpsysParserTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2015 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.loganalysis.parser;
+
+import com.android.loganalysis.item.DumpsysItem;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link DumpsysParser}
+ */
+public class DumpsysParserTest extends TestCase {
+
+ /**
+ * Test that normal input is parsed.
+ */
+ public void testDumpsysParser() {
+ List<String> inputBlock =
+ Arrays.asList(
+ "DUMP OF SERVICE batterystats:",
+ "Battery History (37% used, 95KB used of 256KB, 166 strings using 15KB):",
+ " 0 (9) RESET:TIME: 2014-12-09-11-33-29",
+ " +1s067ms (1) 100 c0500020 -wifi_full_lock -wifi_scan",
+ " +3s297ms (2) 100 80400020 -wake_lock -screen",
+ " +30m02s075ms (1) 100 c0500020 wifi_signal_strength=4 wifi_suppl=completed",
+ " +30m03s012ms (2) 099 c0500020 temp=306 volt=4217",
+ " +33m48s967ms (1) 099 f8400020 +wifi_scan",
+ " +33m49s335ms (2) 098 f0400020 temp=324 -wifi_scan",
+ "Statistics since last charge:",
+ " Time on battery: 2h 21m 5s 622ms (12.0%) realtime, 7m 54s 146ms (0.7%) uptime",
+ " Time on battery screen off: 2h 5m 55s 3ms (1%) realtime, 7m 4s 5ms (7%) uptime",
+ " Total run time: 19h 38m 43s 650ms realtime, 17h 25m 32s 175ms uptime",
+ " All kernel wake locks:",
+ " Kernel Wake lock PowerManagerService.WakeLocks: 1h 3m 50s 5ms (8 times) realtime",
+ " Kernel Wake lock event0-2656 : 3m 49s 268ms (2399 times) realtime",
+ " Kernel Wake lock wlan_wd_wake: 3m 34s 639ms (1751 times) realtime",
+ " Kernel Wake lock wlan_rx_wake: 3m 19s 887ms (225 times) realtime",
+ " Kernel Wake lock wlan_tx_wake: 2m 19s 887ms (225 times) realtime",
+ " Kernel Wake lock tx_wake: 1m 19s 887ms (225 times) realtime",
+ " ",
+ " All partial wake locks:",
+ " Wake lock u0a7 NlpWakeLock: 8m 13s 203ms (1479 times) realtime",
+ " Wake lock u0a7 NlpCollectorWakeLock: 6m 29s 18ms (238 times) realtime",
+ " Wake lock u0a7 GCM_CONN_ALARM: 6m 8s 587ms (239 times) realtime",
+ " Wake lock 1000 *alarm*: 5m 11s 316ms (1469 times) realtime",
+ " Wake lock u10 xxx: 4m 11s 316ms (1469 times) realtime",
+ " Wake lock u30 cst: 2m 11s 316ms (1469 times) realtime",
+ " ",
+ " All wakeup reasons:",
+ " Wakeup reason 200:qcom,smd-rpm:222:fc4: 11m 49s 332ms (0 times) realtime",
+ " Wakeup reason 200:qcom,smd-rpm: 48s 45ms (0 times) realtime",
+ " Wakeup reason 2:qcom,smd-rpm:2:f0.qm,mm:22:fc4mi: 3s 417ms (0 times) realtime",
+ " Wakeup reason 188:qcom,smd-adsp:200:qcom,smd-rpm: 1s 656ms (0 times) realtime",
+ " Wakeup reason 58:qcom,smsm-modem:2:qcom,smd-rpm: 6m 16s 1ms (5 times) realtime",
+ " Wakeup reason 57:qcom,smd-modem:200:qcom,smd-rpm: 40s 995ms (0 times) realtime",
+ " Wakeup reason unknown: 8s 455ms (0 times) realtime",
+ " Wakeup reason 9:bcmsdh_sdmmc:2:qcomd-rpm:240:mso: 8m 5s 9ms (0 times) realtime",
+ " ",
+ " 0:",
+ " User activity: 2 other",
+ " Wake lock SCREEN_FROZEN realtime",
+ " Sensor 0: 9s 908ms realtime (1 times)",
+ " Sensor 1: 9s 997ms realtime (1 times)",
+ " Foreground for: 2h 21m 5s 622ms",
+ " Apk android:",
+ " 24 wakeup alarms",
+ " u0a9:",
+ " Mobile network: 8.1KB received, 1.6KB sent (packets 291 received, 342 sent)",
+ " Mobile radio active: 3m 43s 890ms (34.2%) 39x @ 354 mspp",
+ " Sensor 2: 12m 13s 15ms realtime (5 times)",
+ " Sensor 32: (not used)",
+ " Sensor 35: (not used)",
+ "DUMP OF SERVICE package:",
+ "Package [com.google.android.calculator] (e075c9d):",
+ " userId=10071",
+ " secondaryCpuAbi=null",
+ " versionCode=73000302 minSdk=10000 targetSdk=10000",
+ " versionName=7.3 (3821978)",
+ " splits=[base]",
+ "Package [com.google.android.googlequicksearchbox] (607929e):",
+ " userId=10037",
+ " pkg=Package{af43294 com.google.android.googlequicksearchbox}",
+ " versionCode=300734793 minSdk=10000 targetSdk=10000",
+ " versionName=6.16.35.26.arm64",
+ " apkSigningVersion=2",
+ "DUMP OF SERVICE procstats:",
+ "COMMITTED STATS FROM 2015-03-20-02-01-02 (checked in):",
+ " * com.android.systemui / u0a22 / v22:",
+ " TOTAL: 100% (159MB-160MB-161MB/153MB-153MB-154MB over 13)",
+ " Persistent: 100% (159MB-160MB-161MB/153MB-153MB-154MB over 13)",
+ " * com.google.process.gapps / u0a9 / v22:",
+ " TOTAL: 100% (22MB-24MB-25MB/18MB-19MB-20MB over 13)",
+ " Imp Fg: 100% (22MB-24MB-25MB/18MB-19MB-20MB over 13)",
+ "DUMP OF SERVICE wifi:",
+ "Wi-Fi is enabled",
+ "rec[0]: time=10-09 00:25:16.737 processed=DefaultState org=DeviceActiveState "
+ + "dest=<null> what=155654(0x26006)",
+ "mScreenOff true",
+ " rec[0]: time=10-08 16:49:55.034 processed=WatchdogEnabledState org=OnlineState "
+ + "dest=OnlineWatchState what=135170(0x21002)",
+ "rec[30]: time=10-08 13:06:50.379 processed=DriverStartedState org=ConnectedState "
+ + "dest=<null> what=131143(0x20047) (1)CMD_START_SCAN rt=4720806/7884852 10013 51 "
+ + "ic=0 proc(ms):14 onGoing full started:1444334810368,11 cnt=24 rssi=-127 f=-1 "
+ + "sc=0 link=-1 tx=1.5, 1.7, 0.0 rx=8.4 fiv=20000 [on:3266 tx:65 rx:556 "
+ + "period:1268] from screen [on:266962 period:266959]",
+ "rec[357]: time=10-08 13:10:13.199 processed=DriverStartedState org=ConnectedState "
+ + "dest=<null> what=131143(0x20047) (-2)CMD_START_SCAN rt=4923625/8087671 10013 "
+ + "175 ic=0 proc(ms):2 onGoing full started:1444335011199,1999 cnt=24 rssi=-127 "
+ + "f=-1 sc=0 link=-1 tx=8.4, 1.7, 0.0 rx=6.7 fiv=20000 [on:0 tx:0 rx:0 period:990]"
+ + "from screen [on:467747 period:469779]",
+ "WifiConfigStore - Log Begin ----",
+ "10-08 11:09:14.653 - Event [IFNAME=wlan0 CTRL-EVENT-SCAN-STARTED ]",
+ "10-08 13:06:29.489 - Event [IFNAME=wlan0 CTRL-EVENT-DISCONNECTED "
+ + "bssid=9c:1c:12:c3:d0:72 reason=0]",
+ "10-08 13:06:22.280 - Event [IFNAME=wlan0 CTRL-EVENT-SCAN-STARTED ]",
+ "10-08 13:06:25.363 - Event [IFNAME=wlan0 CTRL-EVENT-SCAN-STARTED ]",
+ "10-08 13:08:15.018 - Event [IFNAME=wlan0 CTRL-EVENT-DISCONNECTED "
+ + "bssid=9c:1c:12:e8:72:d2 reason=3 locally_generated=1]",
+ "10-08 13:08:15.324 - wlan0: 442:IFNAME=wlan0 ENABLE_NETWORK 0 -> true");
+
+ DumpsysItem dumpsys = new DumpsysParser().parse(inputBlock);
+ assertNotNull(dumpsys.getBatteryStats());
+ assertNotNull(dumpsys.getPackageStats());
+ assertNotNull(dumpsys.getProcStats());
+ assertNotNull(dumpsys.getWifiStats());
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/DumpsysProcStatsParserTest.java b/javatests/com/android/loganalysis/parser/DumpsysProcStatsParserTest.java
new file mode 100644
index 0000000..27bb7a4
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/DumpsysProcStatsParserTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 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.loganalysis.parser;
+
+import com.android.loganalysis.item.DumpsysProcStatsItem;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link DumpsysProcStatsParser}
+ */
+public class DumpsysProcStatsParserTest extends TestCase {
+
+ /**
+ * Test that normal input is parsed.
+ */
+ public void testDumpsysProcStatsParser() {
+ List<String> inputBlock = Arrays.asList(
+ "COMMITTED STATS FROM 2015-03-20-02-01-02 (checked in):",
+ " * com.android.systemui / u0a22 / v22:",
+ " TOTAL: 100% (159MB-160MB-161MB/153MB-153MB-154MB over 13)",
+ " Persistent: 100% (159MB-160MB-161MB/153MB-153MB-154MB over 13)",
+ " * com.google.process.gapps / u0a9 / v22:",
+ " TOTAL: 100% (22MB-24MB-25MB/18MB-19MB-20MB over 13)",
+ " Imp Fg: 100% (22MB-24MB-25MB/18MB-19MB-20MB over 13)");
+
+ DumpsysProcStatsItem procstats = new DumpsysProcStatsParser().parse(inputBlock);
+ assertEquals("com.android.systemui", procstats.get("u0a22"));
+ assertEquals("com.google.process.gapps", procstats.get("u0a9"));
+ }
+}
+
diff --git a/javatests/com/android/loganalysis/parser/DumpsysProcessMeminfoParserTest.java b/javatests/com/android/loganalysis/parser/DumpsysProcessMeminfoParserTest.java
new file mode 100644
index 0000000..b7042d3
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/DumpsysProcessMeminfoParserTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2018 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.loganalysis.parser;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.loganalysis.item.DumpsysProcessMeminfoItem;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** Unit tests for {@link DumpsysProcessMeminfoParser} */
+public class DumpsysProcessMeminfoParserTest {
+ private static final String TEST_INPUT =
+ "time,28506638,177086152\n"
+ + "4,938,system_server,11,22,N/A,44,0,0,N/A,0,0,0,N/A,0,27613,14013,176602,"
+ + "218228,0,0,122860,122860,1512,1412,5740,8664,0,0,154924,154924,27568,"
+ + "13972,11916,53456,0,0,123008,123008,0,0,0,0,0,0,0,0,Dalvik Other,3662,0,"
+ + "104,0,3660,0,0,0,Stack,1576,0,8,0,1576,0,0,0,Cursor,0,0,0,0,0,0,0,0,"
+ + "Ashmem,156,0,20,0,148,0,0,0,Gfx dev,100,0,48,0,76,0,0,0,Other dev,116,0,"
+ + "164,0,0,96,0,0,.so mmap,7500,2680,3984,21864,904,2680,0,0,.jar mmap,0,0,0,"
+ + "0,0,0,0,0,.apk mmap,72398,71448,0,11736,0,71448,0,0,.ttf mmap,0,0,0,0,0,0,"
+ + "0,0,.dex mmap,76874,46000,0,83644,40,46000,0,0,.oat mmap,8127,2684,64,"
+ + "26652,0,2684,0,0,.art mmap,1991,48,972,10004,1544,48,0,0,Other mmap,137,0,"
+ + "44,1024,4,52,0,0,EGL mtrack,0,0,0,0,0,0,0,0,GL mtrack,111,222,333,444,555,"
+ + "666,777,888,";
+
+ private static final String INVALID_TEST_INPUT = "RANDOM,TEST,DATA\n234235345345";
+
+ // Test that normal input is parsed
+ @Test
+ public void testDumpsysProcessMeminfoParser() {
+ List<String> inputBlock = Arrays.asList(TEST_INPUT.split("\n"));
+ DumpsysProcessMeminfoItem dump = new DumpsysProcessMeminfoParser().parse(inputBlock);
+ assertEquals(938, dump.getPid());
+ assertEquals("system_server", dump.getProcessName());
+ assertEquals(
+ Long.valueOf(11L),
+ dump.get(DumpsysProcessMeminfoItem.NATIVE).get(DumpsysProcessMeminfoItem.MAX));
+ assertEquals(
+ Long.valueOf(22L),
+ dump.get(DumpsysProcessMeminfoItem.DALVIK).get(DumpsysProcessMeminfoItem.MAX));
+ assertFalse(
+ dump.get(DumpsysProcessMeminfoItem.OTHER)
+ .containsKey(DumpsysProcessMeminfoItem.MAX));
+ assertEquals(
+ Long.valueOf(44L),
+ dump.get(DumpsysProcessMeminfoItem.TOTAL).get(DumpsysProcessMeminfoItem.MAX));
+ assertEquals(
+ Long.valueOf(218228L),
+ dump.get(DumpsysProcessMeminfoItem.TOTAL).get(DumpsysProcessMeminfoItem.PSS));
+ assertEquals(
+ Long.valueOf(3662L), dump.get("Dalvik Other").get(DumpsysProcessMeminfoItem.PSS));
+ assertEquals(Long.valueOf(111L), dump.get("GL mtrack").get(DumpsysProcessMeminfoItem.PSS));
+ assertEquals(
+ Long.valueOf(222L),
+ dump.get("GL mtrack").get(DumpsysProcessMeminfoItem.SWAPPABLE_PSS));
+ assertEquals(
+ Long.valueOf(333L),
+ dump.get("GL mtrack").get(DumpsysProcessMeminfoItem.SHARED_DIRTY));
+ assertEquals(
+ Long.valueOf(444L),
+ dump.get("GL mtrack").get(DumpsysProcessMeminfoItem.SHARED_CLEAN));
+ assertEquals(
+ Long.valueOf(555L),
+ dump.get("GL mtrack").get(DumpsysProcessMeminfoItem.PRIVATE_DIRTY));
+ assertEquals(
+ Long.valueOf(666L),
+ dump.get("GL mtrack").get(DumpsysProcessMeminfoItem.PRIVATE_CLEAN));
+ assertEquals(
+ Long.valueOf(777L),
+ dump.get("GL mtrack").get(DumpsysProcessMeminfoItem.SWAPPED_OUT));
+ assertEquals(
+ Long.valueOf(888L),
+ dump.get("GL mtrack").get(DumpsysProcessMeminfoItem.SWAPPED_OUT_PSS));
+ }
+
+ // Test that the parser does not crash on invalid input and returns empty data
+ @Test
+ public void testDumpsysProcessMeminfoParserInvalid() {
+ List<String> inputBlock = Arrays.asList(INVALID_TEST_INPUT.split("\n"));
+ DumpsysProcessMeminfoItem dump = new DumpsysProcessMeminfoParser().parse(inputBlock);
+ assertNull(dump.getProcessName());
+ assertTrue(dump.get(DumpsysProcessMeminfoItem.TOTAL).isEmpty());
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/DumpsysWifiStatsParserTest.java b/javatests/com/android/loganalysis/parser/DumpsysWifiStatsParserTest.java
new file mode 100644
index 0000000..f52e432
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/DumpsysWifiStatsParserTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2016 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.loganalysis.parser;
+
+import com.android.loganalysis.item.DumpsysWifiStatsItem;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link DumpsysWifiStatsParser}
+ */
+public class DumpsysWifiStatsParserTest extends TestCase {
+
+ /**
+ * Test that normal input is parsed.
+ */
+ public void testDumpsysWifiStatsParser() {
+ List<String> inputBlock = Arrays.asList(
+ "Wi-Fi is enabled",
+ "rec[0]: time=10-09 00:25:16.737 processed=DefaultState org=DeviceActiveState "
+ + "dest=<null> what=155654(0x26006)",
+ "mScreenOff true",
+ " rec[0]: time=10-08 16:49:55.034 processed=WatchdogEnabledState org=OnlineState "
+ + "dest=OnlineWatchState what=135170(0x21002)",
+ "rec[30]: time=10-08 13:06:50.379 processed=DriverStartedState org=ConnectedState "
+ + "dest=<null> what=131143(0x20047) (1)CMD_START_SCAN rt=4720806/7884852 10013 51 "
+ + "ic=0 proc(ms):14 onGoing full started:1444334810368,11 cnt=24 rssi=-127 f=-1 "
+ + "sc=0 link=-1 tx=1.5, 1.7, 0.0 rx=8.4 fiv=20000 [on:3266 tx:65 rx:556 "
+ + "period:1268] from screen [on:266962 period:266959]",
+ "rec[357]: time=10-08 13:10:13.199 processed=DriverStartedState org=ConnectedState "
+ + "dest=<null> what=131143(0x20047) (-2)CMD_START_SCAN rt=4923625/8087671 10013 "
+ + "175 ic=0 proc(ms):2 onGoing full started:1444335011199,1999 cnt=24 rssi=-127 "
+ + "f=-1 sc=0 link=-1 tx=8.4, 1.7, 0.0 rx=6.7 fiv=20000 [on:0 tx:0 rx:0 period:990] "
+ + "from screen [on:467747 period:469779]",
+ "WifiConfigStore - Log Begin ----",
+ "10-08 11:09:14.653 - Event [IFNAME=wlan0 CTRL-EVENT-SCAN-STARTED ]",
+ "10-08 13:06:29.489 - Event [IFNAME=wlan0 CTRL-EVENT-DISCONNECTED "
+ + "bssid=9c:1c:12:c3:d0:72 reason=0]",
+ "10-08 13:06:22.280 - Event [IFNAME=wlan0 CTRL-EVENT-SCAN-STARTED ]",
+ "10-08 13:06:25.363 - Event [IFNAME=wlan0 CTRL-EVENT-SCAN-STARTED ]",
+ "10-08 13:08:15.018 - Event [IFNAME=wlan0 CTRL-EVENT-DISCONNECTED "
+ + "bssid=9c:1c:12:e8:72:d2 reason=3 locally_generated=1]",
+ "10-08 13:08:15.324 - wlan0: 442:IFNAME=wlan0 ENABLE_NETWORK 0 -> true",
+ "01-21 13:17:23.1 - Event [IFNAME=wlan0 Trying to associate with SSID 'WL-power']",
+ "01-21 13:18:23.1 - Event [IFNAME=wlan0 Trying to associate with SSID 'WL-power']",
+ "01-21 13:18:23.1 - Event [IFNAME=wlan0 Trying to associate with SSID 'WL-power']");
+
+ DumpsysWifiStatsItem wifiStats = new DumpsysWifiStatsParser().parse(inputBlock);
+ assertEquals(2, wifiStats.getNumWifiDisconnects());
+ assertEquals(3, wifiStats.getNumWifiScans());
+ assertEquals(3, wifiStats.getNumWifiAssociations());
+ }
+
+ /**
+ * Test that input with no wifi disconnect and wifi scans.
+ */
+ public void testDumpsysWifiStatsParser_no_wifi_scan_disconnect() {
+ List<String> inputBlock = Arrays.asList(
+ "Wi-Fi is enabled",
+ "rec[0]: time=10-09 00:25:16.737 processed=DefaultState org=DeviceActiveState "
+ + "dest=<null> what=155654(0x26006)",
+ "mScreenOff true",
+ " rec[0]: time=10-08 16:49:55.034 processed=WatchdogEnabledState org=OnlineState "
+ + "dest=OnlineWatchState what=135170(0x21002)",
+ "rec[30]: time=10-08 13:06:50.379 processed=DriverStartedState org=ConnectedState "
+ + "dest=<null> what=131143(0x20047) (1)CMD_START_SCAN rt=4720806/7884852 10013 51 "
+ + "ic=0 proc(ms):14 onGoing full started:1444334810368,11 cnt=24 rssi=-127 f=-1 "
+ + "sc=0 link=-1 tx=1.5, 1.7, 0.0 rx=8.4 fiv=20000 [on:3266 tx:65 rx:556 "
+ + "period:1268] from screen [on:266962 period:266959]",
+ "rec[357]: time=10-08 13:10:13.199 processed=DriverStartedState org=ConnectedState "
+ + "dest=<null> what=131143(0x20047) (-2)CMD_START_SCAN rt=4923625/8087671 10013 "
+ + "175 ic=0 proc(ms):2 onGoing full started:1444335011199,1999 cnt=24 rssi=-127 "
+ + "f=-1 sc=0 link=-1 tx=8.4, 1.7, 0.0 rx=6.7 fiv=20000 [on:0 tx:0 rx:0 period:990]"
+ + "from screen [on:467747 period:469779]",
+ "WifiConfigStore - Log Begin ----",
+ "10-08 13:07:37.777 - wlan0: 384:IFNAME=wlan0 ENABLE_NETWORK 4 -> true",
+ "10-08 13:07:37.789 - wlan0: 388:IFNAME=wlan0 SAVE_CONFIG -> true",
+ "10-08 13:08:15.065 - wlan0: 427:IFNAME=wlan0 SET ps 1 -> true",
+ "10-08 13:08:15.179 - wlan0: 432:IFNAME=wlan0 SET pno 1 -> true");
+
+ DumpsysWifiStatsItem wifiStats = new DumpsysWifiStatsParser().parse(inputBlock);
+ assertEquals(0, wifiStats.getNumWifiDisconnects());
+ assertEquals(0, wifiStats.getNumWifiScans());
+ assertEquals(0, wifiStats.getNumWifiAssociations());
+ }
+}
+
diff --git a/javatests/com/android/loganalysis/parser/DvmLockSampleParserTest.java b/javatests/com/android/loganalysis/parser/DvmLockSampleParserTest.java
new file mode 100644
index 0000000..cb85999
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/DvmLockSampleParserTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 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.loganalysis.parser;
+
+import com.android.loganalysis.item.DvmLockSampleItem;
+import com.android.loganalysis.parser.DvmLockSampleParser;
+
+import junit.framework.TestCase;
+import java.util.Arrays;
+import java.util.List;
+
+public class DvmLockSampleParserTest extends TestCase {
+
+ /**
+ * Sanity-check our DVM lock line parser against a single log line
+ */
+ public void testSingleDvmLine() {
+ List<String> input = Arrays.asList(
+ "[android.support.test.aupt,0,Instr: android.support.test.aupt,75," +
+ "AccessibilityCache.java,256,-,96,15]");
+
+ DvmLockSampleItem item = new DvmLockSampleParser().parse(input);
+ assertEquals("android.support.test.aupt", item.getAttribute(DvmLockSampleItem.PROCESS_NAME));
+ assertEquals(Boolean.FALSE, item.getAttribute(DvmLockSampleItem.SENSITIVITY_FLAG));
+ assertEquals("Instr: android.support.test.aupt", item.getAttribute(DvmLockSampleItem.WAITING_THREAD_NAME));
+ assertEquals(75, item.getAttribute(DvmLockSampleItem.WAIT_TIME));
+ assertEquals("AccessibilityCache.java", item.getAttribute(DvmLockSampleItem.WAITING_SOURCE_FILE));
+ assertEquals(256, item.getAttribute(DvmLockSampleItem.WAITING_SOURCE_LINE));
+ assertEquals("AccessibilityCache.java", item.getAttribute(DvmLockSampleItem.OWNER_FILE_NAME));
+ assertEquals(96, item.getAttribute(DvmLockSampleItem.OWNER_ACQUIRE_SOURCE_LINE));
+ assertEquals(15, item.getAttribute(DvmLockSampleItem.SAMPLE_PERCENTAGE));
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/EventsLogParserTest.java b/javatests/com/android/loganalysis/parser/EventsLogParserTest.java
new file mode 100644
index 0000000..6956d16
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/EventsLogParserTest.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2016 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.loganalysis.parser;
+
+import com.android.loganalysis.item.LatencyItem;
+import com.android.loganalysis.item.TransitionDelayItem;
+
+import junit.framework.TestCase;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link EventsLogParser}.
+ */
+public class EventsLogParserTest extends TestCase {
+
+ private File mTempFile = null;
+
+ @Override
+ protected void tearDown() throws Exception {
+ mTempFile.delete();
+ }
+
+ /**
+ * Test for empty events logs passed to the transition delay parser
+ */
+ public void testEmptyEventsLog() throws IOException {
+ List<String> lines = Arrays.asList("");
+ List<TransitionDelayItem> transitionItems =
+ new EventsLogParser().parseTransitionDelayInfo(readInputBuffer(getTempFile(lines)));
+ assertEquals("Transition Delay items list should be empty", 0,transitionItems.size());
+ }
+
+ /**
+ * Test for no transition delay info in the events log
+ */
+ public void testNoTransitionDelayInfo() throws IOException {
+ List<String> lines = Arrays
+ .asList(
+ "08-25 12:56:15.850 1152 8968 I am_focused_stack: [0,0,1,appDied setFocusedActivity]",
+ "08-25 12:56:15.850 1152 8968 I wm_task_moved: [6,1,1]",
+ "08-25 12:56:15.852 1152 8968 I am_focused_activity: [0,com.google.android.apps.nexuslauncher/.NexusLauncherActivity,appDied]",
+ "08-25 12:56:15.852 1152 8968 I wm_task_removed: [27,removeTask]",
+ "08-25 12:56:15.852 1152 8968 I wm_stack_removed: 1");
+ List<TransitionDelayItem> transitionItems =
+ new EventsLogParser().parseTransitionDelayInfo(readInputBuffer(getTempFile(lines)));
+ assertEquals("Transition Delay items list should be empty", 0,
+ transitionItems.size());
+ }
+
+ /**
+ * Test for Cold launch transition delay and starting window delay info
+ */
+ public void testValidColdTransitionDelay() throws IOException {
+ List<String> lines = Arrays
+ .asList("09-18 23:56:19.376 1140 1221 I sysui_multi_action: [319,51,321,50,322,190,325,670,757,761,758,7,759,1,806,com.google.android.calculator,871,com.android.calculator2.Calculator,904,com.google.android.apps.nexuslauncher,905,0,945,41]",
+ "09-18 23:56:19.376 1140 1221 I sysui_multi_action: [319,51,321,50,322,190,325,670,757,761,758,7,759,1,806,com.google.android.calculator,871,com.android.calculator2.Calculator,905,0,945,41]");
+ List<TransitionDelayItem> transitionItems =
+ new EventsLogParser().parseTransitionDelayInfo(readInputBuffer(getTempFile(lines)));
+ assertEquals("Startinng Window Delay items list should have two item", 2,
+ transitionItems.size());
+ assertEquals("Component name not parsed correctly",
+ "com.google.android.calculator/com.android.calculator2.Calculator",
+ transitionItems.get(0).getComponentName());
+ assertEquals("Transition delay is not parsed correctly", Long.valueOf(51),
+ transitionItems.get(0).getTransitionDelay());
+ assertEquals("Starting window delay is not parsed correctly", Long.valueOf(50),
+ transitionItems.get(0).getStartingWindowDelay());
+ assertEquals("Date and time is not parsed correctly", "09-18 23:56:19.376",
+ transitionItems.get(0).getDateTime());
+ }
+
+ /**
+ * Test for Hot launch transition delay and starting window delay info
+ */
+ public void testValidHotTransitionDelay() throws IOException {
+ List<String> lines = Arrays
+ .asList("09-18 23:56:19.376 1140 1221 I sysui_multi_action: [319,51,321,50,322,190,325,670,757,761,758,7,759,1,806,com.google.android.calculator,871,com.android.calculator2.Calculator,904,com.google.android.apps.nexuslauncher,905,0]",
+ "09-18 23:56:19.376 1140 1221 I sysui_multi_action: [319,51,321,50,322,190,325,670,757,761,758,7,759,1,806,com.google.android.calculator,871,com.android.calculator2.Calculator,905,0]",
+ "09-19 02:26:30.182 1143 1196 I sysui_multi_action: [319,87,322,75,325,212,757,761,758,9,759,2,806,com.google.android.apps.nexuslauncher,871,com.google.android.apps.nexuslauncher.NexusLauncherActivity,904,com.google.android.apps.nexuslauncher,905,0]",
+ "09-19 02:26:30.182 1143 1196 I sysui_multi_action: [319,87,322,75,325,212,757,761,758,9,759,2,806,com.google.android.apps.nexuslauncher,871,com.google.android.apps.nexuslauncher.NexusLauncherActivity,905,0]");
+ List<TransitionDelayItem> transitionItems =
+ new EventsLogParser().parseTransitionDelayInfo(readInputBuffer(getTempFile(lines)));
+ assertEquals("Transition Delay items list should have four item", 4,
+ transitionItems.size());
+ assertEquals("Component name not parsed correctly",
+ "com.google.android.calculator/com.android.calculator2.Calculator",
+ transitionItems.get(0).getComponentName());
+ assertEquals("Transition delay is not parsed correctly", Long.valueOf(51),
+ transitionItems.get(0).getTransitionDelay());
+ assertEquals("Date is not parsed correctly", "09-18 23:56:19.376",
+ transitionItems.get(0).getDateTime());
+ }
+
+ /**
+ * Test for same app transition delay items order after parsing from the events log
+ */
+ public void testTransitionDelayOrder() throws IOException {
+ List<String> lines = Arrays
+ .asList("09-18 23:56:19.376 1140 1221 I sysui_multi_action: [319,51,321,59,322,190,325,670,757,761,758,7,759,1,806,com.google.android.calculator,871,com.android.calculator2.Calculator,904,com.google.android.apps.nexuslauncher,905,0,945,41]",
+ "09-18 23:59:18.380 1140 1221 I sysui_multi_action: [319,55,321,65,322,190,325,670,757,761,758,7,759,1,806,com.google.android.calculator,871,com.android.calculator2.Calculator,905,0,945,41]");
+ List<TransitionDelayItem> transitionItems =
+ new EventsLogParser().parseTransitionDelayInfo(readInputBuffer(getTempFile(lines)));
+ assertEquals("Transition Delay items list should have two items", 2,
+ transitionItems.size());
+ assertEquals("Transition delay for the first item is not set correct", Long.valueOf(59),
+ transitionItems.get(0).getStartingWindowDelay());
+ assertEquals("Transition delay for the second item is not set correct", Long.valueOf(65),
+ transitionItems.get(1).getStartingWindowDelay());
+ }
+
+ /**
+ * Test for two different different apps transition delay items
+ */
+ public void testDifferentAppTransitionDelay() throws IOException {
+ List<String> lines = Arrays
+ .asList("09-18 23:56:19.376 1140 1221 I sysui_multi_action: [319,51,321,50,322,190,325,670,757,761,758,7,759,1,806,com.google.android.calculator,871,com.android.calculator2.Calculator,904,com.google.android.apps.nexuslauncher,905,0]",
+ "09-19 02:26:30.182 1143 1196 I sysui_multi_action: [319,87,322,75,325,212,757,761,758,9,759,2,806,com.google.android.apps.nexuslauncher,871,com.google.android.apps.nexuslauncher.NexusLauncherActivity,904,com.google.android.apps.nexuslauncher,905,0]");
+ List<TransitionDelayItem> transitionItems =
+ new EventsLogParser().parseTransitionDelayInfo(readInputBuffer(getTempFile(lines)));
+ assertEquals("Transition Delay items list should have two items", 2,
+ transitionItems.size());
+ assertEquals("Calculator is not the first transition delay item",
+ "com.google.android.calculator/com.android.calculator2.Calculator",
+ transitionItems.get(0).getComponentName());
+ assertEquals("Maps is not the second transition delay item",
+ "com.google.android.apps.nexuslauncher/"
+ + "com.google.android.apps.nexuslauncher.NexusLauncherActivity",
+ transitionItems.get(1).getComponentName());
+ }
+
+ /**
+ * Test for invalid transition delay items pattern having different code.
+ */
+ public void testInvalidTransitionPattern() throws IOException {
+ List<String> lines = Arrays
+ .asList("01-02 08:11:58.691 934 986 I sysui_multi_action: a[319,48,322,82,325,84088,757,761,758,9,759,4,807,com.google.android.calculator,871,com.android.calculator2.Calculator,905,0]",
+ "01-02 08:12:03.639 934 970 I sysui_multi_action: [757,803,799,window_time_0,802,5]",
+ "01-02 08:12:10.849 934 986 I sysui_multi_action: 319,42,321,59,322,208,325,84100,757,761,758,9,759,4,806,com.google.android.apps.maps,871,com.google.android.maps.MapsActivity,905,0]",
+ "01-02 08:12:16.895 1446 1446 I sysui_multi_action: [757,803,799,overview_trigger_nav_btn,802,1]",
+ "01-02 08:12:16.895 1446 1446 I sysui_multi_action: [757,803,799,overview_source_app,802,1]",
+ "01-02 08:12:16.895 1446 1446 I sysui_multi_action: [757,804,799,overview_source_app_index,801,8,802,1]");
+ List<TransitionDelayItem> transitionItems =
+ new EventsLogParser().parseTransitionDelayInfo(readInputBuffer(getTempFile(lines)));
+ assertEquals("Transition Delay items list should be empty", 0,
+ transitionItems.size());
+ }
+
+ /**
+ * Test for valid latency item
+ */
+ public void testValidLatencyInfo() throws IOException {
+ List<String> lines = Arrays
+ .asList("08-25 13:01:19.412 1152 9031 I am_restart_activity: [com.google.android.gm/.ConversationListActivityGmail,0,85290699,38]",
+ "08-25 13:01:19.437 1152 1226 I sysui_action: [321,85]",
+ "08-25 13:01:19.437 1152 1226 I sysui_action: [320,1]",
+ "08-25 13:01:19.437 1152 1226 I sysui_action: [319,85]",
+ "08-25 12:56:15.850 1152 8968 I am_focused_stack: [0,0,1,appDied setFocusedActivity]",
+ "09-19 11:53:16.893 1080 1160 I sysui_latency: [1,50]");
+ List<LatencyItem> latencyItems =
+ new EventsLogParser().parseLatencyInfo(readInputBuffer(getTempFile(lines)));
+ assertEquals("One latency item should present in the list", 1, latencyItems.size());
+ assertEquals("Action Id is not correct", 1, latencyItems.get(0).getActionId());
+ assertEquals("Delay is not correct", 50L, latencyItems.get(0).getDelay());
+ }
+
+ /**
+ * Test for empty delay info
+ */
+ public void testInvalidLatencyInfo() throws IOException {
+ List<String> lines = Arrays
+ .asList("08-25 13:01:19.412 1152 9031 I am_restart_activity: [com.google.android.gm/.ConversationListActivityGmail,0,85290699,38]",
+ "08-25 13:01:19.437 1152 1226 I sysui_action: [321,85]",
+ "08-25 13:01:19.437 1152 1226 I sysui_action: [320,1]",
+ "08-25 13:01:19.437 1152 1226 I sysui_action: [319,85]",
+ "08-25 12:56:15.850 1152 8968 I am_focused_stack: [0,0,1,appDied setFocusedActivity]",
+ "09-19 11:53:16.893 1080 1160 I sysui_latency: [1]");
+ List<LatencyItem> latencyItems =
+ new EventsLogParser().parseLatencyInfo(readInputBuffer(getTempFile(lines)));
+ assertEquals("Latency items list should be empty", 0, latencyItems.size());
+ }
+
+ /**
+ * Test for empty latency info
+ */
+ public void testEmptyLatencyInfo() throws IOException {
+ List<String> lines = Arrays
+ .asList("08-25 13:01:19.412 1152 9031 I am_restart_activity: [com.google.android.gm/.ConversationListActivityGmail,0,85290699,38]",
+ "08-25 13:01:19.437 1152 1226 I sysui_action: [321,85]",
+ "08-25 13:01:19.437 1152 1226 I sysui_action: [320,1]",
+ "08-25 13:01:19.437 1152 1226 I sysui_action: [319,85]",
+ "08-25 12:56:15.850 1152 8968 I am_focused_stack: [0,0,1,appDied setFocusedActivity]",
+ "09-19 11:53:16.893 1080 1160 I sysui_latency: []");
+ List<LatencyItem> latencyItems =
+ new EventsLogParser().parseLatencyInfo(readInputBuffer(getTempFile(lines)));
+ assertEquals("Latency items list should be empty", 0, latencyItems.size());
+ }
+
+
+ /**
+ * Test for order of the latency items
+ */
+ public void testLatencyInfoOrder() throws IOException {
+ List<String> lines = Arrays
+ .asList("09-19 11:53:16.893 1080 1160 I sysui_latency: [1,50]",
+ "08-25 13:01:19.437 1152 1226 I sysui_action: [321,85]",
+ "08-25 13:01:19.437 1152 1226 I sysui_action: [320,1]",
+ "08-25 13:01:19.437 1152 1226 I sysui_action: [319,85]",
+ "08-25 12:56:15.850 1152 8968 I am_focused_stack: [0,0,1,appDied setFocusedActivity]",
+ "09-19 11:53:16.893 1080 1160 I sysui_latency: [2,100]");
+ List<LatencyItem> latencyItems =
+ new EventsLogParser().parseLatencyInfo(readInputBuffer(getTempFile(lines)));
+ assertEquals("Latency list should have 2 items", 2, latencyItems.size());
+ assertEquals("First latency id is not 1", 1, latencyItems.get(0).getActionId());
+ assertEquals("Second latency id is not 2", 2, latencyItems.get(1).getActionId());
+ }
+
+ /**
+ * Write list of strings to file and use it for testing.
+ */
+ public File getTempFile(List<String> sampleEventsLogs) throws IOException {
+ mTempFile = File.createTempFile("events_logcat", ".txt");
+ BufferedWriter out = new BufferedWriter(new FileWriter(mTempFile));
+ for (String line : sampleEventsLogs) {
+ out.write(line);
+ out.newLine();
+ }
+ out.close();
+ return mTempFile;
+ }
+
+ /**
+ * Reader to read the input from the given temp file
+ */
+ public BufferedReader readInputBuffer(File tempFile) throws IOException {
+ return (new BufferedReader(new InputStreamReader(new FileInputStream(tempFile))));
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/GfxInfoParserTest.java b/javatests/com/android/loganalysis/parser/GfxInfoParserTest.java
new file mode 100644
index 0000000..fcd053b
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/GfxInfoParserTest.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2015 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.loganalysis.parser;
+
+import com.android.loganalysis.item.GfxInfoItem;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class GfxInfoParserTest extends TestCase {
+
+ /**
+ * Tests gfxinfo output from M with single process.
+ */
+ public void testSingleProcess() {
+ List<String> input = Arrays.asList(
+ "** Graphics info for pid 853 [com.google.android.leanbacklauncher] **",
+ "",
+ "Stats since: 13370233957ns",
+ "Total frames rendered: 20391",
+ "Janky frames: 785 (3.85%)",
+ "90th percentile: 9ms",
+ "95th percentile: 14ms",
+ "99th percentile: 32ms",
+ "Number Missed Vsync: 155",
+ "Number High input latency: 0",
+ "Number Slow UI thread: 469",
+ "Number Slow bitmap uploads: 65",
+ "Number Slow issue draw commands: 153",
+ "",
+ "Caches:",
+ "Current memory usage / total memory usage (bytes):",
+ " TextureCache 16055224 / 50331648",
+ " LayerCache 0 / 33554432 (numLayers = 0)",
+ " Layers total 0 (numLayers = 0)",
+ " RenderBufferCache 0 / 2097152",
+ " GradientCache 0 / 838860",
+ " PathCache 0 / 25165824",
+ " TessellationCache 1048296 / 1048576",
+ " TextDropShadowCache 0 / 4194304",
+ " PatchCache 0 / 131072",
+ " FontRenderer 0 A8 524288 / 524288",
+ " FontRenderer 0 RGBA 0 / 0",
+ " FontRenderer 0 total 524288 / 524288",
+ "Other:",
+ " FboCache 0 / 0",
+ "Total memory usage:",
+ " 17627808 bytes, 16.81 MB",
+ "",
+ "Profile data in ms:",
+ "",
+ "\tcom.google.android.leanbacklauncher/com.google.android.leanbacklauncher.MainActivity/android.view.ViewRootImpl@8dc465 (visibility=8)",
+ "Stats since: 13370233957ns",
+ "Total frames rendered: 20391",
+ "Janky frames: 785 (3.85%)",
+ "90th percentile: 9ms",
+ "95th percentile: 14ms",
+ "99th percentile: 32ms",
+ "Number Missed Vsync: 155",
+ "Number High input latency: 0",
+ "Number Slow UI thread: 469",
+ "Number Slow bitmap uploads: 65",
+ "Number Slow issue draw commands: 153",
+ "",
+ "View hierarchy:",
+ "",
+ " com.google.android.leanbacklauncher/com.google.android.leanbacklauncher.MainActivity/android.view.ViewRootImpl@8dc465",
+ " 220 views, 177.61 kB of display lists",
+ "",
+ "",
+ "Total ViewRootImpl: 1",
+ "Total Views: 220",
+ "Total DisplayList: 177.61 kB");
+
+ GfxInfoItem item = new GfxInfoParser().parse(input);
+
+ assertEquals(1, item.getPids().size());
+ assertEquals("com.google.android.leanbacklauncher", item.getName(853));
+ assertEquals(20391, item.getTotalFrames(853));
+ assertEquals(785, item.getJankyFrames(853));
+ assertEquals(9, item.getPrecentile90(853));
+ assertEquals(14, item.getPrecentile95(853));
+ assertEquals(32, item.getPrecentile99(853));
+ }
+
+ /**
+ * Test gfxinfo output from M with multiple processes.
+ */
+ public void testMultipleProcesses() {
+ List<String> input = Arrays.asList(
+ "Applications Graphics Acceleration Info:",
+ "Uptime: 6127679 Realtime: 6127679",
+ "",
+ "** Graphics info for pid 844 [com.google.android.leanbacklauncher] **",
+ "",
+ "Stats since: 12167093145ns",
+ "Total frames rendered: 1690",
+ "Janky frames: 125 (7.40%)",
+ "90th percentile: 13ms",
+ "95th percentile: 19ms",
+ "99th percentile: 48ms",
+ "Number Missed Vsync: 17",
+ "Number High input latency: 0",
+ "Number Slow UI thread: 32",
+ "Number Slow bitmap uploads: 20",
+ "Number Slow issue draw commands: 67",
+ "",
+ "Caches:",
+ "Current memory usage / total memory usage (bytes):",
+ " TextureCache 16550096 / 50331648",
+ " LayerCache 0 / 33554432 (numLayers = 0)",
+ " Layers total 0 (numLayers = 0)",
+ " RenderBufferCache 0 / 2097152",
+ " GradientCache 0 / 838860",
+ " PathCache 0 / 25165824",
+ " TessellationCache 350424 / 1048576",
+ " TextDropShadowCache 0 / 4194304",
+ " PatchCache 0 / 131072",
+ " FontRenderer 0 A8 524288 / 524288",
+ " FontRenderer 0 RGBA 0 / 0",
+ " FontRenderer 0 total 524288 / 524288",
+ "Other:",
+ " FboCache 0 / 0",
+ "Total memory usage:",
+ " 17424808 bytes, 16.62 MB",
+ "",
+ "Profile data in ms:",
+ "",
+ "\tcom.google.android.leanbacklauncher/com.google.android.leanbacklauncher.MainActivity/android.view.ViewRootImpl@178d02b (visibility=0)",
+ "Stats since: 12167093145ns",
+ "Total frames rendered: 1690",
+ "Janky frames: 125 (7.40%)",
+ "90th percentile: 13ms",
+ "95th percentile: 19ms",
+ "99th percentile: 48ms",
+ "Number Missed Vsync: 17",
+ "Number High input latency: 0",
+ "Number Slow UI thread: 32",
+ "Number Slow bitmap uploads: 20",
+ "Number Slow issue draw commands: 67",
+ "",
+ "View hierarchy:",
+ "",
+ " com.google.android.leanbacklauncher/com.google.android.leanbacklauncher.MainActivity/android.view.ViewRootImpl@178d02b",
+ " 221 views, 207.24 kB of display lists",
+ "",
+ "",
+ "Total ViewRootImpl: 1",
+ "Total Views: 221",
+ "Total DisplayList: 207.24 kB",
+ "",
+ "",
+ "** Graphics info for pid 1881 [com.android.vending] **",
+ "",
+ "Stats since: 6092969986095ns",
+ "Total frames rendered: 693",
+ "Janky frames: 62 (8.95%)",
+ "90th percentile: 16ms",
+ "95th percentile: 26ms",
+ "99th percentile: 81ms",
+ "Number Missed Vsync: 17",
+ "Number High input latency: 0",
+ "Number Slow UI thread: 30",
+ "Number Slow bitmap uploads: 4",
+ "Number Slow issue draw commands: 13",
+ "",
+ "Caches:",
+ "Current memory usage / total memory usage (bytes):",
+ " TextureCache 7369504 / 50331648",
+ " LayerCache 0 / 33554432 (numLayers = 0)",
+ " Layers total 0 (numLayers = 0)",
+ " RenderBufferCache 0 / 2097152",
+ " GradientCache 0 / 838860",
+ " PathCache 0 / 25165824",
+ " TessellationCache 0 / 1048576",
+ " TextDropShadowCache 0 / 4194304",
+ " PatchCache 0 / 131072",
+ " FontRenderer 0 A8 524288 / 524288",
+ " FontRenderer 0 RGBA 0 / 0",
+ " FontRenderer 0 total 524288 / 524288",
+ "Other:",
+ " FboCache 0 / 0",
+ "Total memory usage:",
+ " 7893792 bytes, 7.53 MB",
+ "",
+ "Profile data in ms:",
+ "",
+ "\tcom.android.vending/com.google.android.finsky.activities.MainActivity/android.view.ViewRootImpl@5bd0cb2 (visibility=8)",
+ "Stats since: 6092969986095ns",
+ "Total frames rendered: 693",
+ "Janky frames: 62 (8.95%)",
+ "90th percentile: 16ms",
+ "95th percentile: 26ms",
+ "99th percentile: 81ms",
+ "Number Missed Vsync: 17",
+ "Number High input latency: 0",
+ "Number Slow UI thread: 30",
+ "Number Slow bitmap uploads: 4",
+ "Number Slow issue draw commands: 13",
+ "",
+ "View hierarchy:",
+ "",
+ " com.android.vending/com.google.android.finsky.activities.MainActivity/android.view.ViewRootImpl@5bd0cb2",
+ " 195 views, 157.71 kB of display lists",
+ "",
+ "",
+ "Total ViewRootImpl: 1",
+ "Total Views: 195",
+ "Total DisplayList: 157.71 kB",
+ "",
+ "",
+ "** Graphics info for pid 2931 [com.google.android.videos] **",
+ "",
+ "Stats since: 6039768250261ns",
+ "Total frames rendered: 107",
+ "Janky frames: 42 (39.25%)",
+ "90th percentile: 48ms",
+ "95th percentile: 65ms",
+ "99th percentile: 113ms",
+ "Number Missed Vsync: 9",
+ "Number High input latency: 0",
+ "Number Slow UI thread: 28",
+ "Number Slow bitmap uploads: 8",
+ "Number Slow issue draw commands: 20",
+ "",
+ "Caches:",
+ "Current memory usage / total memory usage (bytes):",
+ " TextureCache 7880000 / 50331648",
+ " LayerCache 0 / 33554432 (numLayers = 0)",
+ " Layers total 0 (numLayers = 0)",
+ " RenderBufferCache 0 / 2097152",
+ " GradientCache 0 / 838860",
+ " PathCache 0 / 25165824",
+ " TessellationCache 0 / 1048576",
+ " TextDropShadowCache 0 / 4194304",
+ " PatchCache 0 / 131072",
+ " FontRenderer 0 A8 524288 / 524288",
+ " FontRenderer 0 RGBA 0 / 0",
+ " FontRenderer 0 total 524288 / 524288",
+ "Other:",
+ " FboCache 0 / 0",
+ "Total memory usage:",
+ " 8404288 bytes, 8.01 MB",
+ "",
+ "Profile data in ms:",
+ "",
+ "\tcom.google.android.videos/com.google.android.videos.pano.activity.PanoHomeActivity/android.view.ViewRootImpl@3d96e69 (visibility=8)",
+ "Stats since: 6039768250261ns",
+ "Total frames rendered: 107",
+ "Janky frames: 42 (39.25%)",
+ "90th percentile: 48ms",
+ "95th percentile: 65ms",
+ "99th percentile: 113ms",
+ "Number Missed Vsync: 9",
+ "Number High input latency: 0",
+ "Number Slow UI thread: 28",
+ "Number Slow bitmap uploads: 8",
+ "Number Slow issue draw commands: 20",
+ "",
+ "View hierarchy:",
+ "",
+ " com.google.android.videos/com.google.android.videos.pano.activity.PanoHomeActivity/android.view.ViewRootImpl@3d96e69",
+ " 219 views, 173.57 kB of display lists",
+ "",
+ "",
+ "Total ViewRootImpl: 1",
+ "Total Views: 219",
+ "Total DisplayList: 173.57 kB");
+
+ GfxInfoItem item = new GfxInfoParser().parse(input);
+
+ assertEquals(3, item.getPids().size());
+ assertEquals("com.google.android.leanbacklauncher", item.getName(844));
+ assertEquals(1690, item.getTotalFrames(844));
+ assertEquals(125, item.getJankyFrames(844));
+ assertEquals(13, item.getPrecentile90(844));
+ assertEquals(19, item.getPrecentile95(844));
+ assertEquals(48, item.getPrecentile99(844));
+ assertEquals("com.android.vending", item.getName(1881));
+ assertEquals(693, item.getTotalFrames(1881));
+ assertEquals(62, item.getJankyFrames(1881));
+ assertEquals(16, item.getPrecentile90(1881));
+ assertEquals(26, item.getPrecentile95(1881));
+ assertEquals(81, item.getPrecentile99(1881));
+ assertEquals("com.google.android.videos", item.getName(2931));
+ assertEquals(107, item.getTotalFrames(2931));
+ assertEquals(42, item.getJankyFrames(2931));
+ assertEquals(48, item.getPrecentile90(2931));
+ assertEquals(65, item.getPrecentile95(2931));
+ assertEquals(113, item.getPrecentile99(2931));
+ }
+
+ /**
+ * Test gfxinfo output from L with single process.
+ * In L, gfxinfo does not contain Jank number information.
+ * This method tests that GfxInfoParser silently ignores such outputs.
+ */
+ public void testSingleProcessInL() {
+ List<String> input = Arrays.asList(
+ "** Graphics info for pid 1924 [com.google.android.leanbacklauncher] **",
+ "",
+ "Caches:",
+ "Current memory usage / total memory usage (bytes):",
+ " TextureCache 19521592 / 50331648",
+ " LayerCache 14745600 / 50331648 (numLayers = 5)",
+ " Layer size 512x512; isTextureLayer()=0; texid=392 fbo=0; refs=1",
+ " Layer size 512x448; isTextureLayer()=0; texid=377 fbo=0; refs=1",
+ " Layer size 1920x832; isTextureLayer()=0; texid=360 fbo=0; refs=1",
+ " Layer size 1920x512; isTextureLayer()=0; texid=14 fbo=0; refs=1",
+ " Layer size 1920x320; isTextureLayer()=0; texid=393 fbo=0; refs=1",
+ " Layers total 14745600 (numLayers = 5)",
+ " RenderBufferCache 0 / 8388608",
+ " GradientCache 0 / 1048576",
+ " PathCache 3370264 / 33554432",
+ " TessellationCache 194928 / 1048576",
+ " TextDropShadowCache 0 / 6291456",
+ " PatchCache 0 / 131072",
+ " FontRenderer 0 A8 1048576 / 1048576",
+ " FontRenderer 0 RGBA 0 / 0",
+ " FontRenderer 0 total 1048576 / 1048576",
+ "Other:",
+ " FboCache 0 / 0",
+ "Total memory usage:",
+ " 38880960 bytes, 37.08 MB",
+ "",
+ "Profile data in ms:",
+ "",
+ "\tcom.google.android.leanbacklauncher/com.google.android.leanbacklauncher.MainActivity/android.view.ViewRootImpl@355a7923 (visibility=0)",
+ "View hierarchy:",
+ "",
+ " com.google.android.leanbacklauncher/com.google.android.leanbacklauncher.MainActivity/android.view.ViewRootImpl@355a7923",
+ " 142 views, 136.96 kB of display lists",
+ "",
+ "",
+ "Total ViewRootImpl: 1",
+ "Total Views: 142",
+ "Total DisplayList: 136.96 kB");
+
+ GfxInfoItem item = new GfxInfoParser().parse(input);
+
+ assertEquals(0, item.getPids().size());
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/InterruptParserTest.java b/javatests/com/android/loganalysis/parser/InterruptParserTest.java
new file mode 100644
index 0000000..d45f3af
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/InterruptParserTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 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.loganalysis.parser;
+
+
+
+import com.android.loganalysis.item.InterruptItem;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link InterruptParser}
+ */
+public class InterruptParserTest extends TestCase {
+
+ /**
+ * Test that normal input is parsed.
+ */
+ public void testInterruptParser() {
+ List<String> inputBlock = Arrays.asList(
+ " Wakeup reason 200:qcom,smd-rpm:222:fc4: 11m 49s 332ms (0 times) realtime",
+ " Wakeup reason 200:qcom,smd-rpm: 48s 45ms (0 times) realtime",
+ " Wakeup reason 2:qcom,smd-rpm:2:f0.qm,mm:22:fc4mi: 3s 417ms (0 times) realtime",
+ " Wakeup reason 188:qcom,smd-adsp:200:qcom,smd-rpm: 1s 656ms (0 times) realtime",
+ " Wakeup reason 58:qcom,smsm-modem:2:qcom,smd-rpm: 6m 16s 1ms (5 times) realtime",
+ " Wakeup reason 57:qcom,smd-modem:200:qcom,smd-rpm: 40s 995ms (0 times) realtime",
+ " Wakeup reason unknown: 8s 455ms (0 times) realtime",
+ " Wakeup reason 9:bcmsdh_sdmmc:2:qcomd-rpm:240:mso: 8m 5s 9ms (0 times) realtime");
+
+ InterruptItem interrupt = new InterruptParser().parse(inputBlock);
+
+ assertEquals(1, interrupt.getInterrupts(
+ InterruptItem.InterruptCategory.WIFI_INTERRUPT).size());
+
+ assertEquals("9:bcmsdh_sdmmc:2:qcomd-rpm:240:mso", interrupt.getInterrupts(
+ InterruptItem.InterruptCategory.WIFI_INTERRUPT).get(0).getName());
+
+ assertEquals(2, interrupt.getInterrupts(
+ InterruptItem.InterruptCategory.MODEM_INTERRUPT).size());
+
+ assertEquals(5, interrupt.getInterrupts(InterruptItem.InterruptCategory.MODEM_INTERRUPT).
+ get(0).getInterruptCount());
+
+ }
+}
+
diff --git a/javatests/com/android/loganalysis/parser/JavaCrashParserTest.java b/javatests/com/android/loganalysis/parser/JavaCrashParserTest.java
new file mode 100644
index 0000000..a7d06b3
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/JavaCrashParserTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2012 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.loganalysis.parser;
+
+import com.android.loganalysis.item.JavaCrashItem;
+import com.android.loganalysis.util.ArrayUtil;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link JavaCrashParser}.
+ */
+public class JavaCrashParserTest extends TestCase {
+
+ /**
+ * Test that Java crashes are parsed with no message.
+ */
+ public void testParse_no_message() {
+ List<String> lines = Arrays.asList(
+ "java.lang.Exception",
+ "\tat class.method1(Class.java:1)",
+ "\tat class.method2(Class.java:2)",
+ "\tat class.method3(Class.java:3)");
+
+ JavaCrashItem jc = new JavaCrashParser().parse(lines);
+ assertNotNull(jc);
+ assertEquals("java.lang.Exception", jc.getException());
+ assertNull(jc.getMessage());
+ assertEquals(ArrayUtil.join("\n", lines), jc.getStack());
+ }
+
+ /**
+ * Test that Java crashes are parsed with a message.
+ */
+ public void testParse_message() {
+ List<String> lines = Arrays.asList(
+ "java.lang.Exception: This is the message",
+ "\tat class.method1(Class.java:1)",
+ "\tat class.method2(Class.java:2)",
+ "\tat class.method3(Class.java:3)");
+
+ JavaCrashItem jc = new JavaCrashParser().parse(lines);
+ assertNotNull(jc);
+ assertEquals("java.lang.Exception", jc.getException());
+ assertEquals("This is the message", jc.getMessage());
+ assertEquals(ArrayUtil.join("\n", lines), jc.getStack());
+ }
+
+ /**
+ * Test that Java crashes are parsed if the message spans multiple lines.
+ */
+ public void testParse_multiline_message() {
+ List<String> lines = Arrays.asList(
+ "java.lang.Exception: This message",
+ "is many lines",
+ "long.",
+ "\tat class.method1(Class.java:1)",
+ "\tat class.method2(Class.java:2)",
+ "\tat class.method3(Class.java:3)");
+
+ JavaCrashItem jc = new JavaCrashParser().parse(lines);
+ assertNotNull(jc);
+ assertEquals("java.lang.Exception", jc.getException());
+ assertEquals("This message\nis many lines\nlong.", jc.getMessage());
+ assertEquals(ArrayUtil.join("\n", lines), jc.getStack());
+ }
+
+ /**
+ * Test that caused by sections of Java crashes are parsed, with no message or single or
+ * multiline messages.
+ */
+ public void testParse_caused_by() {
+ List<String> lines = Arrays.asList(
+ "java.lang.Exception: This is the message",
+ "\tat class.method1(Class.java:1)",
+ "\tat class.method2(Class.java:2)",
+ "\tat class.method3(Class.java:3)",
+ "Caused by: java.lang.Exception",
+ "\tat class.method4(Class.java:4)",
+ "Caused by: java.lang.Exception: This is the caused by message",
+ "\tat class.method5(Class.java:5)",
+ "Caused by: java.lang.Exception: This is a multiline",
+ "caused by message",
+ "\tat class.method6(Class.java:6)");
+
+ JavaCrashItem jc = new JavaCrashParser().parse(lines);
+ assertNotNull(jc);
+ assertEquals("java.lang.Exception", jc.getException());
+ assertEquals("This is the message", jc.getMessage());
+ assertEquals(ArrayUtil.join("\n", lines), jc.getStack());
+ }
+
+ /**
+ * Test that the Java crash is cutoff if an unexpected line is handled.
+ */
+ public void testParse_cutoff() {
+ List<String> lines = Arrays.asList(
+ "java.lang.Exception: This is the message",
+ "\tat class.method1(Class.java:1)",
+ "\tat class.method2(Class.java:2)",
+ "\tat class.method3(Class.java:3)",
+ "Invalid line",
+ "java.lang.Exception: This is the message");
+
+ JavaCrashItem jc = new JavaCrashParser().parse(lines);
+ assertNotNull(jc);
+ assertEquals("java.lang.Exception", jc.getException());
+ assertEquals("This is the message", jc.getMessage());
+ assertEquals(ArrayUtil.join("\n", lines.subList(0, lines.size()-2)), jc.getStack());
+ }
+
+ /**
+ * Tests that only parts between the markers are parsed.
+ */
+ public void testParse_begin_end_markers() {
+ List<String> lines = Arrays.asList(
+ "error: this message has begin and end",
+ "----- begin exception -----",
+ "java.lang.Exception: This message",
+ "is many lines",
+ "long.",
+ "\tat class.method1(Class.java:1)",
+ "\tat class.method2(Class.java:2)",
+ "\tat class.method3(Class.java:3)",
+ "----- end exception -----");
+
+ JavaCrashItem jc = new JavaCrashParser().parse(lines);
+ assertNotNull(jc);
+ assertEquals("java.lang.Exception", jc.getException());
+ assertEquals("This message\nis many lines\nlong.", jc.getMessage());
+ assertNotNull(jc.getStack());
+ assertFalse(jc.getStack().contains("begin exception"));
+ assertFalse(jc.getStack().contains("end exception"));
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/KernelLogParserTest.java b/javatests/com/android/loganalysis/parser/KernelLogParserTest.java
new file mode 100644
index 0000000..159a90e
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/KernelLogParserTest.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2013 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.loganalysis.parser;
+
+import com.android.loganalysis.item.KernelLogItem;
+import com.android.loganalysis.item.LowMemoryKillerItem;
+import com.android.loganalysis.item.MiscKernelLogItem;
+import com.android.loganalysis.item.PageAllocationFailureItem;
+import com.android.loganalysis.item.SELinuxItem;
+import com.android.loganalysis.util.LogPatternUtil;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link KernelLogParser}.
+ */
+public class KernelLogParserTest extends TestCase {
+ /**
+ * Test that log lines formatted by last kmsg are able to be parsed.
+ */
+ public void testParseLastKmsg() {
+ List<String> lines = Arrays.asList(
+ "[ 0.000000] Start",
+ "[ 1.000000] Kernel panic",
+ "[ 2.000000] End");
+
+ KernelLogItem kernelLog = new KernelLogParser().parse(lines);
+ assertNotNull(kernelLog);
+ assertEquals(0.0, kernelLog.getStartTime(), 0.0000005);
+ assertEquals(2.0, kernelLog.getStopTime(), 0.0000005);
+ assertEquals(1, kernelLog.getEvents().size());
+ assertEquals(1, kernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET).size());
+
+ MiscKernelLogItem item = kernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET).get(0);
+ assertEquals(1.0, item.getEventTime(), 0.0000005);
+ assertEquals("[ 0.000000] Start", item.getPreamble());
+ assertEquals("Kernel panic", item.getStack());
+ }
+
+ /**
+ * Test that log lines formatted by dmsg are able to be parsed.
+ */
+ public void testParseDmesg() {
+ List<String> lines = Arrays.asList(
+ "<1>[ 0.000000] Start",
+ "<1>[ 1.000000] Kernel panic",
+ "<1>[ 2.000000] End");
+
+ KernelLogItem kernelLog = new KernelLogParser().parse(lines);
+ assertNotNull(kernelLog);
+ assertEquals(0.0, kernelLog.getStartTime(), 0.0000005);
+ assertEquals(2.0, kernelLog.getStopTime(), 0.0000005);
+ assertEquals(1, kernelLog.getEvents().size());
+ assertEquals(1, kernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET).size());
+
+ MiscKernelLogItem item = kernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET).get(0);
+ assertEquals(1.0, item.getEventTime(), 0.0000005);
+ assertEquals("<1>[ 0.000000] Start", item.getPreamble());
+ assertEquals("Kernel panic", item.getStack());
+ }
+
+ /**
+ * Test that last boot reasons are parsed.
+ */
+ public void testParseLastMessage() {
+ List<String> lines = Arrays.asList(
+ "[ 0.000000] Start",
+ "[ 2.000000] End",
+ "Last boot reason: hw_reset");
+
+ KernelLogItem kernelLog = new KernelLogParser().parse(lines);
+ assertNotNull(kernelLog);
+ assertEquals(0.0, kernelLog.getStartTime(), 0.0000005);
+ assertEquals(2.0, kernelLog.getStopTime(), 0.0000005);
+ assertEquals(1, kernelLog.getEvents().size());
+ assertEquals(1, kernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET).size());
+
+ MiscKernelLogItem item = kernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET).get(0);
+ assertEquals(2.0, item.getEventTime(), 0.0000005);
+ assertEquals("[ 0.000000] Start\n[ 2.000000] End", item.getPreamble());
+ assertEquals("Last boot reason: hw_reset", item.getStack());
+ }
+
+ /**
+ * Test that unknown last boot reasons are parsed.
+ */
+ public void testParseUnknownLastMessage() {
+ List<String> lines = Arrays.asList(
+ "[ 0.000000] Start",
+ "[ 2.000000] End",
+ "Last boot reason: unknown failure");
+
+ KernelLogItem kernelLog = new KernelLogParser().parse(lines);
+ assertNotNull(kernelLog);
+ assertEquals(0.0, kernelLog.getStartTime(), 0.0000005);
+ assertEquals(2.0, kernelLog.getStopTime(), 0.0000005);
+ assertEquals(1, kernelLog.getEvents().size());
+ assertEquals(1, kernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET).size());
+
+ MiscKernelLogItem item = kernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET).get(0);
+ assertEquals(2.0, item.getEventTime(), 0.0000005);
+ assertEquals("[ 0.000000] Start\n[ 2.000000] End", item.getPreamble());
+ assertNotNull(item.getStack());
+ }
+
+ public void testParseKnownGoodLastMessage() {
+ List<String> lines = Arrays.asList(
+ "[ 0.000000] Start",
+ "[ 2.000000] End",
+ "Last boot reason: reboot");
+
+ KernelLogItem kernelLog = new KernelLogParser().parse(lines);
+ assertNotNull(kernelLog);
+ assertEquals(0.0, kernelLog.getStartTime(), 0.0000005);
+ assertEquals(2.0, kernelLog.getStopTime(), 0.0000005);
+ assertEquals(0, kernelLog.getEvents().size());
+ }
+
+ /**
+ * Test that reset reasons don't crash if times are set.
+ */
+ public void testNoPreviousLogs() {
+ List<String> lines = Arrays.asList(
+ "Last boot reason: hw_reset");
+
+ KernelLogItem kernelLog = new KernelLogParser().parse(lines);
+ assertNotNull(kernelLog);
+ assertNull(kernelLog.getStartTime());
+ assertNull(kernelLog.getStopTime());
+ assertEquals(1, kernelLog.getEvents().size());
+ assertEquals(1, kernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET).size());
+
+ MiscKernelLogItem item = kernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET).get(0);
+ assertNull(item.getEventTime());
+ assertEquals("", item.getPreamble());
+ assertEquals("Last boot reason: hw_reset", item.getStack());
+ }
+
+ /**
+ * Test that an empty input returns {@code null}.
+ */
+ public void testEmptyInput() {
+ KernelLogItem item = new KernelLogParser().parse(Arrays.asList(""));
+ assertNull(item);
+ }
+
+ /**
+ * Test that kernel patterns are matched.
+ */
+ public void testPatterns() {
+ List<String> kernelResetPatterns = Arrays.asList(
+ "smem: DIAG",
+ "smsm: AMSS FATAL ERROR",
+ "kernel BUG at ",
+ "PVR_K:(Fatal): Debug assertion failed! []",
+ "Kernel panic",
+ "BP panicked",
+ "WROTE DSP RAMDUMP",
+ "tegra_wdt: last reset due to watchdog timeout",
+ "tegra_wdt tegra_wdt.0: last reset is due to watchdog timeout.",
+ "Last reset was MPU Watchdog Timer reset",
+ "[MODEM_IF] CRASH",
+ "Last boot reason: kernel_panic",
+ "Last boot reason: rpm_err",
+ "Last boot reason: hw_reset",
+ "Last boot reason: wdog_",
+ "Last boot reason: tz_err",
+ "Last boot reason: adsp_err",
+ "Last boot reason: modem_err",
+ "Last boot reason: mba_err",
+ "Last boot reason: watchdog",
+ "Last boot reason: watchdogr",
+ "Last boot reason: Watchdog",
+ "Last boot reason: Panic",
+ "Last reset was system watchdog timer reset");
+
+ LogPatternUtil patternUtil = new KernelLogParser().getLogPatternUtil();
+
+ for (String pattern : kernelResetPatterns) {
+ assertEquals(String.format("Message \"%s\" was not matched.", pattern),
+ KernelLogParser.KERNEL_RESET, patternUtil.checkMessage(pattern));
+ }
+
+ assertEquals(KernelLogParser.KERNEL_ERROR, patternUtil.checkMessage("Internal error:"));
+ assertEquals(KernelLogParser.SELINUX_DENIAL, patternUtil.checkMessage(
+ "avc: denied scontext=0:0:domain:0 "));
+ }
+
+ /**
+ * Test that an SELinux Denial can be parsed out of a list of log lines.
+ */
+ public void testSelinuxDenialParse() {
+ final String SELINUX_DENIAL_STACK = "type=1400 audit(1384544483.730:10): avc: denied " +
+ "{ getattr } for pid=797 comm=\"Binder_5\" path=\"/dev/pts/1\" + " +
+ "dev=devpts ino=4 scontext=u:r:system_server:s0 " +
+ "tcontext=u:object_r:devpts:s0 tclass=chr_file";
+ List<String> lines = Arrays.asList(
+ "<4>[ 0.000000] Memory policy: ECC disabled, Data cache writealloc",
+ "<7>[ 7.896355] SELinux: initialized (dev cgroup, type cgroup)" +
+ ", uses genfs_contexts",
+ "<5>[ 43.399164] " + SELINUX_DENIAL_STACK);
+ KernelLogItem kernelLog = new KernelLogParser().parse(lines);
+
+ assertNotNull(kernelLog);
+ assertEquals(0.0, kernelLog.getStartTime(), 0.0000005);
+ assertEquals(43.399164, kernelLog.getStopTime(), 0.0000005);
+ assertEquals(2, kernelLog.getEvents().size());
+ assertEquals(1, kernelLog.getMiscEvents(KernelLogParser.SELINUX_DENIAL).size());
+ assertEquals(1, kernelLog.getSELinuxEvents().size());
+
+ MiscKernelLogItem item = kernelLog.getMiscEvents(KernelLogParser.SELINUX_DENIAL).get(0);
+ assertEquals(43.399164, item.getEventTime(), 0.0000005);
+ assertEquals(KernelLogParser.SELINUX_DENIAL, item.getCategory());
+ assertEquals(SELINUX_DENIAL_STACK, item.getStack());
+
+ SELinuxItem selinuxItem = kernelLog.getSELinuxEvents().get(0);
+ assertEquals("system_server", selinuxItem.getSContext());
+ assertEquals(43.399164, selinuxItem.getEventTime(), 0.0000005);
+ assertEquals(KernelLogParser.SELINUX_DENIAL, selinuxItem.getCategory());
+ assertEquals(SELINUX_DENIAL_STACK, selinuxItem.getStack());
+ }
+
+ /**
+ * Test that an LowMemoryKiller event can be parsed out of a list of log lines.
+ */
+ public void testLowMemoryKillerParse() {
+ final String LMK_MESSAGE = "Killing '.qcrilmsgtunnel' (3699), adj 100,";
+ List<String> lines = Arrays.asList(
+ "<4>[ 0.000000] Memory policy: ECC disabled, Data cache writealloc",
+ "<7>[ 7.896355] SELinux: initialized (dev cgroup, type cgroup)" +
+ ", uses genfs_contexts",
+ "<3>[ 43.399164] " + LMK_MESSAGE);
+ KernelLogItem kernelLog = new KernelLogParser().parse(lines);
+
+ assertNotNull(kernelLog);
+ assertEquals(0.0, kernelLog.getStartTime(), 0.0000005);
+ assertEquals(43.399164, kernelLog.getStopTime(), 0.0000005);
+ assertEquals(2, kernelLog.getEvents().size());
+ assertEquals(1, kernelLog.getMiscEvents(KernelLogParser.LOW_MEMORY_KILLER).size());
+ assertEquals(1, kernelLog.getLowMemoryKillerEvents().size());
+
+ MiscKernelLogItem miscItem = kernelLog.getMiscEvents(KernelLogParser.LOW_MEMORY_KILLER)
+ .get(0);
+ assertEquals(43.399164, miscItem.getEventTime(), 0.0000005);
+ assertEquals(KernelLogParser.LOW_MEMORY_KILLER, miscItem.getCategory());
+ assertEquals(LMK_MESSAGE, miscItem.getStack());
+
+ LowMemoryKillerItem lmkItem = kernelLog.getLowMemoryKillerEvents().get(0);
+ assertEquals(KernelLogParser.LOW_MEMORY_KILLER, lmkItem.getCategory());
+ assertEquals(3699, lmkItem.getPid());
+ assertEquals(".qcrilmsgtunnel", lmkItem.getProcessName());
+ assertEquals(100, lmkItem.getAdjustment());
+ assertEquals(LMK_MESSAGE, lmkItem.getStack());
+ }
+
+ /**
+ * Test that page allocation failures can be parsed out of a list of log lines.
+ */
+ public void testPageAllocationFailureParse() {
+ final String ALLOC_FAILURE = "page allocation failure: order:3, mode:0x10c0d0";
+ List<String> lines = Arrays.asList(
+ "<4>[ 0.000000] Memory policy: ECC disabled, Data cache writealloc",
+ "<7>[ 7.896355] SELinux: initialized (dev cgroup, type cgroup)" +
+ ", uses genfs_contexts",
+ "<3>[ 43.399164] " + ALLOC_FAILURE);
+ KernelLogItem kernelLog = new KernelLogParser().parse(lines);
+
+ assertNotNull(kernelLog);
+ assertEquals(0.0, kernelLog.getStartTime(), 0.0000005);
+ assertEquals(43.399164, kernelLog.getStopTime(), 0.0000005);
+ assertEquals(2, kernelLog.getEvents().size());
+ assertEquals(1, kernelLog.getMiscEvents(KernelLogParser.PAGE_ALLOC_FAILURE).size());
+ assertEquals(1, kernelLog.getPageAllocationFailureEvents().size());
+
+ MiscKernelLogItem miscItem = kernelLog.getMiscEvents(KernelLogParser.PAGE_ALLOC_FAILURE)
+ .get(0);
+ assertEquals(43.399164, miscItem.getEventTime(), 0.0000005);
+ assertEquals(KernelLogParser.PAGE_ALLOC_FAILURE, miscItem.getCategory());
+ assertEquals(ALLOC_FAILURE, miscItem.getStack());
+
+ PageAllocationFailureItem failItem = kernelLog.getPageAllocationFailureEvents().get(0);
+ assertEquals(KernelLogParser.PAGE_ALLOC_FAILURE, failItem.getCategory());
+ assertEquals(3, failItem.getOrder());
+ assertEquals(ALLOC_FAILURE, failItem.getStack());
+ }
+
+ public void testMantaReset() {
+ final List<String> lines = Arrays.asList("[ 3281.347296] ---fimc_is_ischain_close(0)",
+ "[ 3281.432055] fimc_is_scalerc_video_close",
+ "[ 3281.432270] fimc_is_scalerp_video_close",
+ "[ 3287.688303] wm8994-codec wm8994-codec: FIFO error",
+ "",
+ "No errors detected",
+ "Last reset was system watchdog timer reset (RST_STAT=0x100000)");
+
+ KernelLogItem kernelLog = new KernelLogParser().parse(lines);
+ assertEquals(1, kernelLog.getEvents().size());
+ assertEquals(1, kernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET).size());
+ }
+
+ /**
+ * Test that only the first kernel reset is taken but other signatures can have multiple
+ */
+ public void testMultipleKernelResets() {
+ final String SELINUX_DENIAL_STACK = "type=1400 audit(1384544483.730:10): avc: denied " +
+ "{ getattr } for pid=797 comm=\"Binder_5\" path=\"/dev/pts/1\" + " +
+ "dev=devpts ino=4 scontext=u:r:system_server:s0 " +
+ "tcontext=u:object_r:devpts:s0 tclass=chr_file";
+ final List<String> lines = Arrays.asList(
+ "[ 0.000000] Kernel panic",
+ "[ 0.000000] Internal error:",
+ "[ 0.000000] " + SELINUX_DENIAL_STACK,
+ "[ 1.000000] Kernel panic",
+ "[ 1.000000] Internal error:",
+ "[ 1.000000] " + SELINUX_DENIAL_STACK,
+ "[ 2.000000] Kernel panic",
+ "[ 2.000000] Internal error:",
+ "[ 2.000000] " + SELINUX_DENIAL_STACK);
+
+ KernelLogItem kernelLog = new KernelLogParser().parse(lines);
+ assertEquals(7, kernelLog.getEvents().size());
+ assertEquals(1, kernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET).size());
+ assertEquals(0.0,
+ kernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET).get(0).getEventTime());
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/LocationServiceParserTest.java b/javatests/com/android/loganalysis/parser/LocationServiceParserTest.java
new file mode 100644
index 0000000..fc58e45
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/LocationServiceParserTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2016 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.loganalysis.parser;
+
+import com.android.loganalysis.item.LocationDumpsItem;
+import com.android.loganalysis.item.LocationDumpsItem.LocationInfoItem;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link LocationServiceParser}
+ */
+public class LocationServiceParserTest extends TestCase {
+
+ /**
+ * Test that normal input is parsed.
+ */
+ public void testLocationClientsSize() {
+ List<String> inputBlock = Arrays.asList(
+ " Location Request History By Package:",
+ " Interval effective/min/max 1/0/0[s] Duration: 140[minutes] "
+ + "[com.google.android.gms, PRIORITY_NO_POWER, UserLocationProducer] "
+ + "Num requests: 2 Active: true",
+ " Interval effective/min/max 284/285/3600[s] Duration: 140[minutes] "
+ + "[com.google.android.googlequicksearchbox, PRIORITY_BALANCED_POWER_ACCURACY] "
+ + "Num requests: 5 Active: true",
+ " Interval effective/min/max 0/0/0[s] Duration: 0[minutes] "
+ + "[com.google.android.apps.walletnfcrel, PRIORITY_BALANCED_POWER_ACCURACY] "
+ + "Num requests: 1 Active: false",
+ " ",
+ " FLP WakeLock Count");
+
+ LocationDumpsItem locationClients = new LocationServiceParser().parse(inputBlock);
+ assertNotNull(locationClients.getLocationClients());
+ assertEquals(locationClients.getLocationClients().size(), 3);
+ }
+
+ /**
+ * Test that normal input is parsed.
+ */
+ public void testLocationClientParser() {
+ List<String> inputBlock = Arrays.asList(
+ " Location Request History By Package:",
+ " Interval effective/min/max 1/0/0[s] Duration: 140[minutes] "
+ + "[com.google.android.gms, PRIORITY_NO_POWER, UserLocationProducer] "
+ + "Num requests: 2 Active: true");
+
+ LocationDumpsItem locationClients = new LocationServiceParser().parse(inputBlock);
+ assertNotNull(locationClients.getLocationClients());
+ LocationInfoItem client = locationClients.getLocationClients().iterator().next();
+ assertEquals(client.getPackage(), "com.google.android.gms");
+ assertEquals(client.getEffectiveInterval(), 1);
+ assertEquals(client.getMinInterval(), 0);
+ assertEquals(client.getMaxInterval(), 0);
+ assertEquals(client.getPriority(), "PRIORITY_NO_POWER");
+ assertEquals(client.getDuration(), 140);
+ }
+
+ /**
+ * Test that invalid input is parsed.
+ */
+ public void testLocationClientParserInvalidInput() {
+ List<String> inputBlock = Arrays.asList(
+ " Location Request History By Package:",
+ " Interval effective/min/max 1/0/0[s] Duration: 140[minutes] "
+ + "[com.google.android.gms PRIORITY_NO_POWER UserLocationProducer] "
+ + "Num requests: 2 Active: true");
+ LocationDumpsItem locationClients = new LocationServiceParser().parse(inputBlock);
+ assertEquals(locationClients.getLocationClients().size(), 0);
+ }
+
+}
+
diff --git a/javatests/com/android/loganalysis/parser/LogcatParserTest.java b/javatests/com/android/loganalysis/parser/LogcatParserTest.java
new file mode 100644
index 0000000..1d4f72c
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/LogcatParserTest.java
@@ -0,0 +1,845 @@
+/*
+ * Copyright (C) 2012 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.loganalysis.parser;
+
+import com.android.loganalysis.item.JavaCrashItem;
+import com.android.loganalysis.item.LogcatItem;
+import com.android.loganalysis.item.MiscLogcatItem;
+import com.android.loganalysis.util.ArrayUtil;
+
+import junit.framework.TestCase;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Unit tests for {@link LogcatParserTest}.
+ */
+public class LogcatParserTest extends TestCase {
+
+ /**
+ * Test that an ANR is parsed in the log.
+ */
+ public void testParse_anr() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "04-25 17:17:08.445 312 366 E ActivityManager: ANR (application not responding) in process: com.android.package",
+ "04-25 17:17:08.445 312 366 E ActivityManager: Reason: keyDispatchingTimedOut",
+ "04-25 17:17:08.445 312 366 E ActivityManager: Load: 0.71 / 0.83 / 0.51",
+ "04-25 17:17:08.445 312 366 E ActivityManager: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait");
+
+ LogcatItem logcat = new LogcatParser("2012").parse(lines);
+ assertNotNull(logcat);
+ assertEquals(parseTime("2012-04-25 17:17:08.445"), logcat.getStartTime());
+ assertEquals(parseTime("2012-04-25 17:17:08.445"), logcat.getStopTime());
+ assertEquals(1, logcat.getEvents().size());
+ assertEquals(1, logcat.getAnrs().size());
+ assertEquals(312, logcat.getAnrs().get(0).getPid().intValue());
+ assertEquals(366, logcat.getAnrs().get(0).getTid().intValue());
+ assertEquals("", logcat.getAnrs().get(0).getLastPreamble());
+ assertEquals("", logcat.getAnrs().get(0).getProcessPreamble());
+ assertEquals(parseTime("2012-04-25 17:17:08.445"), logcat.getAnrs().get(0).getEventTime());
+ }
+
+ /**
+ * Test that an ANR is parsed in the log.
+ */
+ public void testParse_anr_pid() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "04-25 17:17:08.445 312 366 E ActivityManager: ANR (application not responding) in process: com.android.package",
+ "04-25 17:17:08.445 312 366 E ActivityManager: PID: 1234",
+ "04-25 17:17:08.445 312 366 E ActivityManager: Reason: keyDispatchingTimedOut",
+ "04-25 17:17:08.445 312 366 E ActivityManager: Load: 0.71 / 0.83 / 0.51",
+ "04-25 17:17:08.445 312 366 E ActivityManager: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait");
+
+ LogcatItem logcat = new LogcatParser("2012").parse(lines);
+ assertNotNull(logcat);
+ assertEquals(parseTime("2012-04-25 17:17:08.445"), logcat.getStartTime());
+ assertEquals(parseTime("2012-04-25 17:17:08.445"), logcat.getStopTime());
+ assertEquals(1, logcat.getEvents().size());
+ assertEquals(1, logcat.getAnrs().size());
+ assertEquals(1234, logcat.getAnrs().get(0).getPid().intValue());
+ assertNull(logcat.getAnrs().get(0).getTid());
+ assertEquals("", logcat.getAnrs().get(0).getLastPreamble());
+ assertEquals("", logcat.getAnrs().get(0).getProcessPreamble());
+ assertEquals(parseTime("2012-04-25 17:17:08.445"), logcat.getAnrs().get(0).getEventTime());
+ }
+
+ /**
+ * Test that Java crashes can be parsed.
+ */
+ public void testParse_java_crash() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: java.lang.Exception",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)");
+
+ LogcatItem logcat = new LogcatParser("2012").parse(lines);
+ assertNotNull(logcat);
+ assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStartTime());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStopTime());
+ assertEquals(1, logcat.getEvents().size());
+ assertEquals(1, logcat.getJavaCrashes().size());
+ assertEquals(3064, logcat.getJavaCrashes().get(0).getPid().intValue());
+ assertEquals(3082, logcat.getJavaCrashes().get(0).getTid().intValue());
+ assertEquals("", logcat.getJavaCrashes().get(0).getLastPreamble());
+ assertEquals("", logcat.getJavaCrashes().get(0).getProcessPreamble());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"),
+ logcat.getJavaCrashes().get(0).getEventTime());
+ }
+
+ public void testParse_test_exception() {
+ List<String> lines = Arrays.asList(
+ "11-25 19:26:53.581 5832 7008 I TestRunner: ----- begin exception -----",
+ "11-25 19:26:53.589 5832 7008 I TestRunner: ",
+ "11-25 19:26:53.589 5832 7008 I TestRunner: java.util.concurrent.TimeoutException",
+ "11-25 19:26:53.589 5832 7008 I TestRunner: at android.support.test.uiautomator.WaitMixin.wait(WaitMixin.java:49)",
+ "11-25 19:26:53.589 5832 7008 I TestRunner: at android.support.test.uiautomator.WaitMixin.wait(WaitMixin.java:36)",
+ "11-25 19:26:53.589 5832 7008 I TestRunner: at android.support.test.uiautomator.UiDevice.wait(UiDevice.java:169)",
+ "11-25 19:26:53.589 5832 7008 I TestRunner: at com.android.test.uiautomator.common.helpers.MapsHelper.doSearch(MapsHelper.java:87)",
+ "11-25 19:26:53.589 5832 7008 I TestRunner: at com.android.test.uiautomator.aupt.MapsTest.testMaps(MapsTest.java:58)",
+ "11-25 19:26:53.589 5832 7008 I TestRunner: at java.lang.reflect.Method.invoke(Native Method)",
+ "11-25 19:26:53.589 5832 7008 I TestRunner: at java.lang.reflect.Method.invoke(Method.java:372)",
+ "11-25 19:26:53.589 5832 7008 I TestRunner: at android.test.InstrumentationTestCase.runMethod(InstrumentationTestCase.java:214)",
+ "11-25 19:26:53.589 5832 7008 I TestRunner: at android.test.InstrumentationTestCase.runTest(InstrumentationTestCase.java:199)",
+ "11-25 19:26:53.589 5832 7008 I TestRunner: at junit.framework.TestCase.runBare(TestCase.java:134)",
+ "11-25 19:26:53.589 5832 7008 I TestRunner: at junit.framework.TestResult$1.protect(TestResult.java:115)",
+ "11-25 19:26:53.589 5832 7008 I TestRunner: at junit.framework.TestResult.runProtected(TestResult.java:133)",
+ "11-25 19:26:53.589 5832 7008 I TestRunner: at junit.framework.TestResult.run(TestResult.java:118)",
+ "11-25 19:26:53.589 5832 7008 I TestRunner: at junit.framework.TestCase.run(TestCase.java:124)",
+ "11-25 19:26:53.589 5832 7008 I TestRunner: at android.support.test.aupt.AuptTestRunner$AuptPrivateTestRunner.runTest(AuptTestRunner.java:182)",
+ "11-25 19:26:53.589 5832 7008 I TestRunner: at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:176)",
+ "11-25 19:26:53.589 5832 7008 I TestRunner: at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:555)",
+ "11-25 19:26:53.589 5832 7008 I TestRunner: at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1848)",
+ "11-25 19:26:53.589 5832 7008 I TestRunner: ----- end exception -----"
+ );
+
+ LogcatParser logcatParser = new LogcatParser("2012");
+ logcatParser.addJavaCrashTag("I", "TestRunner", LogcatParser.JAVA_CRASH);
+ LogcatItem logcat = logcatParser.parse(lines);
+ assertNotNull(logcat);
+ assertEquals(1, logcat.getEvents().size());
+ assertEquals(1, logcat.getJavaCrashes().size());
+ assertEquals(5832, logcat.getJavaCrashes().get(0).getPid().intValue());
+ assertEquals(7008, logcat.getJavaCrashes().get(0).getTid().intValue());
+ assertEquals("", logcat.getJavaCrashes().get(0).getLastPreamble());
+ assertEquals("", logcat.getJavaCrashes().get(0).getProcessPreamble());
+ assertEquals(LogcatParser.JAVA_CRASH, logcat.getJavaCrashes().get(0).getCategory());
+ }
+
+ public void testParse_test_exception_with_exras() {
+ List<String> lines = Arrays.asList(
+ "12-06 17:19:18.746 6598 7960 I TestRunner: failed: testYouTube(com.android.test.uiautomator.aupt.YouTubeTest)",
+ "12-06 17:19:18.746 6598 7960 I TestRunner: ----- begin exception -----",
+ "12-06 17:19:18.747 6598 7960 I TestRunner: ",
+ "12-06 17:19:18.747 6598 7960 I TestRunner: java.util.concurrent.TimeoutException",
+ "12-06 17:19:18.747 6598 7960 I TestRunner: at android.support.test.uiautomator.WaitMixin.wait(WaitMixin.java:49)",
+ "12-06 17:19:18.747 6598 7960 I TestRunner: at android.support.test.uiautomator.WaitMixin.wait(WaitMixin.java:36)",
+ "12-06 17:19:18.747 6598 7960 I TestRunner: at android.support.test.uiautomator.UiDevice.wait(UiDevice.java:169)",
+ "12-06 17:19:18.747 6598 7960 I TestRunner: at android.support.test.aupt.AppLauncher.launchApp(AppLauncher.java:127)",
+ "12-06 17:19:18.747 6598 7960 I TestRunner: at com.android.test.uiautomator.common.helpers.YouTubeHelper.open(YouTubeHelper.java:49)",
+ "12-06 17:19:18.747 6598 7960 I TestRunner: at com.android.test.uiautomator.aupt.YouTubeTest.setUp(YouTubeTest.java:44)",
+ "12-06 17:19:18.747 6598 7960 I TestRunner: at junit.framework.TestCase.runBare(TestCase.java:132)",
+ "12-06 17:19:18.747 6598 7960 I TestRunner: at junit.framework.TestResult$1.protect(TestResult.java:115)",
+ "12-06 17:19:18.747 6598 7960 I TestRunner: at junit.framework.TestResult.runProtected(TestResult.java:133)",
+ "12-06 17:19:18.747 6598 7960 I TestRunner: at junit.framework.TestResult.run(TestResult.java:118)",
+ "12-06 17:19:18.747 6598 7960 I TestRunner: at junit.framework.TestCase.run(TestCase.java:124)",
+ "12-06 17:19:18.747 6598 7960 I TestRunner: at android.support.test.aupt.AuptTestRunner$AuptPrivateTestRunner.runTest(AuptTestRunner.java:182)",
+ "12-06 17:19:18.747 6598 7960 I TestRunner: at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:176)",
+ "12-06 17:19:18.747 6598 7960 I TestRunner: at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:555)",
+ "12-06 17:19:18.747 6598 7960 I TestRunner: at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1851)",
+ "12-06 17:19:18.747 6598 7960 I TestRunner: ----- end exception -----"
+ );
+
+ LogcatParser logcatParser = new LogcatParser("2012");
+ logcatParser.addJavaCrashTag("I", "TestRunner", LogcatParser.JAVA_CRASH);
+ LogcatItem logcat = logcatParser.parse(lines);
+ assertNotNull(logcat);
+ assertEquals(1, logcat.getEvents().size());
+ assertEquals(1, logcat.getJavaCrashes().size());
+ assertEquals(6598, logcat.getJavaCrashes().get(0).getPid().intValue());
+ assertEquals(7960, logcat.getJavaCrashes().get(0).getTid().intValue());
+ assertEquals("", logcat.getJavaCrashes().get(0).getLastPreamble());
+ assertEquals("", logcat.getJavaCrashes().get(0).getProcessPreamble());
+ // Check that lines not related to java crash are absent
+ assertFalse(logcat.getJavaCrashes().get(0).getStack().contains("begin exception"));
+ assertFalse(logcat.getJavaCrashes().get(0).getStack().contains("end exception"));
+ assertFalse(logcat.getJavaCrashes().get(0).getStack().contains("failed: testYouTube"));
+ //System.out.println(logcat.getJavaCrashes().get(0).getStack());
+ }
+
+ /**
+ * Test that Java crashes from system server can be parsed.
+ */
+ public void testParse_java_crash_system_server() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: message",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: java.lang.Exception",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)");
+
+ LogcatItem logcat = new LogcatParser("2012").parse(lines);
+ assertNotNull(logcat);
+ assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStartTime());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStopTime());
+ assertEquals(1, logcat.getEvents().size());
+ assertEquals(1, logcat.getJavaCrashes().size());
+ assertEquals("system_server", logcat.getJavaCrashes().get(0).getApp());
+ assertEquals(3064, logcat.getJavaCrashes().get(0).getPid().intValue());
+ assertEquals(3082, logcat.getJavaCrashes().get(0).getTid().intValue());
+ assertEquals("", logcat.getJavaCrashes().get(0).getLastPreamble());
+ assertEquals("", logcat.getJavaCrashes().get(0).getProcessPreamble());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"),
+ logcat.getJavaCrashes().get(0).getEventTime());
+ }
+
+ /**
+ * Test that Java crashes with process and pid can be parsed.
+ */
+ public void testParse_java_crash_process_pid() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: FATAL EXCEPTION: main",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: Process: com.android.package, PID: 1234",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: java.lang.Exception",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)");
+
+ LogcatItem logcat = new LogcatParser("2012").parse(lines);
+ assertNotNull(logcat);
+ assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStartTime());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStopTime());
+ assertEquals(1, logcat.getEvents().size());
+ assertEquals(1, logcat.getJavaCrashes().size());
+ assertEquals("com.android.package", logcat.getJavaCrashes().get(0).getApp());
+ assertEquals(1234, logcat.getJavaCrashes().get(0).getPid().intValue());
+ assertNull(logcat.getJavaCrashes().get(0).getTid());
+ assertEquals("", logcat.getJavaCrashes().get(0).getLastPreamble());
+ assertEquals("", logcat.getJavaCrashes().get(0).getProcessPreamble());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"),
+ logcat.getJavaCrashes().get(0).getEventTime());
+ }
+
+ /**
+ * Test that Java crashes with pid can be parsed.
+ */
+ public void testParse_java_crash_pid() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: FATAL EXCEPTION: main",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: PID: 1234",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: java.lang.Exception",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)");
+
+ LogcatItem logcat = new LogcatParser("2012").parse(lines);
+ assertNotNull(logcat);
+ assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStartTime());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStopTime());
+ assertEquals(1, logcat.getEvents().size());
+ assertEquals(1, logcat.getJavaCrashes().size());
+ assertNull(logcat.getJavaCrashes().get(0).getApp());
+ assertEquals(1234, logcat.getJavaCrashes().get(0).getPid().intValue());
+ assertNull(logcat.getJavaCrashes().get(0).getTid());
+ assertEquals("", logcat.getJavaCrashes().get(0).getLastPreamble());
+ assertEquals("", logcat.getJavaCrashes().get(0).getProcessPreamble());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"),
+ logcat.getJavaCrashes().get(0).getEventTime());
+ }
+
+ /**
+ * Test that Java crashes with process and pid without stack can be parsed.
+ */
+ public void testParse_java_crash_empty() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: FATAL EXCEPTION: main",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: PID: 1234");
+
+ LogcatItem logcat = new LogcatParser("2012").parse(lines);
+ assertNotNull(logcat);
+ assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStartTime());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStopTime());
+ assertEquals(0, logcat.getEvents().size());
+ assertEquals(0, logcat.getJavaCrashes().size());
+ }
+
+ /**
+ * Test that native crashes can be parsed from the info log level.
+ */
+ public void testParse_native_crash_info() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "04-25 18:33:27.273 115 115 I DEBUG : Build fingerprint: 'product:build:target'",
+ "04-25 18:33:27.273 115 115 I DEBUG : pid: 3112, tid: 3112 >>> com.google.android.browser <<<",
+ "04-25 18:33:27.273 115 115 I DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000");
+
+ LogcatItem logcat = new LogcatParser("2012").parse(lines);
+ assertNotNull(logcat);
+ assertEquals(parseTime("2012-04-25 18:33:27.273"), logcat.getStartTime());
+ assertEquals(parseTime("2012-04-25 18:33:27.273"), logcat.getStopTime());
+ assertEquals(1, logcat.getEvents().size());
+ assertEquals(1, logcat.getNativeCrashes().size());
+ assertEquals(3112, logcat.getNativeCrashes().get(0).getPid().intValue());
+ assertEquals(3112, logcat.getNativeCrashes().get(0).getTid().intValue());
+ assertEquals("com.google.android.browser", logcat.getNativeCrashes().get(0).getApp());
+ assertEquals("", logcat.getNativeCrashes().get(0).getLastPreamble());
+ assertEquals("", logcat.getNativeCrashes().get(0).getProcessPreamble());
+ assertEquals(parseTime("2012-04-25 18:33:27.273"),
+ logcat.getNativeCrashes().get(0).getEventTime());
+ }
+
+ /**
+ * Test that native crashes can be parsed from the fatal log level.
+ */
+ public void testParse_native_crash_fatal() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "04-25 18:33:27.273 115 115 F DEBUG : Build fingerprint: 'product:build:target'",
+ "04-25 18:33:27.273 115 115 F DEBUG : pid: 3112, tid: 3112, name: Name >>> com.google.android.browser <<<",
+ "04-25 18:33:27.273 115 115 F DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000");
+
+ LogcatItem logcat = new LogcatParser("2012").parse(lines);
+ assertNotNull(logcat);
+ assertEquals(parseTime("2012-04-25 18:33:27.273"), logcat.getStartTime());
+ assertEquals(parseTime("2012-04-25 18:33:27.273"), logcat.getStopTime());
+ assertEquals(1, logcat.getEvents().size());
+ assertEquals(1, logcat.getNativeCrashes().size());
+ assertEquals(3112, logcat.getNativeCrashes().get(0).getPid().intValue());
+ assertEquals(3112, logcat.getNativeCrashes().get(0).getTid().intValue());
+ assertEquals("com.google.android.browser", logcat.getNativeCrashes().get(0).getApp());
+ assertEquals("", logcat.getNativeCrashes().get(0).getLastPreamble());
+ assertEquals("", logcat.getNativeCrashes().get(0).getProcessPreamble());
+ assertEquals(parseTime("2012-04-25 18:33:27.273"),
+ logcat.getNativeCrashes().get(0).getEventTime());
+ }
+
+ /**
+ * Test that native crashes can be parsed if they have the same pid/tid.
+ */
+ public void testParse_native_crash_same_pid() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "04-25 18:33:27.273 115 115 I DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***",
+ "04-25 18:33:27.273 115 115 I DEBUG : Build fingerprint: 'product:build:target'",
+ "04-25 18:33:27.273 115 115 I DEBUG : pid: 3112, tid: 3112 >>> com.google.android.browser <<<",
+ "04-25 18:33:27.273 115 115 I DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000",
+ "04-25 18:33:27.273 115 115 I DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***",
+ "04-25 18:33:27.273 115 115 I DEBUG : Build fingerprint: 'product:build:target'",
+ "04-25 18:33:27.273 115 115 I DEBUG : pid: 3113, tid: 3113 >>> com.google.android.browser2 <<<",
+ "04-25 18:33:27.273 115 115 I DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000");
+
+ LogcatItem logcat = new LogcatParser("2012").parse(lines);
+ assertNotNull(logcat);
+ assertEquals(parseTime("2012-04-25 18:33:27.273"), logcat.getStartTime());
+ assertEquals(parseTime("2012-04-25 18:33:27.273"), logcat.getStopTime());
+ assertEquals(2, logcat.getEvents().size());
+ assertEquals(2, logcat.getNativeCrashes().size());
+ assertEquals(3112, logcat.getNativeCrashes().get(0).getPid().intValue());
+ assertEquals(3112, logcat.getNativeCrashes().get(0).getTid().intValue());
+ assertEquals("com.google.android.browser", logcat.getNativeCrashes().get(0).getApp());
+ assertEquals(3113, logcat.getNativeCrashes().get(1).getPid().intValue());
+ assertEquals(3113, logcat.getNativeCrashes().get(1).getTid().intValue());
+ assertEquals("com.google.android.browser2", logcat.getNativeCrashes().get(1).getApp());
+ }
+
+ public void testParse_misc_events() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "04-25 18:33:27.273 1676 1821 W AudioTrack: obtainBuffer timed out (is the CPU pegged?) 0x361378 user=0000116a, server=00000000",
+ "04-25 18:33:28.273 7813 7813 E gralloc : GetBufferLock timed out for thread 7813 buffer 0x61 usage 0x200 LockState 1",
+ "04-25 18:33:29.273 395 637 W Watchdog: *** WATCHDOG KILLING SYSTEM PROCESS: null");
+
+ LogcatItem logcat = new LogcatParser("2012").parse(lines);
+ assertNotNull(logcat);
+ assertEquals(parseTime("2012-04-25 18:33:27.273"), logcat.getStartTime());
+ assertEquals(parseTime("2012-04-25 18:33:29.273"), logcat.getStopTime());
+ assertEquals(3, logcat.getEvents().size());
+ assertEquals(1, logcat.getMiscEvents(LogcatParser.HIGH_CPU_USAGE).size());
+ assertEquals(1, logcat.getMiscEvents(LogcatParser.HIGH_MEMORY_USAGE).size());
+ assertEquals(1, logcat.getMiscEvents(LogcatParser.RUNTIME_RESTART).size());
+
+ MiscLogcatItem item = logcat.getMiscEvents(LogcatParser.HIGH_CPU_USAGE).get(0);
+
+ assertEquals(1676, item.getPid().intValue());
+ assertEquals(1821, item.getTid().intValue());
+ assertEquals(parseTime("2012-04-25 18:33:27.273"), item.getEventTime());
+
+ item = logcat.getMiscEvents(LogcatParser.HIGH_MEMORY_USAGE).get(0);
+
+ assertEquals(7813, item.getPid().intValue());
+ assertEquals(7813, item.getTid().intValue());
+ assertEquals(parseTime("2012-04-25 18:33:28.273"), item.getEventTime());
+
+ item = logcat.getMiscEvents(LogcatParser.RUNTIME_RESTART).get(0);
+
+ assertEquals(395, item.getPid().intValue());
+ assertEquals(637, item.getTid().intValue());
+ assertEquals(parseTime("2012-04-25 18:33:29.273"), item.getEventTime());
+ }
+
+ /**
+ * Test that multiple events can be parsed.
+ */
+ public void testParse_multiple_events() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: java.lang.Exception",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)",
+ "04-25 09:55:47.799 3065 3090 E AndroidRuntime: java.lang.Exception",
+ "04-25 09:55:47.799 3065 3090 E AndroidRuntime: \tat class.method1(Class.java:1)",
+ "04-25 09:55:47.799 3065 3090 E AndroidRuntime: \tat class.method2(Class.java:2)",
+ "04-25 09:55:47.799 3065 3090 E AndroidRuntime: \tat class.method3(Class.java:3)",
+ "04-25 17:17:08.445 312 366 E ActivityManager: ANR (application not responding) in process: com.android.package",
+ "04-25 17:17:08.445 312 366 E ActivityManager: Reason: keyDispatchingTimedOut",
+ "04-25 17:17:08.445 312 366 E ActivityManager: Load: 0.71 / 0.83 / 0.51",
+ "04-25 17:17:08.445 312 366 E ActivityManager: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait",
+ "04-25 17:17:08.445 312 366 E ActivityManager: ANR (application not responding) in process: com.android.package",
+ "04-25 17:17:08.445 312 366 E ActivityManager: Reason: keyDispatchingTimedOut",
+ "04-25 17:17:08.445 312 366 E ActivityManager: Load: 0.71 / 0.83 / 0.51",
+ "04-25 17:17:08.445 312 366 E ActivityManager: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait",
+ "04-25 18:33:27.273 115 115 I DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***",
+ "04-25 18:33:27.273 115 115 I DEBUG : Build fingerprint: 'product:build:target'",
+ "04-25 18:33:27.273 115 115 I DEBUG : pid: 3112, tid: 3112 >>> com.google.android.browser <<<",
+ "04-25 18:33:27.273 115 115 I DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000",
+ "04-25 18:33:27.273 117 117 I DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***",
+ "04-25 18:33:27.273 117 117 I DEBUG : Build fingerprint: 'product:build:target'",
+ "04-25 18:33:27.273 117 117 I DEBUG : pid: 3113, tid: 3113 >>> com.google.android.browser <<<",
+ "04-25 18:33:27.273 117 117 I DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000");
+
+
+ LogcatItem logcat = new LogcatParser("2012").parse(lines);
+ assertNotNull(logcat);
+ assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStartTime());
+ assertEquals(parseTime("2012-04-25 18:33:27.273"), logcat.getStopTime());
+ assertEquals(6, logcat.getEvents().size());
+ assertEquals(2, logcat.getAnrs().size());
+ assertEquals(2, logcat.getJavaCrashes().size());
+ assertEquals(2, logcat.getNativeCrashes().size());
+
+ assertEquals(312, logcat.getAnrs().get(0).getPid().intValue());
+ assertEquals(366, logcat.getAnrs().get(0).getTid().intValue());
+ assertEquals(parseTime("2012-04-25 17:17:08.445"), logcat.getAnrs().get(0).getEventTime());
+
+ assertEquals(312, logcat.getAnrs().get(1).getPid().intValue());
+ assertEquals(366, logcat.getAnrs().get(1).getTid().intValue());
+ assertEquals(parseTime("2012-04-25 17:17:08.445"), logcat.getAnrs().get(1).getEventTime());
+
+ assertEquals(3064, logcat.getJavaCrashes().get(0).getPid().intValue());
+ assertEquals(3082, logcat.getJavaCrashes().get(0).getTid().intValue());
+ assertEquals(
+ parseTime("2012-04-25 09:55:47.799"),
+ logcat.getJavaCrashes().get(0).getEventTime());
+
+ assertEquals(3065, logcat.getJavaCrashes().get(1).getPid().intValue());
+ assertEquals(3090, logcat.getJavaCrashes().get(1).getTid().intValue());
+ assertEquals(
+ parseTime("2012-04-25 09:55:47.799"),
+ logcat.getJavaCrashes().get(1).getEventTime());
+
+ assertEquals(3112, logcat.getNativeCrashes().get(0).getPid().intValue());
+ assertEquals(3112, logcat.getNativeCrashes().get(0).getTid().intValue());
+ assertEquals(
+ parseTime("2012-04-25 18:33:27.273"),
+ logcat.getNativeCrashes().get(0).getEventTime());
+
+ assertEquals(3113, logcat.getNativeCrashes().get(1).getPid().intValue());
+ assertEquals(3113, logcat.getNativeCrashes().get(1).getTid().intValue());
+ assertEquals(
+ parseTime("2012-04-25 18:33:27.273"),
+ logcat.getNativeCrashes().get(1).getEventTime());
+ }
+
+ /** Test that including extra uid column still parses the logs. */
+ public void testParse_uid() throws ParseException {
+ List<String> lines =
+ Arrays.asList(
+ "04-25 09:55:47.799 wifi 3064 3082 E AndroidRuntime: java.lang.Exception",
+ "04-25 09:55:47.799 wifi 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)",
+ "04-25 09:55:47.799 wifi 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)",
+ "04-25 09:55:47.799 wifi 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)",
+ "04-25 09:55:47.799 wifi 3065 3090 E AndroidRuntime: java.lang.Exception",
+ "04-25 09:55:47.799 wifi 3065 3090 E AndroidRuntime: \tat class.method1(Class.java:1)",
+ "04-25 09:55:47.799 wifi 3065 3090 E AndroidRuntime: \tat class.method2(Class.java:2)",
+ "04-25 09:55:47.799 wifi 3065 3090 E AndroidRuntime: \tat class.method3(Class.java:3)",
+ "04-25 17:17:08.445 1337 312 366 E ActivityManager: ANR (application not responding) in process: com.android.package",
+ "04-25 17:17:08.445 1337 312 366 E ActivityManager: Reason: keyDispatchingTimedOut",
+ "04-25 17:17:08.445 1337 312 366 E ActivityManager: Load: 0.71 / 0.83 / 0.51",
+ "04-25 17:17:08.445 1337 312 366 E ActivityManager: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait",
+ "04-25 17:17:08.445 1337 312 366 E ActivityManager: ANR (application not responding) in process: com.android.package",
+ "04-25 17:17:08.445 1337 312 366 E ActivityManager: Reason: keyDispatchingTimedOut",
+ "04-25 17:17:08.445 1337 312 366 E ActivityManager: Load: 0.71 / 0.83 / 0.51",
+ "04-25 17:17:08.445 1337 312 366 E ActivityManager: 33% TOTAL: 21% user + 11% kernel + 0.3% iowait",
+ "04-25 18:33:27.273 wifi123 115 115 I DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***",
+ "04-25 18:33:27.273 wifi123 115 115 I DEBUG : Build fingerprint: 'product:build:target'",
+ "04-25 18:33:27.273 wifi123 115 115 I DEBUG : pid: 3112, tid: 3112 >>> com.google.android.browser <<<",
+ "04-25 18:33:27.273 wifi123 115 115 I DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000",
+ "04-25 18:33:27.273 wifi123 117 117 I DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***",
+ "04-25 18:33:27.273 wifi123 117 117 I DEBUG : Build fingerprint: 'product:build:target'",
+ "04-25 18:33:27.273 wifi123 117 117 I DEBUG : pid: 3113, tid: 3113 >>> com.google.android.browser <<<",
+ "04-25 18:33:27.273 wifi123 117 117 I DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000");
+
+
+ LogcatItem logcat = new LogcatParser("2012").parse(lines);
+ assertNotNull(logcat);
+ assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStartTime());
+ assertEquals(parseTime("2012-04-25 18:33:27.273"), logcat.getStopTime());
+ assertEquals(6, logcat.getEvents().size());
+ assertEquals(2, logcat.getAnrs().size());
+ assertEquals(2, logcat.getJavaCrashes().size());
+ assertEquals(2, logcat.getNativeCrashes().size());
+
+ assertEquals(312, logcat.getAnrs().get(0).getPid().intValue());
+ assertEquals(366, logcat.getAnrs().get(0).getTid().intValue());
+ assertEquals(parseTime("2012-04-25 17:17:08.445"), logcat.getAnrs().get(0).getEventTime());
+
+ assertEquals(312, logcat.getAnrs().get(1).getPid().intValue());
+ assertEquals(366, logcat.getAnrs().get(1).getTid().intValue());
+ assertEquals(parseTime("2012-04-25 17:17:08.445"), logcat.getAnrs().get(1).getEventTime());
+
+ assertEquals(3064, logcat.getJavaCrashes().get(0).getPid().intValue());
+ assertEquals(3082, logcat.getJavaCrashes().get(0).getTid().intValue());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"),
+ logcat.getJavaCrashes().get(0).getEventTime());
+
+ assertEquals(3065, logcat.getJavaCrashes().get(1).getPid().intValue());
+ assertEquals(3090, logcat.getJavaCrashes().get(1).getTid().intValue());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"),
+ logcat.getJavaCrashes().get(1).getEventTime());
+
+ assertEquals(3112, logcat.getNativeCrashes().get(0).getPid().intValue());
+ assertEquals(3112, logcat.getNativeCrashes().get(0).getTid().intValue());
+ assertEquals(parseTime("2012-04-25 18:33:27.273"),
+ logcat.getNativeCrashes().get(0).getEventTime());
+
+ assertEquals(3113, logcat.getNativeCrashes().get(1).getPid().intValue());
+ assertEquals(3113, logcat.getNativeCrashes().get(1).getTid().intValue());
+ assertEquals(parseTime("2012-04-25 18:33:27.273"),
+ logcat.getNativeCrashes().get(1).getEventTime());
+ }
+
+ /**
+ * Test that multiple java crashes and native crashes can be parsed even when interleaved.
+ */
+ public void testParse_multiple_events_interleaved() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: java.lang.Exception",
+ "04-25 09:55:47.799 3065 3090 E AndroidRuntime: java.lang.Exception",
+ "04-25 09:55:47.799 115 115 I DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***",
+ "04-25 09:55:47.799 117 117 I DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)",
+ "04-25 09:55:47.799 3065 3090 E AndroidRuntime: \tat class.method1(Class.java:1)",
+ "04-25 09:55:47.799 115 115 I DEBUG : Build fingerprint: 'product:build:target'",
+ "04-25 09:55:47.799 117 117 I DEBUG : Build fingerprint: 'product:build:target'",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)",
+ "04-25 09:55:47.799 3065 3090 E AndroidRuntime: \tat class.method2(Class.java:2)",
+ "04-25 09:55:47.799 115 115 I DEBUG : pid: 3112, tid: 3112 >>> com.google.android.browser <<<",
+ "04-25 09:55:47.799 117 117 I DEBUG : pid: 3113, tid: 3113 >>> com.google.android.browser <<<",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)",
+ "04-25 09:55:47.799 3065 3090 E AndroidRuntime: \tat class.method3(Class.java:3)",
+ "04-25 09:55:47.799 115 115 I DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000",
+ "04-25 09:55:47.799 117 117 I DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000");
+
+ LogcatItem logcat = new LogcatParser("2012").parse(lines);
+ assertNotNull(logcat);
+ assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStartTime());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStopTime());
+ assertEquals(4, logcat.getEvents().size());
+ assertEquals(0, logcat.getAnrs().size());
+ assertEquals(2, logcat.getJavaCrashes().size());
+ assertEquals(2, logcat.getNativeCrashes().size());
+
+ assertEquals(3064, logcat.getJavaCrashes().get(0).getPid().intValue());
+ assertEquals(3082, logcat.getJavaCrashes().get(0).getTid().intValue());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"),
+ logcat.getJavaCrashes().get(0).getEventTime());
+
+ assertEquals(3065, logcat.getJavaCrashes().get(1).getPid().intValue());
+ assertEquals(3090, logcat.getJavaCrashes().get(1).getTid().intValue());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"),
+ logcat.getJavaCrashes().get(1).getEventTime());
+
+ assertEquals(3112, logcat.getNativeCrashes().get(0).getPid().intValue());
+ assertEquals(3112, logcat.getNativeCrashes().get(0).getTid().intValue());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"),
+ logcat.getNativeCrashes().get(0).getEventTime());
+
+ assertEquals(3113, logcat.getNativeCrashes().get(1).getPid().intValue());
+ assertEquals(3113, logcat.getNativeCrashes().get(1).getTid().intValue());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"),
+ logcat.getNativeCrashes().get(1).getEventTime());
+ }
+
+ /**
+ * Test that the preambles are set correctly.
+ */
+ public void testParse_preambles() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "04-25 09:15:47.799 123 3082 I tag: message 1",
+ "04-25 09:20:47.799 3064 3082 I tag: message 2",
+ "04-25 09:25:47.799 345 3082 I tag: message 3",
+ "04-25 09:30:47.799 3064 3082 I tag: message 4",
+ "04-25 09:35:47.799 456 3082 I tag: message 5",
+ "04-25 09:40:47.799 3064 3082 I tag: message 6",
+ "04-25 09:45:47.799 567 3082 I tag: message 7",
+ "04-25 09:50:47.799 3064 3082 I tag: message 8",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: java.lang.Exception",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)");
+
+ List<String> expectedLastPreamble = Arrays.asList(
+ "04-25 09:15:47.799 123 3082 I tag: message 1",
+ "04-25 09:20:47.799 3064 3082 I tag: message 2",
+ "04-25 09:25:47.799 345 3082 I tag: message 3",
+ "04-25 09:30:47.799 3064 3082 I tag: message 4",
+ "04-25 09:35:47.799 456 3082 I tag: message 5",
+ "04-25 09:40:47.799 3064 3082 I tag: message 6",
+ "04-25 09:45:47.799 567 3082 I tag: message 7",
+ "04-25 09:50:47.799 3064 3082 I tag: message 8");
+
+ List<String> expectedProcPreamble = Arrays.asList(
+ "04-25 09:20:47.799 3064 3082 I tag: message 2",
+ "04-25 09:30:47.799 3064 3082 I tag: message 4",
+ "04-25 09:40:47.799 3064 3082 I tag: message 6",
+ "04-25 09:50:47.799 3064 3082 I tag: message 8");
+
+ LogcatItem logcat = new LogcatParser("2012").parse(lines);
+ assertNotNull(logcat);
+ assertEquals(parseTime("2012-04-25 09:15:47.799"), logcat.getStartTime());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStopTime());
+ assertEquals(1, logcat.getEvents().size());
+ assertEquals(1, logcat.getJavaCrashes().size());
+ assertEquals(3064, logcat.getJavaCrashes().get(0).getPid().intValue());
+ assertEquals(3082, logcat.getJavaCrashes().get(0).getTid().intValue());
+ assertEquals(ArrayUtil.join("\n", expectedLastPreamble),
+ logcat.getJavaCrashes().get(0).getLastPreamble());
+ assertEquals(ArrayUtil.join("\n", expectedProcPreamble),
+ logcat.getJavaCrashes().get(0).getProcessPreamble());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"),
+ logcat.getJavaCrashes().get(0).getEventTime());
+ }
+
+ /**
+ * Test that events while the device is rebooting are ignored.
+ */
+ public void testParse_reboot() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "04-25 09:15:47.799 123 3082 I ShutdownThread: Rebooting, reason: null",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: java.lang.Exception",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)");
+
+ LogcatItem logcat = new LogcatParser("2012").parse(lines);
+ assertNotNull(logcat);
+ assertEquals(parseTime("2012-04-25 09:15:47.799"), logcat.getStartTime());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStopTime());
+ assertEquals(0, logcat.getEvents().size());
+ }
+
+ /**
+ * Test that events while the device is rebooting are ignored, but devices after the reboot are
+ * captured.
+ */
+ public void testParse_reboot_resume() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "04-25 09:15:47.799 123 3082 I ShutdownThread: Rebooting, reason: null",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: java.lang.Exception",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)",
+ "logcat interrupted. May see duplicated content in log.--------- beginning of /dev/log/main",
+ "04-25 09:59:47.799 3064 3082 E AndroidRuntime: java.lang.Exception2",
+ "04-25 09:59:47.799 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)",
+ "04-25 09:59:47.799 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)",
+ "04-25 09:59:47.799 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)");
+
+
+ LogcatItem logcat = new LogcatParser("2012").parse(lines);
+ assertNotNull(logcat);
+ assertEquals(parseTime("2012-04-25 09:15:47.799"), logcat.getStartTime());
+ assertEquals(parseTime("2012-04-25 09:59:47.799"), logcat.getStopTime());
+ assertEquals(1, logcat.getEvents().size());
+ assertEquals("java.lang.Exception2", logcat.getJavaCrashes().get(0).getException());
+
+ lines = Arrays.asList(
+ "04-25 09:15:47.799 123 3082 I ShutdownThread: Rebooting, reason: null",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: java.lang.Exception",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)",
+ "04-25 09:55:47.799 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)",
+ "logcat interrupted. May see duplicated content in log.--------- beginning of main",
+ "04-25 09:59:47.799 3064 3082 E AndroidRuntime: java.lang.Exception2",
+ "04-25 09:59:47.799 3064 3082 E AndroidRuntime: \tat class.method1(Class.java:1)",
+ "04-25 09:59:47.799 3064 3082 E AndroidRuntime: \tat class.method2(Class.java:2)",
+ "04-25 09:59:47.799 3064 3082 E AndroidRuntime: \tat class.method3(Class.java:3)");
+
+
+ logcat = new LogcatParser("2012").parse(lines);
+ assertNotNull(logcat);
+ assertEquals(parseTime("2012-04-25 09:15:47.799"), logcat.getStartTime());
+ assertEquals(parseTime("2012-04-25 09:59:47.799"), logcat.getStopTime());
+ assertEquals(1, logcat.getEvents().size());
+ assertEquals("java.lang.Exception2", logcat.getJavaCrashes().get(0).getException());
+ }
+
+ /**
+ * Test that the time logcat format can be parsed.
+ */
+ public void testParse_time() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "04-25 09:55:47.799 E/AndroidRuntime(3064): java.lang.Exception",
+ "04-25 09:55:47.799 E/AndroidRuntime(3064): \tat class.method1(Class.java:1)",
+ "04-25 09:55:47.799 E/AndroidRuntime(3064): \tat class.method2(Class.java:2)",
+ "04-25 09:55:47.799 E/AndroidRuntime(3064): \tat class.method3(Class.java:3)");
+
+ LogcatItem logcat = new LogcatParser("2012").parse(lines);
+ assertNotNull(logcat);
+ assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStartTime());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"), logcat.getStopTime());
+ assertEquals(1, logcat.getEvents().size());
+ assertEquals(1, logcat.getJavaCrashes().size());
+ assertEquals(3064, logcat.getJavaCrashes().get(0).getPid().intValue());
+ assertNull(logcat.getJavaCrashes().get(0).getTid());
+ assertEquals(parseTime("2012-04-25 09:55:47.799"),
+ logcat.getJavaCrashes().get(0).getEventTime());
+ }
+
+ /**
+ * Test that we can add and find custom patterns that match based on logcat Tags only.
+ */
+ public void testAddPattern_byTagOnly() {
+ List<String> lines = Arrays.asList(
+ "04-25 18:33:28.273 7813 7813 E HelloTag: Hello everyone!!!1",
+ "04-25 18:33:29.273 395 637 I Watchdog: find me!",
+ "04-25 18:33:39.273 395 637 W Watchdog: find me!");
+
+ LogcatParser parser = new LogcatParser("2012");
+ assertNotNull(parser);
+ parser.addPattern(null, null, "HelloTag", "HelloCategory");
+ parser.addPattern(null, null, "Watchdog", "WatchdogCategory");
+ LogcatItem logcat = parser.parse(lines);
+ assertNotNull(logcat);
+
+ /* verify that we find the HelloTag entry */
+ List<MiscLogcatItem> matchedEvents = logcat.getMiscEvents("HelloCategory");
+ assertEquals(1, matchedEvents.size());
+ assertEquals("HelloTag", matchedEvents.get(0).getTag());
+
+ /* verify that we find both Watchdog entries */
+ matchedEvents = logcat.getMiscEvents("WatchdogCategory");
+ assertEquals(2, matchedEvents.size());
+ assertEquals("Watchdog", matchedEvents.get(0).getTag());
+ }
+
+ /**
+ * Test that we can add and find custom patterns that match based on Level AND Tag.
+ */
+ public void testAddPattern_byLevelAndTagOnly() {
+ List<String> lines = Arrays.asList(
+ "04-25 18:33:28.273 7813 7813 E HelloTag: GetBufferLock timed out for thread 7813 buffer 0x61 usage 0x200 LockState 1",
+ "04-25 18:33:29.273 395 637 I Watchdog: Info",
+ "04-25 18:33:29.273 395 637 W Watchdog: Warning");
+
+ LogcatParser parser = new LogcatParser("2012");
+ assertNotNull(parser);
+ parser.addPattern(null, "I", "Watchdog", "WatchdogCategory");
+ LogcatItem logcat = parser.parse(lines);
+ assertNotNull(logcat);
+
+ List<MiscLogcatItem> matchedEvents = logcat.getMiscEvents("WatchdogCategory");
+ assertEquals(1, matchedEvents.size());
+ assertEquals("Watchdog", matchedEvents.get(0).getTag());
+ assertEquals("Info", matchedEvents.get(0).getStack());
+ }
+
+ /**
+ * Test that we can add and find custom patterns that match based on Level, Tag, AND Message.
+ */
+ public void testAddPattern_byLevelTagAndMessageOnly() {
+ List<String> lines = Arrays.asList(
+ "04-25 18:33:29.273 395 637 W Watchdog: I'm the one you need to find!",
+ "04-25 18:33:29.273 395 637 W Watchdog: my message doesn't match.",
+ "04-25 18:33:29.273 395 637 I Watchdog: my Level doesn't match, try and find me!",
+ "04-25 18:33:29.273 395 637 W NotMe: my Tag doesn't match, try and find me!");
+
+ LogcatParser parser = new LogcatParser("2012");
+ assertNotNull(parser);
+ parser.addPattern(Pattern.compile(".*find*."), "W", "Watchdog", "WatchdogCategory");
+ LogcatItem logcat = parser.parse(lines);
+ assertNotNull(logcat);
+
+ /* verify that we find the only entry that matches based on Level, Tag, AND Message */
+ List<MiscLogcatItem> matchedEvents = logcat.getMiscEvents("WatchdogCategory");
+ assertEquals(1, matchedEvents.size());
+ assertEquals("Watchdog", matchedEvents.get(0).getTag());
+ assertEquals("I'm the one you need to find!", matchedEvents.get(0).getStack());
+ }
+
+ public void testFatalException() {
+ List<String> lines = Arrays.asList(
+ "06-05 06:14:51.529 1712 1712 D AndroidRuntime: Calling main entry com.android.commands.input.Input",
+ "06-05 06:14:51.709 1712 1712 E AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: main",
+ "06-05 06:14:51.709 1712 1712 E AndroidRuntime: java.lang.NullPointerException",
+ "06-05 06:14:51.709 1712 1712 E AndroidRuntime: \tat android.hardware.input.InputManager.injectInputEvent(InputManager.java:641)",
+ "06-05 06:14:51.709 1712 1712 E AndroidRuntime: \tat com.android.commands.input.Input.injectKeyEvent(Input.java:233)",
+ "06-05 06:14:51.709 1712 1712 E AndroidRuntime: \tat com.android.commands.input.Input.sendKeyEvent(Input.java:184)",
+ "06-05 06:14:51.709 1712 1712 E AndroidRuntime: \tat com.android.commands.input.Input.run(Input.java:96)",
+ "06-05 06:14:51.709 1712 1712 E AndroidRuntime: \tat com.android.commands.input.Input.main(Input.java:59)",
+ "06-05 06:14:51.709 1712 1712 E AndroidRuntime: \tat com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)",
+ "06-05 06:14:51.709 1712 1712 E AndroidRuntime: \tat com.android.internal.os.RuntimeInit.main(RuntimeInit.java:243)",
+ "06-05 06:14:51.709 1712 1712 E AndroidRuntime: \tat dalvik.system.NativeStart.main(Native Method)");
+ LogcatParser parser = new LogcatParser("2014");
+ LogcatItem logcat = parser.parse(lines);
+ assertEquals(1, logcat.getJavaCrashes().size());
+ JavaCrashItem crash = logcat.getJavaCrashes().get(0);
+ assertEquals("com.android.commands.input.Input", crash.getApp());
+ }
+
+ /**
+ * Test that an empty input returns {@code null}.
+ */
+ public void testEmptyInput() {
+ LogcatItem item = new LogcatParser().parse(Arrays.asList(""));
+ assertNull(item);
+ }
+
+ /**
+ * Test that after clearing a parser, reusing it produces a new LogcatItem instead of
+ * appending to the previous one.
+ */
+ public void testClear() {
+ List<String> lines = Arrays.asList(
+ "04-25 18:33:28.273 7813 7813 E HelloTag: GetBufferLock timed out for thread 7813 buffer 0x61 usage 0x200 LockState 1",
+ "04-25 18:33:28.273 7813 7813 E GoodbyeTag: GetBufferLock timed out for thread 7813 buffer 0x61 usage 0x200 LockState 1"
+ );
+ LogcatParser parser = new LogcatParser();
+ LogcatItem l1 = parser.parse(lines.subList(0, 1));
+ parser.clear();
+ LogcatItem l2 = parser.parse(lines.subList(1, 2));
+ assertEquals(l1.getEvents().size(), 1);
+ assertEquals(l2.getEvents().size(), 1);
+ assertFalse(l1.getEvents().get(0).getTag().equals(l2.getEvents().get(0).getTag()));
+ }
+
+ private Date parseTime(String timeStr) throws ParseException {
+ DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+ return formatter.parse(timeStr);
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/MemHealthParserTest.java b/javatests/com/android/loganalysis/parser/MemHealthParserTest.java
new file mode 100644
index 0000000..4659b76
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/MemHealthParserTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2015 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.loganalysis.parser;
+
+import com.android.loganalysis.item.MemoryHealthItem;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tests for memory health parser.
+ */
+public class MemHealthParserTest extends TestCase {
+ public void testOneForegroundProc() {
+ List<String> lines = Arrays.asList("Foreground",
+ "com.google.android.gm",
+ "Average Native Heap: 10910",
+ "Average Dalvik Heap: 8011",
+ "Average PSS: 90454",
+ "Peak Native Heap: 11136",
+ "Peak Dalvik Heap: 9812",
+ "Peak PSS: 95161",
+ "Average Summary Java Heap: 8223",
+ "Average Summary Native Heap: 3852",
+ "Average Summary Code: 1804",
+ "Average Summary Stack: 246",
+ "Average Summary Graphics: 0",
+ "Average Summary Other: 855",
+ "Average Summary System: 9151",
+ "Average Summary Overall Pss: 24135",
+ "Count 528"
+ );
+
+ MemoryHealthItem item = new MemoryHealthParser().parse(lines);
+ Map<String, Map<String, Long>> processes = item.getForeground();
+ assertNotNull(processes);
+ assertNotNull(processes.get("com.google.android.gm"));
+ Map<String, Long> process = processes.get("com.google.android.gm");
+ assertEquals(10910, process.get("native_avg").longValue());
+ assertEquals(8011, process.get("dalvik_avg").longValue());
+ assertEquals(90454, process.get("pss_avg").longValue());
+ assertEquals(11136, process.get("native_peak").longValue());
+ assertEquals(9812, process.get("dalvik_peak").longValue());
+ assertEquals(95161, process.get("pss_peak").longValue());
+ assertEquals(8223, process.get("summary_java_heap_avg").longValue());
+ assertEquals(3852, process.get("summary_native_heap_avg").longValue());
+ assertEquals(1804, process.get("summary_code_avg").longValue());
+ assertEquals(246, process.get("summary_stack_avg").longValue());
+ assertEquals(0, process.get("summary_graphics_avg").longValue());
+ assertEquals(855, process.get("summary_other_avg").longValue());
+ assertEquals(9151, process.get("summary_system_avg").longValue());
+ assertEquals(24135, process.get("summary_overall_pss_avg").longValue());
+ }
+
+ public void testTwoForegroundProc() {
+ List<String> lines = Arrays.asList("Foreground",
+ "com.google.android.gm",
+ "Average Native Heap: 10910",
+ "Average Dalvik Heap: 8011",
+ "Average PSS: 90454",
+ "Peak Native Heap: 11136",
+ "Peak Dalvik Heap: 9812",
+ "Peak PSS: 95161",
+ "Average Summary Java Heap: 8223",
+ "Average Summary Native Heap: 3852",
+ "Average Summary Code: 1804",
+ "Average Summary Stack: 246",
+ "Average Summary Graphics: 0",
+ "Average Summary Other: 855",
+ "Average Summary System: 9151",
+ "Average Summary Overall Pss: 24135",
+ "Count 528",
+ "com.google.android.music",
+ "Average Native Heap: 1",
+ "Average Dalvik Heap: 2",
+ "Average PSS: 3",
+ "Peak Native Heap: 4",
+ "Peak Dalvik Heap: 5",
+ "Peak PSS: 6",
+ "Average Summary Java Heap: 7",
+ "Average Summary Native Heap: 8",
+ "Average Summary Code: 9",
+ "Average Summary Stack: 10",
+ "Average Summary Graphics: 11",
+ "Average Summary Other: 12",
+ "Average Summary System: 13",
+ "Average Summary Overall Pss: 14",
+ "Count 7"
+ );
+
+ MemoryHealthItem item = new MemoryHealthParser().parse(lines);
+ Map<String, Map<String, Long>> processes = item.getForeground();
+ assertEquals(2, processes.size());
+ Map<String, Long> process = processes.get("com.google.android.music");
+ assertEquals(1, process.get("native_avg").longValue());
+ assertEquals(2, process.get("dalvik_avg").longValue());
+ assertEquals(3, process.get("pss_avg").longValue());
+ assertEquals(4, process.get("native_peak").longValue());
+ assertEquals(5, process.get("dalvik_peak").longValue());
+ assertEquals(6, process.get("pss_peak").longValue());
+ assertEquals(7, process.get("summary_java_heap_avg").longValue());
+ assertEquals(8, process.get("summary_native_heap_avg").longValue());
+ assertEquals(9, process.get("summary_code_avg").longValue());
+ assertEquals(10, process.get("summary_stack_avg").longValue());
+ assertEquals(11, process.get("summary_graphics_avg").longValue());
+ assertEquals(12, process.get("summary_other_avg").longValue());
+ assertEquals(13, process.get("summary_system_avg").longValue());
+ assertEquals(14, process.get("summary_overall_pss_avg").longValue());
+ }
+
+ public void testForegroundBackgroundProc() {
+ List<String> lines = Arrays.asList("Foreground",
+ "com.google.android.gm",
+ "Average Native Heap: 10910",
+ "Average Dalvik Heap: 8011",
+ "Average PSS: 90454",
+ "Peak Native Heap: 11136",
+ "Peak Dalvik Heap: 9812",
+ "Peak PSS: 95161",
+ "Average Summary Java Heap: 8223",
+ "Average Summary Native Heap: 3852",
+ "Average Summary Code: 1804",
+ "Average Summary Stack: 246",
+ "Average Summary Graphics: 0",
+ "Average Summary Other: 855",
+ "Average Summary System: 9151",
+ "Average Summary Overall Pss: 24135",
+ "Count 528",
+ "Background",
+ "com.google.android.music",
+ "Average Native Heap: 1",
+ "Average Dalvik Heap: 2",
+ "Average PSS: 3",
+ "Peak Native Heap: 4",
+ "Peak Dalvik Heap: 5",
+ "Peak PSS: 6",
+ "Average Summary Java Heap: 7",
+ "Average Summary Native Heap: 8",
+ "Average Summary Code: 9",
+ "Average Summary Stack: 10",
+ "Average Summary Graphics: 11",
+ "Average Summary Other: 12",
+ "Average Summary System: 13",
+ "Average Summary Overall Pss: 14",
+ "Count 7"
+ );
+
+ MemoryHealthItem item = new MemoryHealthParser().parse(lines);
+ Map<String, Map<String, Long>> processes = item.getForeground();
+ assertEquals(1, processes.size());
+ assertEquals(1, item.getBackground().size());
+ Map<String, Long> process = item.getBackground().get("com.google.android.music");
+ assertEquals(1, process.get("native_avg").longValue());
+ assertEquals(2, process.get("dalvik_avg").longValue());
+ assertEquals(3, process.get("pss_avg").longValue());
+ assertEquals(4, process.get("native_peak").longValue());
+ assertEquals(5, process.get("dalvik_peak").longValue());
+ assertEquals(6, process.get("pss_peak").longValue());
+ assertEquals(7, process.get("summary_java_heap_avg").longValue());
+ assertEquals(8, process.get("summary_native_heap_avg").longValue());
+ assertEquals(9, process.get("summary_code_avg").longValue());
+ assertEquals(10, process.get("summary_stack_avg").longValue());
+ assertEquals(11, process.get("summary_graphics_avg").longValue());
+ assertEquals(12, process.get("summary_other_avg").longValue());
+ assertEquals(13, process.get("summary_system_avg").longValue());
+ assertEquals(14, process.get("summary_overall_pss_avg").longValue());
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/MemInfoParserTest.java b/javatests/com/android/loganalysis/parser/MemInfoParserTest.java
new file mode 100644
index 0000000..ab41b33
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/MemInfoParserTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.loganalysis.parser;
+
+import com.android.loganalysis.item.MemInfoItem;
+import com.android.loganalysis.util.ArrayUtil;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link MemInfoParser}
+ */
+public class MemInfoParserTest extends TestCase {
+
+ /**
+ * Test that normal input is parsed.
+ */
+ public void testMemInfoParser() {
+ List<String> inputBlock = Arrays.asList(
+ "MemTotal: 353332 kB",
+ "MemFree: 65420 kB",
+ "Buffers: 20800 kB",
+ "Cached: 86204 kB",
+ "SwapCached: 0 kB",
+ "Long: 34359640152 kB",
+ "ExtraLongIgnore: 12345678901234567890 kB");
+
+ MemInfoItem item = new MemInfoParser().parse(inputBlock);
+
+ assertEquals(6, item.size());
+ assertEquals((Long)353332l, item.get("MemTotal"));
+ assertEquals((Long)65420l, item.get("MemFree"));
+ assertEquals((Long)20800l, item.get("Buffers"));
+ assertEquals((Long)86204l, item.get("Cached"));
+ assertEquals((Long)0l, item.get("SwapCached"));
+ assertEquals((Long)34359640152l, item.get("Long"));
+ assertNull(item.get("ExtraLongIgnore"));
+ assertEquals(ArrayUtil.join("\n", inputBlock), item.getText());
+ }
+
+ /**
+ * Test that an empty input returns {@code null}.
+ */
+ public void testEmptyInput() {
+ MemInfoItem item = new MemInfoParser().parse(Arrays.asList(""));
+ assertNull(item);
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/MonkeyLogParserTest.java b/javatests/com/android/loganalysis/parser/MonkeyLogParserTest.java
new file mode 100644
index 0000000..b7e867d
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/MonkeyLogParserTest.java
@@ -0,0 +1,786 @@
+/*
+ * Copyright (C) 2012 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.loganalysis.parser;
+
+import com.android.loganalysis.item.AnrItem;
+import com.android.loganalysis.item.JavaCrashItem;
+import com.android.loganalysis.item.MonkeyLogItem;
+import com.android.loganalysis.item.MonkeyLogItem.DroppedCategory;
+import com.android.loganalysis.item.NativeCrashItem;
+import com.android.loganalysis.util.ArrayUtil;
+
+import junit.framework.TestCase;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Unit tests for {@link MonkeyLogParser}
+ */
+public class MonkeyLogParserTest extends TestCase {
+
+ /**
+ * Test that a monkey can be parsed if there are no crashes.
+ */
+ public void testParse_success() {
+ List<String> lines = Arrays.asList(
+ "# Wednesday, 04/25/2012 01:37:12 AM - device uptime = 242.13: Monkey command used for this test:",
+ "adb shell monkey -p com.google.android.browser -c android.intent.category.SAMPLE_CODE -c android.intent.category.CAR_DOCK -c android.intent.category.LAUNCHER -c android.intent.category.MONKEY -c android.intent.category.INFO --ignore-security-exceptions --throttle 100 -s 528 -v -v -v 10000 ",
+ "",
+ ":Monkey: seed=528 count=10000",
+ ":AllowPackage: com.google.android.browser",
+ ":IncludeCategory: android.intent.category.LAUNCHER",
+ ":Switch: #Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.browser/com.android.browser.BrowserActivity;end",
+ " // Allowing start of Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.google.android.browser/com.android.browser.BrowserActivity } in package com.google.android.browser",
+ "Sleeping for 100 milliseconds",
+ ":Sending Key (ACTION_DOWN): 23 // KEYCODE_DPAD_CENTER",
+ ":Sending Key (ACTION_UP): 23 // KEYCODE_DPAD_CENTER",
+ "Sleeping for 100 milliseconds",
+ ":Sending Trackball (ACTION_MOVE): 0:(-5.0,3.0)",
+ ":Sending Trackball (ACTION_MOVE): 0:(3.0,3.0)",
+ ":Sending Trackball (ACTION_MOVE): 0:(-1.0,3.0)",
+ ":Sending Trackball (ACTION_MOVE): 0:(4.0,-2.0)",
+ ":Sending Trackball (ACTION_MOVE): 0:(1.0,4.0)",
+ ":Sending Trackball (ACTION_MOVE): 0:(-4.0,2.0)",
+ " //[calendar_time:2012-04-25 01:42:20.140 system_uptime:535179]",
+ " // Sending event #9900",
+ ":Sending Trackball (ACTION_MOVE): 0:(2.0,-4.0)",
+ ":Sending Trackball (ACTION_MOVE): 0:(-2.0,0.0)",
+ ":Sending Trackball (ACTION_MOVE): 0:(2.0,2.0)",
+ ":Sending Trackball (ACTION_MOVE): 0:(-5.0,4.0)",
+ "Events injected: 10000",
+ ":Dropped: keys=5 pointers=6 trackballs=7 flips=8 rotations=9",
+ "// Monkey finished",
+ "",
+ "# Wednesday, 04/25/2012 01:42:09 AM - device uptime = 539.21: Monkey command ran for: 04:57 (mm:ss)",
+ "",
+ "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------");
+
+ MonkeyLogItem monkeyLog = new MonkeyLogParser().parse(lines);
+ assertNotNull(monkeyLog);
+ // FIXME: Add test back once time situation has been worked out.
+ // assertEquals(parseTime("2012-04-25 01:37:12"), monkeyLog.getStartTime());
+ // assertEquals(parseTime("2012-04-25 01:42:09"), monkeyLog.getStopTime());
+ assertEquals(1, monkeyLog.getPackages().size());
+ assertTrue(monkeyLog.getPackages().contains("com.google.android.browser"));
+ assertEquals(1, monkeyLog.getCategories().size());
+ assertTrue(monkeyLog.getCategories().contains("android.intent.category.LAUNCHER"));
+ assertEquals(100, monkeyLog.getThrottle());
+ assertEquals(528, monkeyLog.getSeed().intValue());
+ assertEquals(10000, monkeyLog.getTargetCount().intValue());
+ assertTrue(monkeyLog.getIgnoreSecurityExceptions());
+ assertEquals(4 * 60 * 1000 + 57 * 1000, monkeyLog.getTotalDuration().longValue());
+ assertEquals(242130, monkeyLog.getStartUptimeDuration().longValue());
+ assertEquals(539210, monkeyLog.getStopUptimeDuration().longValue());
+ assertTrue(monkeyLog.getIsFinished());
+ assertFalse(monkeyLog.getNoActivities());
+ assertEquals(9900, monkeyLog.getIntermediateCount());
+ assertEquals(10000, monkeyLog.getFinalCount().intValue());
+ assertEquals(5, monkeyLog.getDroppedCount(DroppedCategory.KEYS).intValue());
+ assertEquals(6, monkeyLog.getDroppedCount(DroppedCategory.POINTERS).intValue());
+ assertEquals(7, monkeyLog.getDroppedCount(DroppedCategory.TRACKBALLS).intValue());
+ assertEquals(8, monkeyLog.getDroppedCount(DroppedCategory.FLIPS).intValue());
+ assertEquals(9, monkeyLog.getDroppedCount(DroppedCategory.ROTATIONS).intValue());
+ assertNull(monkeyLog.getCrash());
+ }
+
+ /**
+ * Test that a monkey can be parsed if there is an ANR.
+ */
+ public void testParse_anr() {
+ List<String> lines = Arrays.asList(
+ "# Tuesday, 04/24/2012 05:23:30 PM - device uptime = 216.48: Monkey command used for this test:",
+ "adb shell monkey -p com.google.android.youtube -c android.intent.category.SAMPLE_CODE -c android.intent.category.CAR_DOCK -c android.intent.category.LAUNCHER -c android.intent.category.MONKEY -c android.intent.category.INFO --ignore-security-exceptions --throttle 100 -s 993 -v -v -v 10000 ",
+ "",
+ ":Monkey: seed=993 count=10000",
+ ":AllowPackage: com.google.android.youtube",
+ ":IncludeCategory: android.intent.category.LAUNCHER",
+ ":Switch: #Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.youtube/.app.honeycomb.Shell%24HomeActivity;end",
+ " // Allowing start of Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.google.android.youtube/.app.honeycomb.Shell$HomeActivity } in package com.google.android.youtube",
+ "Sleeping for 100 milliseconds",
+ ":Sending Key (ACTION_UP): 21 // KEYCODE_DPAD_LEFT",
+ "Sleeping for 100 milliseconds",
+ ":Sending Key (ACTION_DOWN): 22 // KEYCODE_DPAD_RIGHT",
+ ":Sending Key (ACTION_UP): 22 // KEYCODE_DPAD_RIGHT",
+ " //[calendar_time:2012-04-25 00:27:27.155 system_uptime:454996]",
+ " // Sending event #5300",
+ ":Sending Key (ACTION_UP): 19 // KEYCODE_DPAD_UP",
+ "Sleeping for 100 milliseconds",
+ ":Sending Trackball (ACTION_MOVE): 0:(4.0,3.0)",
+ ":Sending Key (ACTION_DOWN): 20 // KEYCODE_DPAD_DOWN",
+ ":Sending Key (ACTION_UP): 20 // KEYCODE_DPAD_DOWN",
+ "// NOT RESPONDING: com.google.android.youtube (pid 3301)",
+ "ANR in com.google.android.youtube (com.google.android.youtube/.app.honeycomb.phone.WatchActivity)",
+ "Reason: keyDispatchingTimedOut",
+ "Load: 1.0 / 1.05 / 0.6",
+ "CPU usage from 4794ms to -1502ms ago with 99% awake:",
+ " 18% 3301/com.google.android.youtube: 16% user + 2.3% kernel / faults: 268 minor 9 major",
+ " 13% 313/system_server: 9.2% user + 4.4% kernel / faults: 906 minor 3 major",
+ " 10% 117/surfaceflinger: 4.9% user + 5.5% kernel / faults: 1 minor",
+ " 10% 120/mediaserver: 6.8% user + 3.6% kernel / faults: 1189 minor",
+ "34% TOTAL: 19% user + 13% kernel + 0.2% iowait + 1% softirq",
+ "",
+ "procrank:",
+ "// procrank status was 0",
+ "anr traces:",
+ "",
+ "",
+ "----- pid 2887 at 2012-04-25 17:17:08 -----",
+ "Cmd line: com.google.android.youtube",
+ "",
+ "DALVIK THREADS:",
+ "(mutexes: tll=0 tsl=0 tscl=0 ghl=0)",
+ "",
+ "\"main\" prio=5 tid=1 SUSPENDED",
+ " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001",
+ " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001",
+ " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0",
+ " at class.method1(Class.java:1)",
+ " at class.method2(Class.java:2)",
+ " at class.method2(Class.java:2)",
+ "",
+ "----- end 2887 -----",
+ "// anr traces status was 0",
+ "** Monkey aborted due to error.",
+ "Events injected: 5322",
+ ":Sending rotation degree=0, persist=false",
+ ":Dropped: keys=1 pointers=0 trackballs=0 flips=0 rotations=0",
+ "## Network stats: elapsed time=252942ms (0ms mobile, 252942ms wifi, 0ms not connected)",
+ "** System appears to have crashed at event 5322 of 10000 using seed 993",
+ "",
+ "# Tuesday, 04/24/2012 05:27:44 PM - device uptime = 471.37: Monkey command ran for: 04:14 (mm:ss)",
+ "",
+ "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------",
+ "");
+
+ List<String> expectedStack = Arrays.asList(
+ "\"main\" prio=5 tid=1 SUSPENDED",
+ " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001",
+ " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001",
+ " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0",
+ " at class.method1(Class.java:1)",
+ " at class.method2(Class.java:2)",
+ " at class.method2(Class.java:2)");
+
+ MonkeyLogItem monkeyLog = new MonkeyLogParser().parse(lines);
+ assertNotNull(monkeyLog);
+ // FIXME: Add test back once time situation has been worked out.
+ // assertEquals(parseTime("2012-04-24 17:23:30"), monkeyLog.getStartTime());
+ // assertEquals(parseTime("2012-04-24 17:27:44"), monkeyLog.getStopTime());
+ assertEquals(1, monkeyLog.getPackages().size());
+ assertTrue(monkeyLog.getPackages().contains("com.google.android.youtube"));
+ assertEquals(1, monkeyLog.getCategories().size());
+ assertTrue(monkeyLog.getCategories().contains("android.intent.category.LAUNCHER"));
+ assertEquals(100, monkeyLog.getThrottle());
+ assertEquals(993, monkeyLog.getSeed().intValue());
+ assertEquals(10000, monkeyLog.getTargetCount().intValue());
+ assertTrue(monkeyLog.getIgnoreSecurityExceptions());
+ assertEquals(4 * 60 * 1000 + 14 * 1000, monkeyLog.getTotalDuration().longValue());
+ assertEquals(216480, monkeyLog.getStartUptimeDuration().longValue());
+ assertEquals(471370, monkeyLog.getStopUptimeDuration().longValue());
+ assertFalse(monkeyLog.getIsFinished());
+ assertFalse(monkeyLog.getNoActivities());
+ assertEquals(5300, monkeyLog.getIntermediateCount());
+ assertEquals(5322, monkeyLog.getFinalCount().intValue());
+ assertNotNull(monkeyLog.getCrash());
+ assertTrue(monkeyLog.getCrash() instanceof AnrItem);
+ assertEquals("com.google.android.youtube", monkeyLog.getCrash().getApp());
+ assertEquals(3301, monkeyLog.getCrash().getPid().intValue());
+ assertEquals("keyDispatchingTimedOut", ((AnrItem) monkeyLog.getCrash()).getReason());
+ assertEquals(ArrayUtil.join("\n", expectedStack),
+ ((AnrItem) monkeyLog.getCrash()).getTrace());
+ }
+
+ /**
+ * Test that a monkey can be parsed if there is a Java crash.
+ */
+ public void testParse_java_crash() {
+ List<String> lines = Arrays.asList(
+ "# Tuesday, 04/24/2012 05:05:50 PM - device uptime = 232.65: Monkey command used for this test:",
+ "adb shell monkey -p com.google.android.apps.maps -c android.intent.category.SAMPLE_CODE -c android.intent.category.CAR_DOCK -c android.intent.category.LAUNCHER -c android.intent.category.MONKEY -c android.intent.category.INFO --ignore-security-exceptions --throttle 100 -s 501 -v -v -v 10000 ",
+ "",
+ ":Monkey: seed=501 count=10000",
+ ":AllowPackage: com.google.android.apps.maps",
+ ":IncludeCategory: android.intent.category.LAUNCHER",
+ ":Switch: #Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.maps/com.google.android.maps.LatitudeActivity;end",
+ " // Allowing start of Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.google.android.apps.maps/com.google.android.maps.LatitudeActivity } in package com.google.android.apps.maps",
+ "Sleeping for 100 milliseconds",
+ ":Sending Touch (ACTION_DOWN): 0:(332.0,70.0)",
+ ":Sending Touch (ACTION_UP): 0:(332.55292,76.54678)",
+ " //[calendar_time:2012-04-25 00:06:38.419 system_uptime:280799]",
+ " // Sending event #1600",
+ ":Sending Touch (ACTION_MOVE): 0:(1052.2666,677.64594)",
+ ":Sending Touch (ACTION_UP): 0:(1054.7593,687.3757)",
+ "Sleeping for 100 milliseconds",
+ "// CRASH: com.google.android.apps.maps (pid 3161)",
+ "// Short Msg: java.lang.Exception",
+ "// Long Msg: java.lang.Exception: This is the message",
+ "// Build Label: google/yakju/maguro:JellyBean/JRN24B/338896:userdebug/dev-keys",
+ "// Build Changelist: 338896",
+ "// Build Time: 1335309051000",
+ "// java.lang.Exception: This is the message",
+ "// \tat class.method1(Class.java:1)",
+ "// \tat class.method2(Class.java:2)",
+ "// \tat class.method3(Class.java:3)",
+ "// ",
+ "** Monkey aborted due to error.",
+ "Events injected: 1649",
+ ":Sending rotation degree=0, persist=false",
+ ":Dropped: keys=0 pointers=0 trackballs=0 flips=0 rotations=0",
+ "## Network stats: elapsed time=48897ms (0ms mobile, 48897ms wifi, 0ms not connected)",
+ "** System appears to have crashed at event 1649 of 10000 using seed 501",
+ "",
+ "# Tuesday, 04/24/2012 05:06:40 PM - device uptime = 282.53: Monkey command ran for: 00:49 (mm:ss)",
+ "",
+ "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------",
+ "");
+
+ MonkeyLogItem monkeyLog = new MonkeyLogParser().parse(lines);
+ assertNotNull(monkeyLog);
+ // FIXME: Add test back once time situation has been worked out.
+ // assertEquals(parseTime("2012-04-24 17:05:50"), monkeyLog.getStartTime());
+ // assertEquals(parseTime("2012-04-24 17:06:40"), monkeyLog.getStopTime());
+ assertEquals(1, monkeyLog.getPackages().size());
+ assertTrue(monkeyLog.getPackages().contains("com.google.android.apps.maps"));
+ assertEquals(1, monkeyLog.getCategories().size());
+ assertTrue(monkeyLog.getCategories().contains("android.intent.category.LAUNCHER"));
+ assertEquals(100, monkeyLog.getThrottle());
+ assertEquals(501, monkeyLog.getSeed().intValue());
+ assertEquals(10000, monkeyLog.getTargetCount().intValue());
+ assertTrue(monkeyLog.getIgnoreSecurityExceptions());
+ assertEquals(49 * 1000, monkeyLog.getTotalDuration().longValue());
+ assertEquals(232650, monkeyLog.getStartUptimeDuration().longValue());
+ assertEquals(282530, monkeyLog.getStopUptimeDuration().longValue());
+ assertFalse(monkeyLog.getIsFinished());
+ assertFalse(monkeyLog.getNoActivities());
+ assertEquals(1600, monkeyLog.getIntermediateCount());
+ assertEquals(1649, monkeyLog.getFinalCount().intValue());
+ assertNotNull(monkeyLog.getCrash());
+ assertTrue(monkeyLog.getCrash() instanceof JavaCrashItem);
+ assertEquals("com.google.android.apps.maps", monkeyLog.getCrash().getApp());
+ assertEquals(3161, monkeyLog.getCrash().getPid().intValue());
+ assertEquals("java.lang.Exception", ((JavaCrashItem) monkeyLog.getCrash()).getException());
+ }
+
+ /**
+ * Test that a monkey can be parsed if there is a Java crash even if monkey lines are mixed in
+ * the crash.
+ */
+ public void testParse_java_crash_mixed() {
+ List<String> lines = Arrays.asList(
+ "# Tuesday, 04/24/2012 05:05:50 PM - device uptime = 232.65: Monkey command used for this test:",
+ "adb shell monkey -p com.google.android.apps.maps -c android.intent.category.SAMPLE_CODE -c android.intent.category.CAR_DOCK -c android.intent.category.LAUNCHER -c android.intent.category.MONKEY -c android.intent.category.INFO --ignore-security-exceptions --throttle 100 -s 501 -v -v -v 10000 ",
+ "",
+ ":Monkey: seed=501 count=10000",
+ ":AllowPackage: com.google.android.apps.maps",
+ ":IncludeCategory: android.intent.category.LAUNCHER",
+ ":Switch: #Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.maps/com.google.android.maps.LatitudeActivity;end",
+ " // Allowing start of Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.google.android.apps.maps/com.google.android.maps.LatitudeActivity } in package com.google.android.apps.maps",
+ "Sleeping for 100 milliseconds",
+ ":Sending Touch (ACTION_DOWN): 0:(332.0,70.0)",
+ ":Sending Touch (ACTION_UP): 0:(332.55292,76.54678)",
+ " //[calendar_time:2012-04-25 00:06:38.419 system_uptime:280799]",
+ " // Sending event #1600",
+ ":Sending Touch (ACTION_MOVE): 0:(1052.2666,677.64594)",
+ "// CRASH: com.google.android.apps.maps (pid 3161)",
+ "// Short Msg: java.lang.Exception",
+ ":Sending Touch (ACTION_UP): 0:(1054.7593,687.3757)",
+ "// Long Msg: java.lang.Exception: This is the message",
+ "// Build Label: google/yakju/maguro:JellyBean/JRN24B/338896:userdebug/dev-keys",
+ "// Build Changelist: 338896",
+ "Sleeping for 100 milliseconds",
+ "// Build Time: 1335309051000",
+ "// java.lang.Exception: This is the message",
+ "// \tat class.method1(Class.java:1)",
+ "// \tat class.method2(Class.java:2)",
+ "// \tat class.method3(Class.java:3)",
+ "// ",
+ "** Monkey aborted due to error.",
+ "Events injected: 1649",
+ ":Sending rotation degree=0, persist=false",
+ ":Dropped: keys=0 pointers=0 trackballs=0 flips=0 rotations=0",
+ "## Network stats: elapsed time=48897ms (0ms mobile, 48897ms wifi, 0ms not connected)",
+ "** System appears to have crashed at event 1649 of 10000 using seed 501",
+ "",
+ "# Tuesday, 04/24/2012 05:06:40 PM - device uptime = 282.53: Monkey command ran for: 00:49 (mm:ss)",
+ "",
+ "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------",
+ "");
+
+ MonkeyLogItem monkeyLog = new MonkeyLogParser().parse(lines);
+ assertNotNull(monkeyLog);
+ // FIXME: Add test back once time situation has been worked out.
+ // assertEquals(parseTime("2012-04-24 17:05:50"), monkeyLog.getStartTime());
+ // assertEquals(parseTime("2012-04-24 17:06:40"), monkeyLog.getStopTime());
+ assertEquals(1, monkeyLog.getPackages().size());
+ assertTrue(monkeyLog.getPackages().contains("com.google.android.apps.maps"));
+ assertEquals(1, monkeyLog.getCategories().size());
+ assertTrue(monkeyLog.getCategories().contains("android.intent.category.LAUNCHER"));
+ assertEquals(100, monkeyLog.getThrottle());
+ assertEquals(501, monkeyLog.getSeed().intValue());
+ assertEquals(10000, monkeyLog.getTargetCount().intValue());
+ assertTrue(monkeyLog.getIgnoreSecurityExceptions());
+ assertEquals(49 * 1000, monkeyLog.getTotalDuration().longValue());
+ assertEquals(232650, monkeyLog.getStartUptimeDuration().longValue());
+ assertEquals(282530, monkeyLog.getStopUptimeDuration().longValue());
+ assertFalse(monkeyLog.getIsFinished());
+ assertFalse(monkeyLog.getNoActivities());
+ assertEquals(1600, monkeyLog.getIntermediateCount());
+ assertEquals(1649, monkeyLog.getFinalCount().intValue());
+ assertNotNull(monkeyLog.getCrash());
+ assertTrue(monkeyLog.getCrash() instanceof JavaCrashItem);
+ assertEquals("com.google.android.apps.maps", monkeyLog.getCrash().getApp());
+ assertEquals(3161, monkeyLog.getCrash().getPid().intValue());
+ assertEquals("java.lang.Exception", ((JavaCrashItem) monkeyLog.getCrash()).getException());
+ }
+
+
+ /**
+ * Test that a monkey can be parsed if there is a native crash.
+ */
+ public void testParse_native_crash() {
+ List<String> lines = Arrays.asList(
+ "# Tuesday, 04/24/2012 05:05:50 PM - device uptime = 232.65: Monkey command used for this test:",
+ "adb shell monkey -p com.google.android.apps.maps -c android.intent.category.SAMPLE_CODE -c android.intent.category.CAR_DOCK -c android.intent.category.LAUNCHER -c android.intent.category.MONKEY -c android.intent.category.INFO --ignore-security-exceptions --throttle 100 -s 501 -v -v -v 10000 ",
+ "",
+ ":Monkey: seed=501 count=10000",
+ ":AllowPackage: com.google.android.apps.maps",
+ ":IncludeCategory: android.intent.category.LAUNCHER",
+ ":Switch: #Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.maps/com.google.android.maps.LatitudeActivity;end",
+ " // Allowing start of Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.google.android.apps.maps/com.google.android.maps.LatitudeActivity } in package com.google.android.apps.maps",
+ "Sleeping for 100 milliseconds",
+ ":Sending Touch (ACTION_DOWN): 0:(332.0,70.0)",
+ ":Sending Touch (ACTION_UP): 0:(332.55292,76.54678)",
+ " //[calendar_time:2012-04-25 00:06:38.419 system_uptime:280799]",
+ " // Sending event #1600",
+ ":Sending Touch (ACTION_MOVE): 0:(1052.2666,677.64594)",
+ ":Sending Touch (ACTION_UP): 0:(1054.7593,687.3757)",
+ "Sleeping for 100 milliseconds",
+ "// CRASH: com.android.chrome (pid 2162)",
+ "// Short Msg: Native crash",
+ "// Long Msg: Native crash: Segmentation fault",
+ "// Build Label: google/mantaray/manta:JellyBeanMR2/JWR02/624470:userdebug/dev-keys",
+ "// Build Changelist: 624470",
+ "// Build Time: 1364920502000",
+ "// *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***",
+ "// Build fingerprint: 'google/mantaray/manta:4.1/JRO01/12345:userdebug/dev-keys'",
+ "// Revision: '7'",
+ "// pid: 2162, tid: 2216, name: .android.chrome >>> com.android.chrome <<<",
+ "// signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr deadbaad",
+ "// r0 00000027 r1 00001000 r2 00000008 r3 deadbaad",
+ "// r4 00000000 r5 7af65e64 r6 00000000 r7 7af65ea4",
+ "// r8 401291f4 r9 00200000 sl 7784badc fp 00001401",
+ "// ip 7af65ea4 sp 7af65e60 lr 400fed6b pc 400fc2d4 cpsr 600f0030",
+ "// d0 3332303033312034 d1 6361707320737332",
+ "// d2 632e6c6f6f705f34 d3 205d29383231280a",
+ "// scr 60000010",
+ "// ",
+ "// backtrace:",
+ "// #00 pc 0001e2d4 /system/lib/libc.so",
+ "// #01 pc 0001c4bc /system/lib/libc.so (abort+4)",
+ "// #02 pc 0023a515 /system/lib/libchromeview.so",
+ "// #03 pc 006f8a27 /system/lib/libchromeview.so",
+ "// ",
+ "// stack:",
+ "// 7af65e20 77856cf8 ",
+ "// 7af65e24 7af65e64 [stack:2216]",
+ "// 7af65e28 00000014 ",
+ "// 7af65e2c 76a88e6c /system/lib/libchromeview.so",
+ "** Monkey aborted due to error.",
+ "Events injected: 1649",
+ ":Sending rotation degree=0, persist=false",
+ ":Dropped: keys=0 pointers=0 trackballs=0 flips=0 rotations=0",
+ "## Network stats: elapsed time=48897ms (0ms mobile, 48897ms wifi, 0ms not connected)",
+ "** System appears to have crashed at event 1649 of 10000 using seed 501",
+ "",
+ "# Tuesday, 04/24/2012 05:06:40 PM - device uptime = 282.53: Monkey command ran for: 00:49 (mm:ss)",
+ "",
+ "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------",
+ "");
+
+ MonkeyLogItem monkeyLog = new MonkeyLogParser().parse(lines);
+ assertNotNull(monkeyLog);
+ // FIXME: Add test back once time situation has been worked out.
+ // assertEquals(parseTime("2012-04-24 17:05:50"), monkeyLog.getStartTime());
+ // assertEquals(parseTime("2012-04-24 17:06:40"), monkeyLog.getStopTime());
+ assertEquals(1, monkeyLog.getPackages().size());
+ assertTrue(monkeyLog.getPackages().contains("com.google.android.apps.maps"));
+ assertEquals(1, monkeyLog.getCategories().size());
+ assertTrue(monkeyLog.getCategories().contains("android.intent.category.LAUNCHER"));
+ assertEquals(100, monkeyLog.getThrottle());
+ assertEquals(501, monkeyLog.getSeed().intValue());
+ assertEquals(10000, monkeyLog.getTargetCount().intValue());
+ assertTrue(monkeyLog.getIgnoreSecurityExceptions());
+ assertEquals(49 * 1000, monkeyLog.getTotalDuration().longValue());
+ assertEquals(232650, monkeyLog.getStartUptimeDuration().longValue());
+ assertEquals(282530, monkeyLog.getStopUptimeDuration().longValue());
+ assertFalse(monkeyLog.getIsFinished());
+ assertFalse(monkeyLog.getNoActivities());
+ assertEquals(1600, monkeyLog.getIntermediateCount());
+ assertEquals(1649, monkeyLog.getFinalCount().intValue());
+ assertNotNull(monkeyLog.getCrash());
+ assertTrue(monkeyLog.getCrash() instanceof NativeCrashItem);
+ assertEquals("com.android.chrome", monkeyLog.getCrash().getApp());
+ assertEquals(2162, monkeyLog.getCrash().getPid().intValue());
+ assertEquals("google/mantaray/manta:4.1/JRO01/12345:userdebug/dev-keys",
+ ((NativeCrashItem) monkeyLog.getCrash()).getFingerprint());
+ // Make sure that the entire stack is included.
+ assertEquals(23, ((NativeCrashItem) monkeyLog.getCrash()).getStack().split("\n").length);
+ }
+
+ /**
+ * Test that a monkey can be parsed if there is a native crash with extra info at the end.
+ */
+ public void testParse_native_crash_strip_extra() {
+ List<String> lines = Arrays.asList(
+ "# Tuesday, 04/24/2012 05:05:50 PM - device uptime = 232.65: Monkey command used for this test:",
+ "adb shell monkey -p com.google.android.apps.maps -c android.intent.category.SAMPLE_CODE -c android.intent.category.CAR_DOCK -c android.intent.category.LAUNCHER -c android.intent.category.MONKEY -c android.intent.category.INFO --ignore-security-exceptions --throttle 100 -s 501 -v -v -v 10000 ",
+ "",
+ ":Monkey: seed=501 count=10000",
+ ":AllowPackage: com.google.android.apps.maps",
+ ":IncludeCategory: android.intent.category.LAUNCHER",
+ ":Switch: #Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.maps/com.google.android.maps.LatitudeActivity;end",
+ " // Allowing start of Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.google.android.apps.maps/com.google.android.maps.LatitudeActivity } in package com.google.android.apps.maps",
+ "Sleeping for 100 milliseconds",
+ ":Sending Touch (ACTION_DOWN): 0:(332.0,70.0)",
+ ":Sending Touch (ACTION_UP): 0:(332.55292,76.54678)",
+ " //[calendar_time:2012-04-25 00:06:38.419 system_uptime:280799]",
+ " // Sending event #1600",
+ ":Sending Touch (ACTION_MOVE): 0:(1052.2666,677.64594)",
+ ":Sending Touch (ACTION_UP): 0:(1054.7593,687.3757)",
+ "Sleeping for 100 milliseconds",
+ "// CRASH: com.android.chrome (pid 2162)",
+ "// Short Msg: Native crash",
+ "// Long Msg: Native crash: Segmentation fault",
+ "// Build Label: google/mantaray/manta:JellyBeanMR2/JWR02/624470:userdebug/dev-keys",
+ "// Build Changelist: 624470",
+ "// Build Time: 1364920502000",
+ "// *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***",
+ "// Build fingerprint: 'google/mantaray/manta:4.1/JRO01/12345:userdebug/dev-keys'",
+ "// Revision: '7'",
+ "// pid: 2162, tid: 2216, name: .android.chrome >>> com.android.chrome <<<",
+ "// signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr deadbaad",
+ "// r0 00000027 r1 00001000 r2 00000008 r3 deadbaad",
+ "// r4 00000000 r5 7af65e64 r6 00000000 r7 7af65ea4",
+ "// r8 401291f4 r9 00200000 sl 7784badc fp 00001401",
+ "// ip 7af65ea4 sp 7af65e60 lr 400fed6b pc 400fc2d4 cpsr 600f0030",
+ "// d0 3332303033312034 d1 6361707320737332",
+ "// d2 632e6c6f6f705f34 d3 205d29383231280a",
+ "// scr 60000010",
+ "// ",
+ "// backtrace:",
+ "// #00 pc 0001e2d4 /system/lib/libc.so",
+ "// #01 pc 0001c4bc /system/lib/libc.so (abort+4)",
+ "// #02 pc 0023a515 /system/lib/libchromeview.so",
+ "// #03 pc 006f8a27 /system/lib/libchromeview.so",
+ "// ",
+ "// stack:",
+ "// 7af65e20 77856cf8 ",
+ "// 7af65e24 7af65e64 [stack:2216]",
+ "// 7af65e28 00000014 ",
+ "// 7af65e2c 76a88e6c /system/lib/libchromeview.so",
+ "// ** New native crash detected.",
+ "** Monkey aborted due to error.",
+ "Events injected: 1649",
+ ":Sending rotation degree=0, persist=false",
+ ":Dropped: keys=0 pointers=0 trackballs=0 flips=0 rotations=0",
+ "## Network stats: elapsed time=48897ms (0ms mobile, 48897ms wifi, 0ms not connected)",
+ "** System appears to have crashed at event 1649 of 10000 using seed 501",
+ "",
+ "# Tuesday, 04/24/2012 05:06:40 PM - device uptime = 282.53: Monkey command ran for: 00:49 (mm:ss)",
+ "",
+ "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------",
+ "");
+
+ MonkeyLogItem monkeyLog = new MonkeyLogParser().parse(lines);
+ assertNotNull(monkeyLog);
+ // FIXME: Add test back once time situation has been worked out.
+ // assertEquals(parseTime("2012-04-24 17:05:50"), monkeyLog.getStartTime());
+ // assertEquals(parseTime("2012-04-24 17:06:40"), monkeyLog.getStopTime());
+ assertEquals(1, monkeyLog.getPackages().size());
+ assertTrue(monkeyLog.getPackages().contains("com.google.android.apps.maps"));
+ assertEquals(1, monkeyLog.getCategories().size());
+ assertTrue(monkeyLog.getCategories().contains("android.intent.category.LAUNCHER"));
+ assertEquals(100, monkeyLog.getThrottle());
+ assertEquals(501, monkeyLog.getSeed().intValue());
+ assertEquals(10000, monkeyLog.getTargetCount().intValue());
+ assertTrue(monkeyLog.getIgnoreSecurityExceptions());
+ assertEquals(49 * 1000, monkeyLog.getTotalDuration().longValue());
+ assertEquals(232650, monkeyLog.getStartUptimeDuration().longValue());
+ assertEquals(282530, monkeyLog.getStopUptimeDuration().longValue());
+ assertFalse(monkeyLog.getIsFinished());
+ assertFalse(monkeyLog.getNoActivities());
+ assertEquals(1600, monkeyLog.getIntermediateCount());
+ assertEquals(1649, monkeyLog.getFinalCount().intValue());
+ assertNotNull(monkeyLog.getCrash());
+ assertTrue(monkeyLog.getCrash() instanceof NativeCrashItem);
+ assertEquals("com.android.chrome", monkeyLog.getCrash().getApp());
+ assertEquals(2162, monkeyLog.getCrash().getPid().intValue());
+ NativeCrashItem nc = (NativeCrashItem) monkeyLog.getCrash();
+ assertEquals("google/mantaray/manta:4.1/JRO01/12345:userdebug/dev-keys",
+ nc.getFingerprint());
+ // Make sure that the stack with the last line stripped is included.
+ assertEquals(23, nc.getStack().split("\n").length);
+ assertFalse(nc.getStack().contains("New native crash detected"));
+ }
+
+ /**
+ * Test that a monkey can be parsed if there is an empty native crash.
+ */
+ public void testParse_native_crash_empty() {
+ List<String> lines = Arrays.asList(
+ "# Tuesday, 04/24/2012 05:05:50 PM - device uptime = 232.65: Monkey command used for this test:",
+ "adb shell monkey -p com.google.android.apps.maps -c android.intent.category.SAMPLE_CODE -c android.intent.category.CAR_DOCK -c android.intent.category.LAUNCHER -c android.intent.category.MONKEY -c android.intent.category.INFO --ignore-security-exceptions --throttle 100 -s 501 -v -v -v 10000 ",
+ "",
+ ":Monkey: seed=501 count=10000",
+ ":AllowPackage: com.google.android.apps.maps",
+ ":IncludeCategory: android.intent.category.LAUNCHER",
+ ":Switch: #Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.maps/com.google.android.maps.LatitudeActivity;end",
+ " // Allowing start of Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.google.android.apps.maps/com.google.android.maps.LatitudeActivity } in package com.google.android.apps.maps",
+ "Sleeping for 100 milliseconds",
+ ":Sending Touch (ACTION_DOWN): 0:(332.0,70.0)",
+ ":Sending Touch (ACTION_UP): 0:(332.55292,76.54678)",
+ " //[calendar_time:2012-04-25 00:06:38.419 system_uptime:280799]",
+ " // Sending event #1600",
+ ":Sending Touch (ACTION_MOVE): 0:(1052.2666,677.64594)",
+ ":Sending Touch (ACTION_UP): 0:(1054.7593,687.3757)",
+ "Sleeping for 100 milliseconds",
+ "** New native crash detected.",
+ "** Monkey aborted due to error.",
+ "Events injected: 1649",
+ ":Sending rotation degree=0, persist=false",
+ ":Dropped: keys=0 pointers=0 trackballs=0 flips=0 rotations=0",
+ "## Network stats: elapsed time=48897ms (0ms mobile, 48897ms wifi, 0ms not connected)",
+ "** System appears to have crashed at event 1649 of 10000 using seed 501",
+ "",
+ "# Tuesday, 04/24/2012 05:06:40 PM - device uptime = 282.53: Monkey command ran for: 00:49 (mm:ss)",
+ "",
+ "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------",
+ "");
+
+ MonkeyLogItem monkeyLog = new MonkeyLogParser().parse(lines);
+ assertNotNull(monkeyLog);
+ // FIXME: Add test back once time situation has been worked out.
+ // assertEquals(parseTime("2012-04-24 17:05:50"), monkeyLog.getStartTime());
+ // assertEquals(parseTime("2012-04-24 17:06:40"), monkeyLog.getStopTime());
+ assertEquals(1, monkeyLog.getPackages().size());
+ assertTrue(monkeyLog.getPackages().contains("com.google.android.apps.maps"));
+ assertEquals(1, monkeyLog.getCategories().size());
+ assertTrue(monkeyLog.getCategories().contains("android.intent.category.LAUNCHER"));
+ assertEquals(100, monkeyLog.getThrottle());
+ assertEquals(501, monkeyLog.getSeed().intValue());
+ assertEquals(10000, monkeyLog.getTargetCount().intValue());
+ assertTrue(monkeyLog.getIgnoreSecurityExceptions());
+ assertEquals(49 * 1000, monkeyLog.getTotalDuration().longValue());
+ assertEquals(232650, monkeyLog.getStartUptimeDuration().longValue());
+ assertEquals(282530, monkeyLog.getStopUptimeDuration().longValue());
+ assertFalse(monkeyLog.getIsFinished());
+ assertFalse(monkeyLog.getNoActivities());
+ assertEquals(1600, monkeyLog.getIntermediateCount());
+ assertEquals(1649, monkeyLog.getFinalCount().intValue());
+ assertNotNull(monkeyLog.getCrash());
+ assertTrue(monkeyLog.getCrash() instanceof NativeCrashItem);
+ assertNull(monkeyLog.getCrash().getApp());
+ assertNull(monkeyLog.getCrash().getPid());
+ NativeCrashItem nc = (NativeCrashItem) monkeyLog.getCrash();
+ assertNull(nc.getFingerprint());
+ assertEquals("", nc.getStack());
+ }
+
+ /**
+ * Test that a monkey can be parsed if there are no activities to run.
+ */
+ public void testParse_no_activities() {
+ List<String> lines = Arrays.asList(
+ "# Wednesday, 04/25/2012 01:37:12 AM - device uptime = 242.13: Monkey command used for this test:",
+ "adb shell monkey -p com.google.android.browser -c android.intent.category.SAMPLE_CODE -c android.intent.category.CAR_DOCK -c android.intent.category.LAUNCHER -c android.intent.category.MONKEY -c android.intent.category.INFO --ignore-security-exceptions --throttle 100 -s 528 -v -v -v 10000 ",
+ "",
+ ":Monkey: seed=528 count=10000",
+ ":AllowPackage: com.google.android.browser",
+ ":IncludeCategory: android.intent.category.LAUNCHER",
+ "** No activities found to run, monkey aborted.",
+ "",
+ "# Wednesday, 04/25/2012 01:42:09 AM - device uptime = 539.21: Monkey command ran for: 04:57 (mm:ss)",
+ "",
+ "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------");
+
+ MonkeyLogItem monkeyLog = new MonkeyLogParser().parse(lines);
+ assertNotNull(monkeyLog);
+ // FIXME: Add test back once time situation has been worked out.
+ // assertEquals(parseTime("2012-04-25 01:37:12"), monkeyLog.getStartTime());
+ // assertEquals(parseTime("2012-04-25 01:42:09"), monkeyLog.getStopTime());
+ assertEquals(1, monkeyLog.getPackages().size());
+ assertTrue(monkeyLog.getPackages().contains("com.google.android.browser"));
+ assertEquals(1, monkeyLog.getCategories().size());
+ assertTrue(monkeyLog.getCategories().contains("android.intent.category.LAUNCHER"));
+ assertEquals(100, monkeyLog.getThrottle());
+ assertEquals(528, monkeyLog.getSeed().intValue());
+ assertEquals(10000, monkeyLog.getTargetCount().intValue());
+ assertTrue(monkeyLog.getIgnoreSecurityExceptions());
+ assertEquals(4 * 60 * 1000 + 57 * 1000, monkeyLog.getTotalDuration().longValue());
+ assertEquals(242130, monkeyLog.getStartUptimeDuration().longValue());
+ assertEquals(539210, monkeyLog.getStopUptimeDuration().longValue());
+ assertFalse(monkeyLog.getIsFinished());
+ assertTrue(monkeyLog.getNoActivities());
+ assertEquals(0, monkeyLog.getIntermediateCount());
+ assertNull(monkeyLog.getFinalCount());
+ assertNull(monkeyLog.getDroppedCount(DroppedCategory.KEYS));
+ assertNull(monkeyLog.getDroppedCount(DroppedCategory.POINTERS));
+ assertNull(monkeyLog.getDroppedCount(DroppedCategory.TRACKBALLS));
+ assertNull(monkeyLog.getDroppedCount(DroppedCategory.FLIPS));
+ assertNull(monkeyLog.getDroppedCount(DroppedCategory.ROTATIONS));
+ assertNull(monkeyLog.getCrash());
+ }
+
+ /**
+ * Test that a monkey can be parsed if there is an ANR in the middle of the traces.
+ */
+ public void testParse_malformed_anr() {
+ List<String> lines = Arrays.asList(
+ "# Tuesday, 04/24/2012 05:23:30 PM - device uptime = 216.48: Monkey command used for this test:",
+ "adb shell monkey -p com.google.android.youtube -c android.intent.category.SAMPLE_CODE -c android.intent.category.CAR_DOCK -c android.intent.category.LAUNCHER -c android.intent.category.MONKEY -c android.intent.category.INFO --ignore-security-exceptions --throttle 100 -s 993 -v -v -v 10000 ",
+ "",
+ ":Monkey: seed=993 count=10000",
+ ":AllowPackage: com.google.android.youtube",
+ ":IncludeCategory: android.intent.category.LAUNCHER",
+ ":Switch: #Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.youtube/.app.honeycomb.Shell%24HomeActivity;end",
+ " // Allowing start of Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.google.android.youtube/.app.honeycomb.Shell$HomeActivity } in package com.google.android.youtube",
+ "Sleeping for 100 milliseconds",
+ ":Sending Key (ACTION_UP): 21 // KEYCODE_DPAD_LEFT",
+ "Sleeping for 100 milliseconds",
+ ":Sending Key (ACTION_DOWN): 22 // KEYCODE_DPAD_RIGHT",
+ ":Sending Key (ACTION_UP): 22 // KEYCODE_DPAD_RIGHT",
+ " //[calendar_time:2012-04-25 00:27:27.155 system_uptime:454996]",
+ " // Sending event #5300",
+ ":Sending Key (ACTION_UP): 19 // KEYCODE_DPAD_UP",
+ "Sleeping for 100 milliseconds",
+ ":Sending Trackball (ACTION_MOVE): 0:(4.0,3.0)",
+ ":Sending Key (ACTION_DOWN): 20 // KEYCODE_DPAD_DOWN",
+ ":Sending Key (ACTION_UP): 20 // KEYCODE_DPAD_DOWN",
+ "// NOT RESPONDING: com.google.android.youtube (pid 0)",
+ "ANR in com.google.android.youtube (com.google.android.youtube/.app.honeycomb.phone.WatchActivity)",
+ "PID: 3301",
+ "Reason: keyDispatchingTimedOut",
+ "Load: 1.0 / 1.05 / 0.6",
+ "CPU usage from 4794ms to -1502ms ago with 99% awake:",
+ " 18% 3301/com.google.android.youtube: 16% user + 2.3% kernel / faults: 268 minor 9 major",
+ " 13% 313/system_server: 9.2% user + 4.4% kernel / faults: 906 minor 3 major",
+ " 10% 117/surfaceflinger: 4.9% user + 5.5% kernel / faults: 1 minor",
+ " 10% 120/mediaserver: 6.8% user + 3.6% kernel / faults: 1189 minor",
+ "34% TOTAL: 19% user + 13% kernel + 0.2% iowait + 1% softirq",
+ "",
+ "procrank:",
+ "// procrank status was 0",
+ "anr traces:",
+ "",
+ "",
+ "----- pid 3301 at 2012-04-25 17:17:08 -----",
+ "Cmd line: com.google.android.youtube",
+ "",
+ "DALVIK THREADS:",
+ "(mutexes: tll=0 tsl=0 tscl=0 ghl=0)",
+ "",
+ "\"main\" prio=5 tid=1 SUSPENDED",
+ " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001",
+ " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001",
+ " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0",
+ " at class.method1(Class.java:1)",
+ "// NOT RESPONDING: com.google.android.youtube (pid 3302)",
+ "ANR in com.google.android.youtube (com.google.android.youtube/.app.honeycomb.phone.WatchActivity)",
+ "Reason: keyDispatchingTimedOut",
+ "Load: 1.0 / 1.05 / 0.6",
+ "CPU usage from 4794ms to -1502ms ago with 99% awake:",
+ " 18% 3301/com.google.android.youtube: 16% user + 2.3% kernel / faults: 268 minor 9 major",
+ " 13% 313/system_server: 9.2% user + 4.4% kernel / faults: 906 minor 3 major",
+ " 10% 117/surfaceflinger: 4.9% user + 5.5% kernel / faults: 1 minor",
+ " 10% 120/mediaserver: 6.8% user + 3.6% kernel / faults: 1189 minor",
+ "34% TOTAL: 19% user + 13% kernel + 0.2% iowait + 1% softirq",
+ "",
+ " at class.method2(Class.java:2)",
+ " at class.method2(Class.java:2)",
+ "",
+ "----- end 3301 -----",
+ "// anr traces status was 0",
+ "** Monkey aborted due to error.",
+ "Events injected: 5322",
+ ":Sending rotation degree=0, persist=false",
+ ":Dropped: keys=1 pointers=0 trackballs=0 flips=0 rotations=0",
+ "## Network stats: elapsed time=252942ms (0ms mobile, 252942ms wifi, 0ms not connected)",
+ "** System appears to have crashed at event 5322 of 10000 using seed 993",
+ "",
+ "# Tuesday, 04/24/2012 05:27:44 PM - device uptime = 471.37: Monkey command ran for: 04:14 (mm:ss)",
+ "",
+ "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------",
+ "");
+
+ MonkeyLogItem monkeyLog = new MonkeyLogParser().parse(lines);
+ assertNotNull(monkeyLog);
+ // FIXME: Add test back once time situation has been worked out.
+ // assertEquals(parseTime("2012-04-24 17:23:30"), monkeyLog.getStartTime());
+ // assertEquals(parseTime("2012-04-24 17:27:44"), monkeyLog.getStopTime());
+ assertEquals(1, monkeyLog.getPackages().size());
+ assertTrue(monkeyLog.getPackages().contains("com.google.android.youtube"));
+ assertEquals(1, monkeyLog.getCategories().size());
+ assertTrue(monkeyLog.getCategories().contains("android.intent.category.LAUNCHER"));
+ assertEquals(100, monkeyLog.getThrottle());
+ assertEquals(993, monkeyLog.getSeed().intValue());
+ assertEquals(10000, monkeyLog.getTargetCount().intValue());
+ assertTrue(monkeyLog.getIgnoreSecurityExceptions());
+ assertEquals(4 * 60 * 1000 + 14 * 1000, monkeyLog.getTotalDuration().longValue());
+ assertEquals(216480, monkeyLog.getStartUptimeDuration().longValue());
+ assertEquals(471370, monkeyLog.getStopUptimeDuration().longValue());
+ assertFalse(monkeyLog.getIsFinished());
+ assertFalse(monkeyLog.getNoActivities());
+ assertEquals(5300, monkeyLog.getIntermediateCount());
+ assertEquals(5322, monkeyLog.getFinalCount().intValue());
+ assertNotNull(monkeyLog.getCrash());
+ assertTrue(monkeyLog.getCrash() instanceof AnrItem);
+ assertEquals("com.google.android.youtube", monkeyLog.getCrash().getApp());
+ assertEquals(3301, monkeyLog.getCrash().getPid().intValue());
+ assertEquals("keyDispatchingTimedOut", ((AnrItem) monkeyLog.getCrash()).getReason());
+ }
+
+ /**
+ * Test that the other date format can be parsed.
+ */
+ public void testAlternateDateFormat() {
+ List<String> lines = Arrays.asList(
+ "# Tue Apr 24 17:05:50 PST 2012 - device uptime = 232.65: Monkey command used for this test:",
+ "adb shell monkey -p com.google.android.apps.maps -c android.intent.category.SAMPLE_CODE -c android.intent.category.CAR_DOCK -c android.intent.category.LAUNCHER -c android.intent.category.MONKEY -c android.intent.category.INFO --ignore-security-exceptions --throttle 100 -s 501 -v -v -v 10000 ",
+ "",
+ "# Tue Apr 24 17:06:40 PST 2012 - device uptime = 282.53: Monkey command ran for: 00:49 (mm:ss)",
+ "",
+ "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------",
+ "");
+
+ MonkeyLogItem monkeyLog = new MonkeyLogParser().parse(lines);
+ assertNotNull(monkeyLog);
+ // FIXME: Add test back once time situation has been worked out.
+ // assertEquals(parseTime("2012-04-24 17:05:50"), monkeyLog.getStartTime());
+ // assertEquals(parseTime("2012-04-24 17:06:40"), monkeyLog.getStopTime());
+ }
+
+ @SuppressWarnings("unused")
+ private Date parseTime(String timeStr) throws ParseException {
+ DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ return formatter.parse(timeStr);
+ }
+}
+
diff --git a/javatests/com/android/loganalysis/parser/NativeCrashParserTest.java b/javatests/com/android/loganalysis/parser/NativeCrashParserTest.java
new file mode 100644
index 0000000..6c5cf98
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/NativeCrashParserTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2012 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.loganalysis.parser;
+
+import com.android.loganalysis.item.NativeCrashItem;
+import com.android.loganalysis.util.ArrayUtil;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link NativeCrashParser}.
+ */
+public class NativeCrashParserTest extends TestCase {
+
+ /**
+ * Test that native crashes are parsed.
+ */
+ public void testParseage() {
+ List<String> lines = Arrays.asList(
+ "Build fingerprint: 'google/soju/crespo:4.0.4/IMM76D/299849:userdebug/test-keys'",
+ "pid: 2058, tid: 2523 >>> com.google.android.browser <<<",
+ "signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000",
+ " r0 00000000 r1 007d9064 r2 007d9063 r3 00000004",
+ " r4 006bf518 r5 0091e3b0 r6 00000000 r7 9e3779b9",
+ " r8 000006c1 r9 000006c3 10 00000000 fp 67d246c1",
+ " ip d2363b58 sp 50ed71d8 lr 4edfc89b pc 4edfc6a0 cpsr 20000030",
+ " d0 00640065005f0065 d1 0072006f00740069",
+ " d2 00730075006e006b d3 0066006900670000",
+ " d4 00e6d48800e6d3b8 d5 02d517a000e6d518",
+ " d6 0000270f02d51860 d7 0000000002d51a80",
+ " d8 41d3dc5261e7893b d9 3fa999999999999a",
+ " d10 0000000000000000 d11 0000000000000000",
+ " d12 0000000000000000 d13 0000000000000000",
+ " d14 0000000000000000 d15 0000000000000000",
+ " d16 4070000000000000 d17 40c3878000000000",
+ " d18 412310f000000000 d19 3f91800dedacf040",
+ " d20 0000000000000000 d21 0000000000000000",
+ " d22 4010000000000000 d23 0000000000000000",
+ " d24 3ff0000000000000 d25 0000000000000000",
+ " d26 0000000000000000 d27 8000000000000000",
+ " d28 0000000000000000 d29 3ff0000000000000",
+ " d30 0000000000000000 d31 3ff0000000000000",
+ " scr 20000013",
+ "",
+ " #00 pc 001236a0 /system/lib/libwebcore.so",
+ " #01 pc 00123896 /system/lib/libwebcore.so",
+ " #02 pc 00123932 /system/lib/libwebcore.so",
+ " #03 pc 00123e3a /system/lib/libwebcore.so",
+ " #04 pc 00123e84 /system/lib/libwebcore.so",
+ " #05 pc 003db92a /system/lib/libwebcore.so",
+ " #06 pc 003dd01c /system/lib/libwebcore.so",
+ " #07 pc 002ffb92 /system/lib/libwebcore.so",
+ " #08 pc 0031c120 /system/lib/libwebcore.so",
+ " #09 pc 0031c134 /system/lib/libwebcore.so",
+ " #10 pc 0013fb98 /system/lib/libwebcore.so",
+ " #11 pc 0015b026 /system/lib/libwebcore.so",
+ " #12 pc 0015b164 /system/lib/libwebcore.so",
+ " #13 pc 0015f4cc /system/lib/libwebcore.so",
+ " #14 pc 00170472 /system/lib/libwebcore.so",
+ " #15 pc 0016ecb6 /system/lib/libwebcore.so",
+ " #16 pc 0027120e /system/lib/libwebcore.so",
+ " #17 pc 0026efec /system/lib/libwebcore.so",
+ " #18 pc 0026fcd8 /system/lib/libwebcore.so",
+ " #19 pc 00122efa /system/lib/libwebcore.so",
+ "",
+ "code around pc:",
+ "4edfc680 4a14b5f7 0601f001 23000849 3004f88d ...J....I..#...0",
+ "4edfc690 460a9200 3006f8ad e00e4603 3a019f00 ...F...0.F.....:",
+ "4edfc6a0 5c04f833 f83319ed 042c7c02 2cc7ea84 3..\\..3..|,....,",
+ "4edfc6b0 0405ea8c 24d4eb04 33049400 d1ed2a00 .......$...3.*..",
+ "4edfc6c0 f830b126 46681021 ff72f7ff f7ff4668 &.0.!.hF..r.hF..",
+ "",
+ "code around lr:",
+ "4edfc878 f9caf7ff 60209e03 9605e037 5b04f856 ...... `7...V..[",
+ "4edfc888 d0302d00 d13b1c6b 68a8e02d f7ff6869 .-0.k.;.-..hih..",
+ "4edfc898 6128fef3 b010f8d5 99022500 ea0146aa ..(a.....%...F..",
+ "4edfc8a8 9b01080b 0788eb03 3028f853 b9bdb90b ........S.(0....",
+ "4edfc8b8 3301e015 4638d005 f7ff9905 b970ff15 ...3..8F......p.",
+ "",
+ "stack:",
+ " 50ed7198 01d02c08 [heap]",
+ " 50ed719c 40045881 /system/lib/libc.so",
+ " 50ed71a0 400784c8",
+ " 50ed71a4 400784c8",
+ " 50ed71a8 02b40c68 [heap]",
+ " 50ed71ac 02b40c90 [heap]",
+ " 50ed71b0 50ed7290",
+ " 50ed71b4 006bf518 [heap]",
+ " 50ed71b8 00010000",
+ " 50ed71bc 50ed72a4",
+ " 50ed71c0 7da5a695",
+ " 50ed71c4 50ed7290",
+ " 50ed71c8 00000000",
+ " 50ed71cc 00000008",
+ " 50ed71d0 df0027ad",
+ " 50ed71d4 00000000",
+ "#00 50ed71d8 9e3779b9",
+ " 50ed71dc 00002000",
+ " 50ed71e0 00004000",
+ " 50ed71e4 006bf518 [heap]",
+ " 50ed71e8 0091e3b0 [heap]",
+ " 50ed71ec 01d72588 [heap]",
+ " 50ed71f0 00000000",
+ " 50ed71f4 4edfc89b /system/lib/libwebcore.so",
+ "#01 50ed71f8 01d70a78 [heap]",
+ " 50ed71fc 02b6afa8 [heap]",
+ " 50ed7200 00003fff",
+ " 50ed7204 01d70a78 [heap]",
+ " 50ed7208 00004000",
+ " 50ed720c 01d72584 [heap]",
+ " 50ed7210 00000000",
+ " 50ed7214 00000006",
+ " 50ed7218 006bf518 [heap]",
+ " 50ed721c 50ed72a4",
+ " 50ed7220 7da5a695",
+ " 50ed7224 50ed7290",
+ " 50ed7228 000016b8",
+ " 50ed722c 00000008",
+ " 50ed7230 01d70a78 [heap]",
+ " 50ed7234 4edfc937 /system/lib/libwebcore.so",
+ "debuggerd committing suicide to free the zombie!",
+ "debuggerd");
+
+ NativeCrashItem nc = new NativeCrashParser().parse(lines);
+ assertNotNull(nc);
+ assertEquals(2058, nc.getPid().intValue());
+ assertEquals(2523, nc.getTid().intValue());
+ assertEquals("com.google.android.browser", nc.getApp());
+ assertEquals("google/soju/crespo:4.0.4/IMM76D/299849:userdebug/test-keys",
+ nc.getFingerprint());
+ assertEquals(ArrayUtil.join("\n", lines), nc.getStack());
+ }
+
+ /**
+ * Test that both types of native crash app lines are parsed.
+ */
+ public void testParseApp() {
+ List<String> lines = Arrays.asList(
+ "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***",
+ "Build fingerprint: 'google/soju/crespo:4.0.4/IMM76D/299849:userdebug/test-keys'",
+ "pid: 2058, tid: 2523 >>> com.google.android.browser <<<",
+ "signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000");
+
+ NativeCrashItem nc = new NativeCrashParser().parse(lines);
+ assertNotNull(nc);
+ assertEquals(2058, nc.getPid().intValue());
+ assertEquals(2523, nc.getTid().intValue());
+ assertEquals("com.google.android.browser", nc.getApp());
+
+ lines = Arrays.asList(
+ "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***",
+ "Build fingerprint: 'google/soju/crespo:4.0.4/IMM76D/299849:userdebug/test-keys'",
+ "pid: 2058, tid: 2523, name: com.google.android.browser >>> com.google.android.browser <<<",
+ "signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000");
+
+ nc = new NativeCrashParser().parse(lines);
+ assertNotNull(nc);
+
+ assertEquals(2058, nc.getPid().intValue());
+ assertEquals(2523, nc.getTid().intValue());
+ assertEquals("com.google.android.browser", nc.getApp());
+
+ lines = Arrays.asList(
+ "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***",
+ "Build fingerprint: 'google/soju/crespo:4.0.4/IMM76D/299849:userdebug/test-keys'",
+ "pid: 2058, tid: 2523, name: Atlas Worker #1 >>> com.google.android.browser <<<",
+ "signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000");
+
+ nc = new NativeCrashParser().parse(lines);
+ assertNotNull(nc);
+
+ assertEquals(2058, nc.getPid().intValue());
+ assertEquals(2523, nc.getTid().intValue());
+ assertEquals("com.google.android.browser", nc.getApp());
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/ProcessUsageParserTest.java b/javatests/com/android/loganalysis/parser/ProcessUsageParserTest.java
new file mode 100644
index 0000000..bfba620
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/ProcessUsageParserTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 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.loganalysis.parser;
+
+import com.android.loganalysis.item.ProcessUsageItem;
+import com.android.loganalysis.item.ProcessUsageItem.ProcessUsageInfoItem;
+import com.android.loganalysis.item.ProcessUsageItem.SensorInfoItem;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link ProcessUsageParser}
+ */
+public class ProcessUsageParserTest extends TestCase {
+
+ /**
+ * Test that normal input is parsed.
+ */
+ public void testProcessUsageParser() {
+ List<String> inputBlock = Arrays.asList(
+ " 0:",
+ " Mobile network: 173.70KB received, 102.55KB sent (packets 129)",
+ " Mobile radio active: 6m 5s 80ms (14.9%) 80x @ 139 mspp",
+ " 1000:",
+ " Mobile network: 16.43KB received, 26.26KB sent",
+ " Mobile radio active: 1m 17s 489ms (3.2%) 61x @ 179 mspp",
+ " Sensor 44: 27m 18s 207ms realtime (22 times)",
+ " Sensor 36: 6s 483ms realtime (3 times)",
+ " Proc servicemanager:",
+ " CPU: 2s 20ms usr + 4s 60ms krn ; 0ms fg",
+ " Apk android:",
+ " 266 wakeup alarms",
+ " u0a2:",
+ " Mobile network: 16.43KB received, 26.26KB sent",
+ " Mobile radio active: 1m 17s 489ms (3.2%) 61x @ 179 mspp",
+ " Sensor 0: 5s 207ms realtime (2 times)",
+ " Proc servicemanager:",
+ " CPU: 2s 20ms usr + 4s 60ms krn ; 0ms fg",
+ " Apk android:",
+ " 2 wakeup alarms",
+ " ");
+
+ ProcessUsageItem processUsage = new ProcessUsageParser().parse(inputBlock);
+
+ assertEquals(3, processUsage.getProcessUsage().size());
+
+ LinkedList<ProcessUsageInfoItem> processUsageInfo =
+ (LinkedList<ProcessUsageInfoItem>)processUsage.getProcessUsage();
+
+ assertEquals("1000", processUsageInfo.get(1).getProcessUID());
+ assertEquals(266, processUsageInfo.get(1).getAlarmWakeups());
+
+ LinkedList<SensorInfoItem> sensor = processUsageInfo.get(1).getSensorUsage();
+ assertEquals("44", sensor.get(0).getSensorName());
+ assertEquals("36", sensor.get(1).getSensorName());
+
+ sensor = processUsageInfo.get(2).getSensorUsage();
+ assertEquals("0", sensor.get(0).getSensorName());
+ }
+}
+
diff --git a/javatests/com/android/loganalysis/parser/ProcrankParserTest.java b/javatests/com/android/loganalysis/parser/ProcrankParserTest.java
new file mode 100644
index 0000000..c47b750
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/ProcrankParserTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.loganalysis.parser;
+
+import com.android.loganalysis.item.ProcrankItem;
+import com.android.loganalysis.util.ArrayUtil;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link ProcrankParser}
+ */
+public class ProcrankParserTest extends TestCase {
+
+ /**
+ * Test that normal input is parsed.
+ */
+ public void testProcRankParserShortLine() {
+ List<String> inputBlock = Arrays.asList(
+ " 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",
+ " 51312K 22911K 20608K invalid.format",
+ " ------ ------ ------",
+ " 203624K 163604K TOTAL",
+ "RAM: 731448K total, 415804K free, 9016K buffers, 108548K cached",
+ "[procrank: 1.6s elapsed]");
+
+ ProcrankItem procrank = new ProcrankParser().parse(inputBlock);
+
+ // Ensures that only valid lines are parsed. Only 6 of the 11 lines under the header are
+ // valid.
+ assertEquals(6, procrank.getPids().size());
+
+ // Make sure all expected rows are present, and do a diagonal check of values
+ assertEquals((Integer) 87136, procrank.getVss(178));
+ assertEquals((Integer) 77996, procrank.getRss(1313));
+ assertEquals((Integer) 33122, procrank.getPss(3247));
+ assertEquals((Integer) 28360, procrank.getUss(334));
+ assertEquals("android.process.acore", procrank.getProcessName(2072));
+ assertEquals(ArrayUtil.join("\n", inputBlock), procrank.getText());
+ }
+
+ /**
+ * Test that normal input is parsed.
+ */
+ public void testProcRankParserLongLine() {
+ List<String> inputBlock = Arrays.asList(
+ " PID Vss Rss Pss Uss Swap PSwap USwap ZSwap cmdline",
+ " 6711 3454396K 146300K 108431K 105524K 31540K 20522K 20188K 4546K com.google.android.GoogleCamera",
+ " 1515 2535920K 131984K 93750K 89440K 42676K 31792K 31460K 7043K system_server",
+ "19906 2439540K 130228K 85418K 69296K 11680K 353K 0K 78K com.android.chrome:sandboxed_process10",
+ "13790 2596308K 124424K 75673K 69680K 11336K 334K 0K 74K com.google.android.youtube",
+ " 9288 2437704K 119496K 74288K 69532K 11344K 334K 0K 74K com.google.android.videos",
+ " 51312K 22911K 20608K 0K 0K 0K invalid.format",
+ " ------ ------ ------ ------ ------ ------ ------",
+ " 1061237K 940460K 619796K 225468K 201688K 49950K TOTAL",
+ "ZRAM: 52892K physical used for 238748K in swap (520908K total swap)",
+ "RAM: 1857348K total, 51980K free, 3780K buffers, 456272K cached, 29220K shmem, 97560K slab",
+ "[/system/xbin/su: 3.260s elapsed]");
+
+ ProcrankItem procrank = new ProcrankParser().parse(inputBlock);
+
+ // Ensures that only valid lines are parsed. Only 6 of the 11 lines under the header are
+ // valid.
+ assertEquals(5, procrank.getPids().size());
+
+ // Make sure all expected rows are present, and do a diagonal check of values
+ assertEquals((Integer) 3454396, procrank.getVss(6711));
+ assertEquals((Integer) 146300, procrank.getRss(6711));
+ assertEquals((Integer) 108431, procrank.getPss(6711));
+ assertEquals((Integer) 105524, procrank.getUss(6711));
+ assertEquals("com.google.android.GoogleCamera", procrank.getProcessName(6711));
+ assertEquals(ArrayUtil.join("\n", inputBlock), procrank.getText());
+ }
+
+ /**
+ * Test that an empty input returns {@code null}.
+ */
+ public void testEmptyInput() {
+ ProcrankItem item = new ProcrankParser().parse(Arrays.asList(""));
+ assertNull(item);
+ }
+}
+
diff --git a/javatests/com/android/loganalysis/parser/QtaguidParserTest.java b/javatests/com/android/loganalysis/parser/QtaguidParserTest.java
new file mode 100644
index 0000000..f9442a6
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/QtaguidParserTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2015 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.loganalysis.parser;
+
+import com.android.loganalysis.item.QtaguidItem;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class QtaguidParserTest extends TestCase {
+
+ public void testSingleLine() {
+ List<String> input = Arrays.asList("12 wlan0 0x0 10009 0 111661 353 258252 484 111661 353 0 0 0 0 258252 484 0 0 0 0");
+
+ QtaguidItem item = new QtaguidParser().parse(input);
+
+ assertEquals(1, item.getUids().size());
+ assertEquals(111661, item.getRxBytes(10009));
+ assertEquals(258252, item.getTxBytes(10009));
+ }
+
+ public void testMalformedLine() {
+ List<String> input = Arrays.asList("a b c d", "a b c d e f g h i j k l");
+
+ QtaguidItem item = new QtaguidParser().parse(input);
+
+ assertEquals(0, item.getUids().size());
+ }
+
+ public void testMultipleLines() {
+ List <String> input = Arrays.asList(
+ "IDX IFACE ACCT_TAG_HEX UID_TAG_INT CNT_SET RX_BYTES RX_PACKETS TX_BYTES TX_PACKETS " +
+ "rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets " +
+ "tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets",
+ "2 wlan0 0x0 0 0 669013 7534 272120 2851 161253 1606 203916 1228 303844 4700 123336 998 108412 1268 40372 585",
+ "3 wlan0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0",
+ "4 wlan0 0x0 1000 0 104010 860 135166 2090 2304 23 101706 837 0 0 3774 36 85344 843 46048 1211",
+ "5 wlan0 0x0 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0",
+ "6 wlan0 0x0 1020 0 68666 162 110566 260 0 0 68666 162 0 0 0 0 110566 260 0 0",
+ "7 wlan0 0x0 1020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0",
+ "8 wlan0 0x0 10000 0 826063 2441 486365 2402 725175 2202 100888 239 0 0 427377 2261 58988 141 0 0",
+ "9 wlan0 0x0 10000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0",
+ "10 wlan0 0x0 10007 0 0 0 640 10 0 0 0 0 0 0 640 10 0 0 0 0",
+ "11 wlan0 0x0 10007 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0",
+ "12 wlan0 0x0 10009 0 17773800 18040 6588861 16079 17773800 18040 0 0 0 0 6588861 16079 0 0 0 0",
+ "13 wlan0 0x0 10009 1 6392090 5092 109907 1235 6392090 5092 0 0 0 0 109907 1235 0 0 0 0",
+ "14 wlan0 0x0 10010 0 33397 41 5902 59 33397 41 0 0 0 0 5902 59 0 0 0 0",
+ "15 wlan0 0x0 10010 1 14949782 13336 1099914 12201 14949782 13336 0 0 0 0 1099914 12201 0 0 0 0",
+ "16 wlan0 0x0 10014 0 54459314 43660 1000730 9780 54459314 43660 0 0 0 0 1000730 9780 0 0 0 0",
+ "17 wlan0 0x0 10014 1 5411545 4459 416719 4154 5411545 4459 0 0 0 0 416719 4154 0 0 0 0",
+ "18 wlan0 0x0 10024 0 98775055 80471 3929490 45504 98775055 80471 0 0 0 0 3929490 45504 0 0 0 0",
+ "19 wlan0 0x0 10024 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0");
+ QtaguidItem item = new QtaguidParser().parse(input);
+ assertEquals(9, item.getUids().size());
+ assertEquals(24165890, item.getRxBytes(10009));
+ assertEquals(6698768, item.getTxBytes(10009));
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/SmartMonkeyLogParserTest.java b/javatests/com/android/loganalysis/parser/SmartMonkeyLogParserTest.java
new file mode 100644
index 0000000..1249f7c
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/SmartMonkeyLogParserTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2013 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.loganalysis.parser;
+
+import com.android.loganalysis.item.SmartMonkeyLogItem;
+import com.android.loganalysis.parser.SmartMonkeyLogParser;
+
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.List;
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link SmartMonkeyLogParser} and {@link SmartMonkeyLogItem}
+ */
+public class SmartMonkeyLogParserTest extends TestCase {
+
+ /**
+ * Test for detecting UI exceptions
+ * @throws ParseException
+ */
+ public void testExceptions() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "2013-03-04 12:33:18.789: Starting [UiAutomator Tests][com.android.cts.uiautomator]",
+ "2013-03-04 12:33:18.792: Target invocation count: 1000",
+ "2013-03-04 12:33:18.793: Throttle: 0 ms",
+ "2013-03-04 12:33:19.795: [ 0](Seq: -1)-Launching UiAutomator Tests",
+ "2013-03-04 12:33:37.211: [ 0](Seq: 0)-Found 6 candidates. Using index: 1",
+ "2013-03-04 12:33:37.336: [ 0](Seq: 0)-Clicking: CheckBox (760,194)",
+ "2013-03-04 12:33:38.443: [ 1](Seq: 0)-Found 6 candidates. Using index: 5",
+ "2013-03-04 12:33:38.533: [ 1](Seq: 0)-Clicking: Button~Description for Button (723,874)",
+ "2013-03-04 12:33:39.510: [ 2](Seq: 0)-UI Exception: CRASH: Unfortunately, UiAutomator Test App has stopped.",
+ "2013-03-04 12:43:39.510: [ 2](Seq: 0)-UI Exception: ANR: UiAutomator is not responding.",
+ "2013-03-04 12:53:39.513: Invocations requested: 1000",
+ "2013-03-04 12:53:39.518: Invocations completed: 2",
+ "2013-03-04 12:53:39.520: Device uptime: 608193 sec, Monkey run duration: 20 sec");
+
+ SmartMonkeyLogItem monkeyLog = new SmartMonkeyLogParser().parse(lines);
+ assertNotNull(monkeyLog);
+ assertEquals(1, monkeyLog.getCrashTimes().size());
+ assertEquals(1, monkeyLog.getAnrTimes().size());
+ assertEquals(SmartMonkeyLogParser.parseTime("2013-03-04 12:33:39.510"),
+ monkeyLog.getCrashTimes().toArray()[0]);
+ assertEquals(SmartMonkeyLogParser.parseTime("2013-03-04 12:43:39.510"),
+ monkeyLog.getAnrTimes().toArray()[0]);
+ }
+
+ /**
+ * Tests for parsing smart monkey log header
+ * @throws ParseException
+ */
+ public void testHeader() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "2013-03-04 12:33:18.789: Starting [UiAutomator Tests|YouTube][com.android.cts.uiautomator|com.google.android.youtube]",
+ "2013-03-04 12:33:18.792: Target invocation count: 1000",
+ "2013-03-04 12:33:18.793: Throttle: 1500 ms",
+ "2013-03-04 12:33:18.793: Device uptime: 608173 sec",
+ "2013-03-04 12:33:19.795: [ 0](Seq: -1)-Launching UiAutomator Tests",
+ "2013-03-04 12:33:37.211: [ 0](Seq: 0)-Found 6 candidates. Using index: 1",
+ "2013-03-04 12:33:37.336: [ 0](Seq: 0)-Clicking: CheckBox (760,194)",
+ "2013-03-04 12:33:38.443: [ 1](Seq: 0)-Found 6 candidates. Using index: 5",
+ "2013-03-04 12:33:38.533: [ 1](Seq: 0)-Clicking: Button~Description for Button (723,874)",
+ "2013-03-04 12:33:39.510: [ 2](Seq: 0)-UI Exception: CRASH: Unfortunately, UiAutomator Test App has stopped.",
+ "2013-03-04 12:43:39.510: [ 2](Seq: 0)-UI Exception: ANR: UiAutomator is not responding.",
+ "2013-03-04 12:53:39.513: Invocations requested: 1000",
+ "2013-03-04 12:53:39.518: Invocations completed: 2",
+ "2013-03-04 12:53:39.520: Device uptime: 608193 sec, Monkey run duration: 20 sec");
+ SmartMonkeyLogItem monkeyLog = new SmartMonkeyLogParser().parse(lines);
+ assertNotNull(monkeyLog);
+ assertEquals(2, monkeyLog.getApplications().size());
+ assertEquals("UiAutomator Tests", monkeyLog.getApplications().get(0));
+ assertEquals("YouTube", monkeyLog.getApplications().get(1));
+ assertEquals(2, monkeyLog.getPackages().size());
+ assertEquals("com.android.cts.uiautomator", monkeyLog.getPackages().get(0));
+ assertEquals("com.google.android.youtube", monkeyLog.getPackages().get(1));
+ assertEquals(SmartMonkeyLogParser.parseTime("2013-03-04 12:33:18.789"),
+ monkeyLog.getStartTime());
+ assertEquals(608173, monkeyLog.getStartUptimeDuration());
+ assertEquals(1000, monkeyLog.getTargetInvocations());
+ assertEquals(1500, monkeyLog.getThrottle());
+ }
+
+ /**
+ * Test for parsing log in flight
+ * @throws ParseException
+ */
+ public void testIntermidiateStop() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "2013-03-04 12:33:18.789: Starting [UiAutomator Tests|YouTube][com.android.cts.uiautomator|com.google.android.youtube]",
+ "2013-03-04 12:33:18.792: Target invocation count: 1000",
+ "2013-03-04 12:33:18.793: Throttle: 1500 ms",
+ "2013-03-04 12:33:19.795: [ 0](Seq: -1)-Launching UiAutomator Tests",
+ "2013-03-04 12:33:37.211: [ 0](Seq: 0)-Found 6 candidates. Using index: 1",
+ "2013-03-04 12:33:37.336: [ 0](Seq: 0)-Clicking: CheckBox (760,194)",
+ "2013-03-04 12:33:38.443: [ 1](Seq: 0)-Found 6 candidates. Using index: 5",
+ "2013-03-04 12:33:38.533: [ 12](Seq: 3)-Clicking: Button~Description for Button (723,874)",
+ "2013-03-04 12:33:39.510: [ 12](Seq: 3)-UI Exception: CRASH: Unfortunately, UiAutomator Test App has stopped.");
+
+ SmartMonkeyLogItem monkeyLog = new SmartMonkeyLogParser().parse(lines);
+ assertNotNull(monkeyLog);
+ assertEquals(12, monkeyLog.getIntermediateCount());
+ assertEquals(SmartMonkeyLogParser.parseTime("2013-03-04 12:33:39.510"),
+ monkeyLog.getIntermediateTime());
+ }
+
+ /**
+ * Tests for parsing smart monkey log footer
+ * @throws ParseException
+ */
+ public void testFooter() throws ParseException {
+ List<String> lines = Arrays.asList(
+ "2013-03-04 12:33:18.789: Starting [UiAutomator Tests|YouTube][com.android.cts.uiautomator|com.google.android.youtube]",
+ "2013-03-04 12:33:18.792: Target invocation count: 1000",
+ "2013-03-04 12:33:18.793: Throttle: 1500 ms",
+ "2013-03-04 12:33:19.795: [ 0](Seq: -1)-Launching UiAutomator Tests",
+ "2013-03-04 12:33:37.211: [ 0](Seq: 0)-Found 6 candidates. Using index: 1",
+ "2013-03-04 12:33:37.336: [ 0](Seq: 0)-Clicking: CheckBox (760,194)",
+ "2013-03-04 12:33:38.443: [ 1](Seq: 0)-Found 6 candidates. Using index: 5",
+ "2013-03-04 12:33:38.533: [ 1](Seq: 0)-Clicking: Button~Description for Button (723,874)",
+ "2013-03-04 12:33:38.443: [ 2](Seq: 0)-Found 6 candidates. Using index: 5",
+ "2013-03-04 12:33:38.533: [ 2](Seq: 0)-Clicking: Button~Description for Button (723,874)",
+ "2013-03-04 12:53:39.513: Monkey aborted.",
+ "2013-03-04 12:53:39.513: Invocations requested: 1000",
+ "2013-03-04 12:53:39.518: Invocations completed: 999",
+ "2013-03-04 12:53:39.520: Device uptime: 608193 sec, Monkey run duration: 20 sec");
+
+ SmartMonkeyLogItem monkeyLog = new SmartMonkeyLogParser().parse(lines);
+ assertNotNull(monkeyLog);
+ assertEquals(999, monkeyLog.getFinalCount());
+ assertEquals(20, monkeyLog.getTotalDuration());
+ assertEquals(608193, monkeyLog.getStopUptimeDuration());
+ assertEquals(true, monkeyLog.getIsAborted());
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/SystemPropsParserTest.java b/javatests/com/android/loganalysis/parser/SystemPropsParserTest.java
new file mode 100644
index 0000000..f6dc8a1
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/SystemPropsParserTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.loganalysis.parser;
+
+import com.android.loganalysis.item.SystemPropsItem;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link SystemPropsParser}
+ */
+public class SystemPropsParserTest extends TestCase {
+
+ /**
+ * Test that normal input is parsed.
+ */
+ public void testSimpleParse() {
+ List<String> inputBlock = Arrays.asList(
+ "[dalvik.vm.dexopt-flags]: [m=y]",
+ "[dalvik.vm.heapgrowthlimit]: [48m]",
+ "[dalvik.vm.heapsize]: [256m]",
+ "[gsm.version.ril-impl]: [android moto-ril-multimode 1.0]");
+
+ SystemPropsItem map = new SystemPropsParser().parse(inputBlock);
+
+ 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 = Arrays.asList(
+ "[dalvik.vm.dexopt-flags]: [m=y]",
+ "[ends with newline]: [yup",
+ "]",
+ "[dalvik.vm.heapsize]: [256m]");
+
+ SystemPropsItem map = new SystemPropsParser().parse(inputBlock);
+
+ assertEquals(2, map.size());
+ assertEquals("m=y", map.get("dalvik.vm.dexopt-flags"));
+ assertEquals("256m", map.get("dalvik.vm.heapsize"));
+ }
+
+ /**
+ * Test that an empty input returns {@code null}.
+ */
+ public void testEmptyInput() {
+ SystemPropsItem item = new SystemPropsParser().parse(Arrays.asList(""));
+ assertNull(item);
+ }
+}
+
diff --git a/javatests/com/android/loganalysis/parser/TimingsLogParserTest.java b/javatests/com/android/loganalysis/parser/TimingsLogParserTest.java
new file mode 100644
index 0000000..48881ea
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/TimingsLogParserTest.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2019 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.loganalysis.parser;
+
+import com.android.loganalysis.item.GenericTimingItem;
+import com.android.loganalysis.item.SystemServicesTimingItem;
+
+import junit.framework.TestCase;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/** Unit Test for {@link TimingsLogParser} */
+public class TimingsLogParserTest extends TestCase {
+
+ private TimingsLogParser mParser;
+
+ public TimingsLogParserTest() {
+ mParser = new TimingsLogParser();
+ }
+
+ public void testParseGenericTiming_noPattern() throws IOException {
+ // Test when input is empty
+ String log = "";
+ List<GenericTimingItem> items = mParser.parseGenericTimingItems(createBufferedReader(log));
+ assertNotNull(items);
+ assertEquals(0, items.size());
+ // Test when input is not empty
+ log =
+ String.join(
+ "\n",
+ "01-17 01:22:39.513 0 0 I CPU features: detected feature: GIC system register CPU interface",
+ "01-17 01:22:39.513 0 0 I CPU features: kernel page table isolation forced ON by command line option",
+ "01-17 01:22:39.513 0 0 I CPU features: detected feature: 32-bit EL0 Support",
+ "01-17 01:22:59.823 0 0 I init : Service 'bootanim' (pid 1030) exited with status 0",
+ "01-17 01:22:59.966 0 0 I init : processing action (sys.boot_completed=1) from (/init.rc:796)");
+ items = mParser.parseGenericTimingItems(createBufferedReader(log));
+ assertNotNull(items);
+ assertEquals(0, items.size());
+ }
+
+ public void testParseGenericTiming_multiplePattern_oneOccurrenceEach() throws IOException {
+ String log =
+ String.join(
+ "\n",
+ "01-17 01:22:39.503 0 0 I : Linux version 4.4.177 (Kernel Boot Started)",
+ "01-17 01:22:39.513 0 0 I CPU features: detected feature: GIC system register CPU interface",
+ "01-17 01:22:39.513 0 0 I CPU features: kernel page table isolation forced ON by command line option",
+ "01-17 01:22:39.513 0 0 I CPU features: detected feature: 32-bit EL0 Support",
+ "01-17 01:22:43.634 0 0 I init : starting service 'zygote'...",
+ "01-17 01:22:48.634 0 0 I init : 'zygote' started",
+ "01-17 01:22:53.392 0 0 I init : starting service 'fake service'",
+ "01-17 01:22:59.823 0 0 I init : Service 'bootanim' (pid 1030) exited with status 0",
+ "01-17 01:22:60.334 0 0 I init : 'fake service' started",
+ "01-17 01:22:61.362 938 1111 I ActivityManager: my app started",
+ "01-17 01:22:61.977 938 1111 I ActivityManager: my app displayed");
+
+ mParser.addDurationPatternPair(
+ "BootToAnimEnd",
+ Pattern.compile("Linux version"),
+ Pattern.compile("Service 'bootanim'"));
+ mParser.addDurationPatternPair(
+ "ZygoteStartTime",
+ Pattern.compile("starting service 'zygote'"),
+ Pattern.compile("'zygote' started"));
+ mParser.addDurationPatternPair(
+ "FakeServiceStartTime",
+ Pattern.compile("starting service 'fake service'"),
+ Pattern.compile("'fake service' started"));
+ mParser.addDurationPatternPair(
+ "BootToAppStart",
+ Pattern.compile("Linux version"),
+ Pattern.compile("my app displayed"));
+ mParser.addDurationPatternPair(
+ "AppStartTime",
+ Pattern.compile("my app started"),
+ Pattern.compile("my app displayed"));
+ mParser.addDurationPatternPair(
+ "ZygoteToApp",
+ Pattern.compile("'zygote' started"),
+ Pattern.compile("my app started"));
+ List<GenericTimingItem> items = mParser.parseGenericTimingItems(createBufferedReader(log));
+ assertNotNull(items);
+ assertEquals(6, items.size());
+ // 1st item
+ assertEquals("ZygoteStartTime", items.get(0).getName());
+ assertEquals(5000.0, items.get(0).getDuration());
+ // 2nd item
+ assertEquals("BootToAnimEnd", items.get(1).getName());
+ assertEquals(20320.0, items.get(1).getDuration());
+ // 3rd item
+ assertEquals("FakeServiceStartTime", items.get(2).getName());
+ assertEquals(6942.0, items.get(2).getDuration());
+ // 4th item
+ assertEquals("ZygoteToApp", items.get(3).getName());
+ assertEquals(12728.0, items.get(3).getDuration());
+ // 5th item
+ assertEquals("BootToAppStart", items.get(4).getName());
+ assertEquals(22474.0, items.get(4).getDuration());
+ // 6th item
+ assertEquals("AppStartTime", items.get(5).getName());
+ assertEquals(615.0, items.get(5).getDuration());
+ }
+
+ public void testParseGenericTiming_multiplePattern_someNotMatched() throws IOException {
+ String log =
+ String.join(
+ "\n",
+ "01-17 01:22:39.503 0 0 I : Linux version 4.4.177 (Kernel Boot Started)",
+ "01-17 01:22:39.513 0 0 I CPU features: detected feature: GIC system register CPU interface",
+ "01-17 01:22:39.513 0 0 I CPU features: kernel page table isolation forced ON by command line option",
+ "01-17 01:22:39.513 0 0 I CPU features: detected feature: 32-bit EL0 Support",
+ "01-17 01:22:43.634 0 0 I init : starting service 'zygote'...",
+ "01-17 01:22:48.634 0 0 I init : 'zygote' started",
+ "01-17 01:22:53.392 0 0 I init : starting service 'fake service'",
+ "01-17 01:22:59.823 0 0 I init : Service 'bootanim' (pid 1030) exited with status 0",
+ "01-17 01:22:60.334 0 0 I init : 'fake service' started",
+ "01-17 01:22:61.362 938 1111 I ActivityManager: my app started",
+ "01-17 01:22:61.977 938 1111 I ActivityManager: my app displayed");
+
+ mParser.addDurationPatternPair(
+ "BootToAnimEnd",
+ Pattern.compile("Linux version"),
+ Pattern.compile("Service 'bootanim'"));
+ mParser.addDurationPatternPair(
+ "ZygoteStartTime",
+ Pattern.compile("starting service 'zygote'"),
+ Pattern.compile("End line no there"));
+ mParser.addDurationPatternPair(
+ "FakeServiceStartTime",
+ Pattern.compile("Start line not there"),
+ Pattern.compile("'fake service' started"));
+ mParser.addDurationPatternPair(
+ "AppStartTime",
+ Pattern.compile("Start line not there"),
+ Pattern.compile("End line not there"));
+
+ List<GenericTimingItem> items = mParser.parseGenericTimingItems(createBufferedReader(log));
+ assertNotNull(items);
+ assertEquals(1, items.size());
+ assertEquals("BootToAnimEnd", items.get(0).getName());
+ }
+
+ public void testParseGenericTiming_clearExistingPatterns() throws IOException {
+ String log =
+ String.join(
+ "\n",
+ "01-17 01:22:39.503 0 0 I : Linux version 4.4.177 (Kernel Boot Started)",
+ "01-17 01:22:53.392 0 0 I init : starting service 'fake service'",
+ "01-17 01:22:59.823 0 0 I init : Service 'bootanim' (pid 1030) exited with status 0",
+ "01-17 01:22:60.334 0 0 I init : 'fake service' started",
+ "01-17 01:22:61.362 938 1111 I ActivityManager: my app started",
+ "01-17 01:22:61.977 938 1111 I ActivityManager: my app displayed");
+ mParser.addDurationPatternPair(
+ "BootToAnimEnd",
+ Pattern.compile("Linux version"),
+ Pattern.compile("Service 'bootanim'"));
+ List<GenericTimingItem> items = mParser.parseGenericTimingItems(createBufferedReader(log));
+ assertNotNull(items);
+ assertEquals(1, items.size());
+
+ mParser.clearDurationPatterns();
+ items = mParser.parseGenericTimingItems(createBufferedReader(log));
+ assertNotNull(items);
+ assertEquals(0, items.size());
+ }
+
+ public void testParseGenericTiming_multiplePattern_multipleOccurrence() throws IOException {
+ String log =
+ String.join(
+ "\n",
+ "01-17 01:22:39.503 0 0 I : Linux version 4.4.177 (Kernel Boot Started)",
+ "01-17 01:22:39.513 0 0 I CPU features: detected feature: GIC system register CPU interface",
+ "01-17 01:22:39.513 0 0 I CPU features: kernel page table isolation forced ON by command line option",
+ "01-17 01:22:39.513 0 0 I CPU features: detected feature: 32-bit EL0 Support",
+ "01-17 01:22:43.634 0 0 I init : starting service 'zygote'...",
+ "01-17 01:22:48.634 0 0 I init : 'zygote' started",
+ "01-17 01:22:53.392 0 0 I init : starting service 'fake service'",
+ "01-17 01:22:59.823 0 0 I init : 'bootanim' not reported",
+ "01-17 01:22:60.334 0 0 I init : 'fake service' started",
+ "01-17 01:32:39.503 0 0 I : Linux version 4.4.177 (Kernel Boot Started)",
+ "01-17 01:32:39.513 0 0 I CPU features: detected feature: GIC system register CPU interface",
+ "01-17 01:32:39.513 0 0 I CPU features: kernel page table isolation forced ON by command line option",
+ "01-17 01:32:39.513 0 0 I CPU features: detected feature: 32-bit EL0 Support",
+ "01-17 01:32:43.634 0 0 I init : starting service 'zygote'...",
+ "01-17 01:32:48.634 0 0 I init : 'zygote' started",
+ "01-17 01:32:53.392 0 0 I init : starting service 'a different service'",
+ "01-17 01:32:59.823 0 0 I init : Service 'bootanim' (pid 1030) exited with status 0",
+ "01-17 01:32:60.334 0 0 I init : 'fake service' started",
+ "01-17 01:32:61.362 938 1111 I ActivityManager: my app started",
+ "01-17 01:32:61.977 938 1111 I ActivityManager: my app displayed");
+
+ mParser.addDurationPatternPair(
+ "BootToAnimEnd",
+ Pattern.compile("Linux version"),
+ Pattern.compile("Service 'bootanim'"));
+ mParser.addDurationPatternPair(
+ "ZygoteStartTime",
+ Pattern.compile("starting service 'zygote'"),
+ Pattern.compile("'zygote' started"));
+ mParser.addDurationPatternPair(
+ "FakeServiceStartTime",
+ Pattern.compile("starting service 'fake service'"),
+ Pattern.compile("'fake service' started"));
+ mParser.addDurationPatternPair(
+ "AppStartTime",
+ Pattern.compile("my app started"),
+ Pattern.compile("my app displayed"));
+ List<GenericTimingItem> items = mParser.parseGenericTimingItems(createBufferedReader(log));
+ assertNotNull(items);
+ assertEquals(5, items.size());
+ // 1st item
+ assertEquals("ZygoteStartTime", items.get(0).getName());
+ assertEquals(5000.0, items.get(0).getDuration());
+ // 2nd item
+ assertEquals("FakeServiceStartTime", items.get(1).getName());
+ assertEquals(6942.0, items.get(1).getDuration());
+ // 3rd item
+ assertEquals("ZygoteStartTime", items.get(2).getName());
+ assertEquals(5000.0, items.get(2).getDuration());
+ // 4th item
+ assertEquals("BootToAnimEnd", items.get(3).getName());
+ assertEquals(20320.0, items.get(3).getDuration());
+ // 5th item
+ assertEquals("AppStartTime", items.get(4).getName());
+ assertEquals(615.0, items.get(4).getDuration());
+ }
+
+ public void testParseGenericTiming_wrongTimeFormat() throws IOException {
+ String log =
+ String.join(
+ "\n",
+ "1234252.234 0 0 I : Linux version 4.4.177 (Kernel Boot Started)",
+ "1234259.342 0 0 I CPU features: detected feature: GIC system register CPU interface");
+ mParser.addDurationPatternPair(
+ "BootToAnimEnd",
+ Pattern.compile("Linux version"),
+ Pattern.compile("Service 'bootanim'"));
+ try {
+ List<GenericTimingItem> items =
+ mParser.parseGenericTimingItems(createBufferedReader(log));
+ } catch (RuntimeException e) {
+ assertTrue(
+ "Test should report ParseException",
+ e.getCause().toString().startsWith("java.text.ParseException"));
+ return;
+ }
+ fail("Test should throw ParseException");
+ }
+
+ /** Test that system services duration can be parsed as expected */
+ public void testParseSystemServicesTiming_system_services_duration() throws IOException {
+ String log =
+ String.join(
+ "\n",
+ "01-10 01:25:57.675 981 981 D SystemServerTiming: StartWatchdog took to complete: 38ms",
+ "01-10 01:25:57.675 981 981 I SystemServer: Reading configuration...",
+ "01-10 01:25:57.675 981 981 I SystemServer: ReadingSystemConfig",
+ "01-10 01:25:57.676 981 981 D SystemServerTiming: ReadingSystemConfig took to complete: 0.53ms",
+ "01-10 01:25:57.676 981 981 D SystemServerTiming: ReadingSystemConfig took to complete: 0.53ms", // Parser should skip duplicated log line
+ "01-10 01:25:57.677 465 465 I snet_event_log: [121035042,-1,]",
+ "01-10 01:25:57.678 900 900 I FakeComponent: FakeSubcomponent wrong format took to complete: 10ms",
+ "01-10 01:25:57.678 900 900 I FakeComponent: FakeSubcomponent took to complete: 20s",
+ "01-10 01:25:57.680 981 981 D SystemServerTiming: StartInstaller took to complete: 5ms wrong format",
+ "01-10 01:25:57.682 981 981 D SystemServerTiming: DeviceIdentifiersPolicyService took to complete: 2ms",
+ "01-10 01:25:57.682 981 981 D SystemServerTiming: DeviceIdentifiersPolicyService took to complete: 2ms",
+ "06-06 19:23:54.410 1295 1295 D OtherService : StartTestStack took to complete: 7ms",
+ "06-06 19:23:55.410 129 129 D FakeService : Validtook to complete: 8ms",
+ "06-06 19:23:56.410 981 981 D SystemServerTiming: StartWatchdog took to complete: 38ms"); //Parser should parse the same metric at a different time
+
+ List<SystemServicesTimingItem> items =
+ mParser.parseSystemServicesTimingItems(createBufferedReader(log));
+ assertNotNull(items);
+ assertEquals(6, items.size());
+ assertEquals("SystemServerTiming", items.get(0).getComponent());
+ assertEquals(38.0, items.get(0).getDuration());
+ assertNull(items.get(0).getStartTime());
+ assertEquals("ReadingSystemConfig", items.get(1).getSubcomponent());
+ assertEquals(0.53, items.get(1).getDuration());
+ assertNull(items.get(1).getStartTime());
+ assertEquals("DeviceIdentifiersPolicyService", items.get(2).getSubcomponent());
+ assertEquals("OtherService", items.get(3).getComponent());
+ assertEquals("StartTestStack", items.get(3).getSubcomponent());
+ assertEquals(7.0, items.get(3).getDuration());
+ assertEquals("FakeService", items.get(4).getComponent());
+ assertEquals("Valid", items.get(4).getSubcomponent());
+ assertEquals(8.0, items.get(4).getDuration());
+ assertNull(items.get(4).getStartTime());
+ assertEquals("SystemServerTiming", items.get(5).getComponent());
+ assertEquals("StartWatchdog", items.get(5).getSubcomponent());
+ assertEquals(38.0, items.get(5).getDuration());
+ }
+
+ /** Test that system services start time can be parsed as expected */
+ public void testParseSystemServicesTiming_system_services_start_time() throws IOException {
+ String log =
+ String.join(
+ "\n",
+ "01-10 01:24:45.536 1079 1079 D BootAnimation: BootAnimationStartTiming start time: 8611ms",
+ "01-10 01:24:45.537 1079 1079 D BootAnimation: BootAnimationPreloadTiming start time: 8611ms",
+ "01-10 01:24:45.556 874 1021 I ServiceManager: Waiting for service 'package_native' on '/dev/binder'...",
+ "01-10 01:24:45.561 466 466 I snet_event_log: [121035042,-1,]",
+ "01-10 01:24:45.583 1080 1080 I SystemServer: InitBeforeStartServices start time: 2345ms wrong format",
+ "01-10 01:25:24.095 1014 1111 D BootAnimation: BootAnimationShownTiming start time: 9191s",
+ "06-06 19:23:49.299 603 603 E qdmetadata: Unknown paramType 2",
+ "06-06 19:23:49.299 603 603 I FakeComponent : wrong subcomponent start time: 234ms",
+ "06-06 19:23:49.299 603 603 D FakeComponent: Subcomponent start time 234ms",
+ "06-06 19:23:49.299 1079 1079 D BootAnimation: BootAnimationStopTiming start time: 24839ms",
+ "06-06 19:23:59.299 179 179 D FakeService : Validstart time: 34839ms");
+
+ List<SystemServicesTimingItem> items =
+ mParser.parseSystemServicesTimingItems(createBufferedReader(log));
+ assertNotNull(items);
+ assertEquals(4, items.size());
+ assertEquals("BootAnimation", items.get(0).getComponent());
+ assertEquals("BootAnimationStartTiming", items.get(0).getSubcomponent());
+ assertEquals(8611.0, items.get(0).getStartTime());
+ assertNull(items.get(0).getDuration());
+ assertEquals("BootAnimationPreloadTiming", items.get(1).getSubcomponent());
+ assertEquals("BootAnimation", items.get(2).getComponent());
+ assertEquals("BootAnimationStopTiming", items.get(2).getSubcomponent());
+ assertEquals(24839.0, items.get(2).getStartTime());
+ assertNull(items.get(2).getDuration());
+ assertEquals("FakeService", items.get(3).getComponent());
+ assertEquals("Valid", items.get(3).getSubcomponent());
+ assertEquals(34839.0, items.get(3).getStartTime());
+ assertNull(items.get(3).getDuration());
+ }
+
+ private BufferedReader createBufferedReader(String input) {
+ InputStream inputStream = new ByteArrayInputStream(input.getBytes());
+ InputStreamReader reader = new InputStreamReader(inputStream);
+ return new BufferedReader(reader);
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/TopParserTest.java b/javatests/com/android/loganalysis/parser/TopParserTest.java
new file mode 100644
index 0000000..9b2253b
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/TopParserTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2013 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.loganalysis.parser;
+
+import com.android.loganalysis.item.TopItem;
+import com.android.loganalysis.util.ArrayUtil;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link ProcrankParser}
+ */
+public class TopParserTest extends TestCase {
+
+ /**
+ * Test that the output of the top command is parsed.
+ */
+ public void testTopParser() {
+ List<String> inputBlock = Arrays.asList(
+ "User 20%, System 20%, IOW 5%, IRQ 3%",
+ "User 150 + Nice 50 + Sys 200 + Idle 510 + IOW 60 + IRQ 5 + SIRQ 25 = 1000",
+ "",
+ " PID TID PR CPU% S VSS RSS PCY UID Thread Proc",
+ " 4474 4474 0 2% R 1420K 768K shell top top");
+
+ TopItem item = new TopParser().parse(inputBlock);
+
+ assertEquals(150, item.getUser());
+ assertEquals(50, item.getNice());
+ assertEquals(200, item.getSystem());
+ assertEquals(510, item.getIdle());
+ assertEquals(60, item.getIow());
+ assertEquals(5, item.getIrq());
+ assertEquals(25, item.getSirq());
+ assertEquals(1000, item.getTotal());
+ assertEquals(ArrayUtil.join("\n", inputBlock), item.getText());
+ }
+
+ /**
+ * Test that the last output is stored.
+ */
+ public void testLastTop() {
+ List<String> inputBlock = Arrays.asList(
+ "User 0 + Nice 0 + Sys 0 + Idle 1000 + IOW 0 + IRQ 0 + SIRQ 0 = 1000",
+ "User 0 + Nice 0 + Sys 0 + Idle 1000 + IOW 0 + IRQ 0 + SIRQ 0 = 1000",
+ "User 150 + Nice 50 + Sys 200 + Idle 510 + IOW 60 + IRQ 5 + SIRQ 25 = 1000");
+
+ TopItem item = new TopParser().parse(inputBlock);
+
+ assertEquals(150, item.getUser());
+ assertEquals(50, item.getNice());
+ assertEquals(200, item.getSystem());
+ assertEquals(510, item.getIdle());
+ assertEquals(60, item.getIow());
+ assertEquals(5, item.getIrq());
+ assertEquals(25, item.getSirq());
+ assertEquals(1000, item.getTotal());
+ }
+
+ /**
+ * Test that an empty input returns {@code null}.
+ */
+ public void testEmptyInput() {
+ TopItem item = new TopParser().parse(Arrays.asList(""));
+ assertNull(item);
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/TraceFormatParserTest.java b/javatests/com/android/loganalysis/parser/TraceFormatParserTest.java
new file mode 100644
index 0000000..06ce9ec
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/TraceFormatParserTest.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2017 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.loganalysis.parser;
+
+import static org.junit.Assert.fail;
+
+import com.android.loganalysis.item.TraceFormatItem;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+
+/** Test for {@link TraceFormatParser}. */
+@RunWith(JUnit4.class)
+public class TraceFormatParserTest {
+ private TraceFormatParser mParser;
+
+ // "unwrap" the regex strings so that we can compare with the generated regex
+ private static final String MATCH_NUM_UNESCAPED =
+ TraceFormatParser.MATCH_NUM.replaceAll("\\\\\\\\", "\\\\");
+ private static final String MATCH_HEX_UNESCAPED =
+ TraceFormatParser.MATCH_HEX.replaceAll("\\\\\\\\", "\\\\");
+ private static final String MATCH_STR_UNESCAPED =
+ TraceFormatParser.MATCH_STR.replaceAll("\\\\\\\\", "\\\\");
+
+ @Before
+ public void setUp() {
+ mParser = new TraceFormatParser();
+ }
+
+ @Test
+ public void testParseFormatLine() {
+ List<String> formatLine =
+ Arrays.asList("print fmt: \"foo=%llu, bar=%s\", REC->foo, REC->bar");
+ String expectedRegex =
+ String.format(
+ "foo=(?<foo>%s), bar=(?<bar>%s)", MATCH_NUM_UNESCAPED, MATCH_STR_UNESCAPED);
+ List<String> expectedParameters = Arrays.asList("foo", "bar");
+ List<String> expectedNumericParameters = Arrays.asList("foo");
+ List<String> expectedHexParameters = Arrays.asList();
+ List<String> expectedStringParameters = Arrays.asList("bar");
+ String shouldMatch = "foo=123, bar=enabled";
+
+ TraceFormatItem parsedItem = mParser.parse(formatLine);
+ Assert.assertEquals(expectedParameters, parsedItem.getParameters());
+ Assert.assertEquals(expectedNumericParameters, parsedItem.getNumericParameters());
+ Assert.assertEquals(expectedHexParameters, parsedItem.getHexParameters());
+ Assert.assertEquals(expectedStringParameters, parsedItem.getStringParameters());
+ Assert.assertEquals(expectedRegex, parsedItem.getRegex().toString());
+ Matcher m = parsedItem.getRegex().matcher(shouldMatch);
+ Assert.assertTrue(m.matches());
+ Assert.assertEquals(m.group("foo"), "123");
+ Assert.assertEquals(m.group("bar"), "enabled");
+ }
+
+ @Test
+ public void testNoParameters() {
+ List<String> formatLine = Arrays.asList("print fmt: \"foo\"");
+ String expectedRegex = "foo";
+ List<String> expectedParameters = Arrays.asList();
+ String shouldMatch = "foo";
+
+ TraceFormatItem parsedItem = mParser.parse(formatLine);
+ Assert.assertEquals(expectedParameters, parsedItem.getParameters());
+ Assert.assertEquals(expectedRegex, parsedItem.getRegex().toString());
+ Matcher m = parsedItem.getRegex().matcher(shouldMatch);
+ Assert.assertTrue(m.matches());
+ }
+
+ @Test
+ public void testNullInput() {
+ try {
+ mParser.parse(null);
+ fail("Expected an exception thrown by TraceFormatParser");
+ } catch (RuntimeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testEmptyInput() {
+ List<String> formatLine = Arrays.asList("");
+ try {
+ mParser.parse(formatLine);
+ fail("Expected an exception thrown by TraceFormatParser");
+ } catch (RuntimeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testMultiLineInput() {
+ List<String> formatLine = Arrays.asList("foo", "bar");
+ try {
+ mParser.parse(formatLine);
+ fail("Expected an exception thrown by TraceFormatParser");
+ } catch (RuntimeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testOneLineInvalidInput() {
+ List<String> formatLine = Arrays.asList("foo bar");
+ try {
+ mParser.parse(formatLine);
+ fail("Expected an exception thrown by TraceFormatParser");
+ } catch (RuntimeException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testQuoteInParams() {
+ List<String> formatLine =
+ Arrays.asList("print fmt: \"foo %s\", REC->foo ? \"online\" : \"offline\"");
+ String expectedRegex = String.format("foo (?<foo>%s)", MATCH_STR_UNESCAPED);
+ String shouldMatch = "foo online";
+
+ TraceFormatItem parsedItem = mParser.parse(formatLine);
+ Assert.assertEquals(expectedRegex, parsedItem.getRegex().toString());
+ Matcher m = parsedItem.getRegex().matcher(shouldMatch);
+ Assert.assertTrue(m.matches());
+ Assert.assertEquals(m.group("foo"), "online");
+ }
+
+ @Test
+ public void testCategorizeParameters() {
+ List<String> formatLine =
+ Arrays.asList(
+ "print fmt: \"num1=%lu, num2=%f, hex=%08x, str=%s\", REC->num1, REC->num2, REC->hex, REC->str");
+ List<String> expectedNumericParameters = Arrays.asList("num1", "num2");
+ List<String> expectedHexParameters = Arrays.asList("hex");
+ List<String> expectedStringParameters = Arrays.asList("str");
+
+ TraceFormatItem parsedItem = mParser.parse(formatLine);
+ Assert.assertEquals(expectedNumericParameters, parsedItem.getNumericParameters());
+ Assert.assertEquals(expectedHexParameters, parsedItem.getHexParameters());
+ Assert.assertEquals(expectedStringParameters, parsedItem.getStringParameters());
+ }
+
+ @Test
+ public void testCaseConvertParameterName() {
+ List<String> formatLine = Arrays.asList("print fmt: \"foo_bar=%llu\", REC->foo_bar");
+ List<String> expectedParameters = Arrays.asList("fooBar");
+ String shouldMatch = "foo_bar=123";
+
+ TraceFormatItem parsedItem = mParser.parse(formatLine);
+ Assert.assertEquals(expectedParameters, parsedItem.getParameters());
+ Matcher m = parsedItem.getRegex().matcher(shouldMatch);
+ Assert.assertTrue(m.matches());
+ Assert.assertEquals(m.group("fooBar"), "123");
+ }
+
+ @Test
+ public void testMatchInt() {
+ List<String> formatLine =
+ Arrays.asList("print fmt: \"foo=%d, bar=%lu\", REC->foo, REC->bar");
+ String shouldMatch = "foo=-123, bar=456";
+
+ TraceFormatItem parsedItem = mParser.parse(formatLine);
+ Matcher m = parsedItem.getRegex().matcher(shouldMatch);
+ Assert.assertTrue(m.matches());
+ Assert.assertEquals(m.group("foo"), "-123");
+ Assert.assertEquals(m.group("bar"), "456");
+ }
+
+ @Test
+ public void testMatchFloat() {
+ List<String> formatLine =
+ Arrays.asList("print fmt: \"foo=%f, bar=%.2f\", REC->foo, REC->bar");
+ String shouldMatch = "foo=123.4567, bar=456.78";
+
+ TraceFormatItem parsedItem = mParser.parse(formatLine);
+ Matcher m = parsedItem.getRegex().matcher(shouldMatch);
+ Assert.assertTrue(m.matches());
+ Assert.assertEquals(m.group("foo"), "123.4567");
+ Assert.assertEquals(m.group("bar"), "456.78");
+ }
+
+ @Test
+ public void testMatchHex() {
+ List<String> formatLine =
+ Arrays.asList(
+ "print fmt: \"foo=0x%04x, bar=0x%08X, baz=%x\", REC->foo, REC->bar, REC->baz");
+ String shouldMatch = "foo=0x007b, bar=0x000001C8, baz=7b";
+
+ TraceFormatItem parsedItem = mParser.parse(formatLine);
+ Matcher m = parsedItem.getRegex().matcher(shouldMatch);
+ Assert.assertTrue(m.matches());
+ Assert.assertEquals(m.group("foo"), "007b");
+ Assert.assertEquals(m.group("bar"), "000001C8");
+ Assert.assertEquals(m.group("baz"), "7b");
+ }
+
+ @Test
+ public void testMatchString() {
+ List<String> formatLine =
+ Arrays.asList("print fmt: \"foo=%s, bar=%s\", REC->foo, REC->bar");
+ String shouldMatch = "foo=oof, bar=123";
+
+ TraceFormatItem parsedItem = mParser.parse(formatLine);
+ Matcher m = parsedItem.getRegex().matcher(shouldMatch);
+ Assert.assertTrue(m.matches());
+ Assert.assertEquals(m.group("foo"), "oof");
+ Assert.assertEquals(m.group("bar"), "123");
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/TracesParserTest.java b/javatests/com/android/loganalysis/parser/TracesParserTest.java
new file mode 100644
index 0000000..c5046e3
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/TracesParserTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.loganalysis.parser;
+
+import com.android.loganalysis.item.TracesItem;
+import com.android.loganalysis.util.ArrayUtil;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link TracesParser}
+ */
+public class TracesParserTest extends TestCase {
+
+ /**
+ * Test that the parser parses the correct stack.
+ */
+ public void testTracesParser() {
+ List<String> lines = Arrays.asList(
+ "",
+ "",
+ "----- pid 2887 at 2012-05-02 16:43:41 -----",
+ "Cmd line: com.android.package",
+ "",
+ "DALVIK THREADS:",
+ "(mutexes: tll=0 tsl=0 tscl=0 ghl=0)",
+ "",
+ "\"main\" prio=5 tid=1 SUSPENDED",
+ " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001",
+ " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001",
+ " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0",
+ " at class.method1(Class.java:1)",
+ " at class.method2(Class.java:2)",
+ " at class.method2(Class.java:2)",
+ "",
+ "\"Task_1\" prio=5 tid=27 WAIT",
+ " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001",
+ " | sysTid=4789 nice=10 sched=0/0 cgrp=bg_non_interactive handle=0000000001",
+ " | schedstat=( 0 0 0 ) utm=0 stm=0 core=0",
+ " at class.method1(Class.java:1)",
+ " - waiting on <0x00000001> (a java.lang.Thread) held by tid=27 (Task_1)",
+ " at class.method2(Class.java:2)",
+ " at class.method2(Class.java:2)",
+ "",
+ "\"Task_2\" prio=5 tid=26 NATIVE",
+ " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001",
+ " | sysTid=4343 nice=0 sched=0/0 cgrp=foreground handle=0000000001",
+ " | schedstat=( 0 0 0 ) utm=6 stm=3 core=0",
+ " #00 pc 00001234 /system/lib/lib.so (addr+8)",
+ " #01 pc 00001235 /system/lib/lib.so (addr+16)",
+ " #02 pc 00001236 /system/lib/lib.so (addr+24)",
+ " at class.method1(Class.java:1)",
+ "",
+ "----- end 2887 -----",
+ "",
+ "",
+ "----- pid 256 at 2012-05-02 16:43:41 -----",
+ "Cmd line: system",
+ "",
+ "DALVIK THREADS:",
+ "(mutexes: tll=0 tsl=0 tscl=0 ghl=0)",
+ "",
+ "\"main\" prio=5 tid=1 NATIVE",
+ " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001",
+ " | sysTid=256 nice=0 sched=0/0 cgrp=foreground handle=0000000001",
+ " | schedstat=( 0 0 0 ) utm=175 stm=41 core=0",
+ " #00 pc 00001234 /system/lib/lib.so (addr+8)",
+ " #01 pc 00001235 /system/lib/lib.so (addr+16)",
+ " #02 pc 00001236 /system/lib/lib.so (addr+24)",
+ " at class.method1(Class.java:1)",
+ " at class.method2(Class.java:2)",
+ " at class.method2(Class.java:2)",
+ "",
+ "----- end 256 -----",
+ "");
+
+ List<String> expectedStack = Arrays.asList(
+ "\"main\" prio=5 tid=1 SUSPENDED",
+ " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001",
+ " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001",
+ " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0",
+ " at class.method1(Class.java:1)",
+ " at class.method2(Class.java:2)",
+ " at class.method2(Class.java:2)");
+
+ TracesItem traces = new TracesParser().parse(lines);
+ assertEquals(2887, traces.getPid().intValue());
+ assertEquals("com.android.package", traces.getApp());
+ assertEquals(ArrayUtil.join("\n", expectedStack), traces.getStack());
+ }
+
+ /**
+ * Test that both forms of cmd line match for the trace.
+ */
+ public void testTracesParser_cmdline() {
+ List<String> expectedStack = Arrays.asList(
+ "\"main\" prio=5 tid=1 SUSPENDED",
+ " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001",
+ " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001",
+ " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0",
+ " at class.method1(Class.java:1)",
+ " at class.method2(Class.java:2)",
+ " at class.method2(Class.java:2)");
+
+ List<String> lines = Arrays.asList(
+ "",
+ "",
+ "----- pid 2887 at 2012-05-02 16:43:41 -----",
+ "Cmd line: com.android.package",
+ "",
+ "DALVIK THREADS:",
+ "(mutexes: tll=0 tsl=0 tscl=0 ghl=0)",
+ "",
+ "\"main\" prio=5 tid=1 SUSPENDED",
+ " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001",
+ " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001",
+ " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0",
+ " at class.method1(Class.java:1)",
+ " at class.method2(Class.java:2)",
+ " at class.method2(Class.java:2)",
+ "");
+
+ TracesItem traces = new TracesParser().parse(lines);
+ assertEquals(2887, traces.getPid().intValue());
+ assertEquals("com.android.package", traces.getApp());
+ assertEquals(ArrayUtil.join("\n", expectedStack), traces.getStack());
+
+ lines = Arrays.asList(
+ "",
+ "",
+ "----- pid 2887 at 2012-05-02 16:43:41 -----",
+ "Cmdline: com.android.package Original command line: <unset>",
+ "",
+ "DALVIK THREADS:",
+ "(mutexes: tll=0 tsl=0 tscl=0 ghl=0)",
+ "",
+ "\"main\" prio=5 tid=1 SUSPENDED",
+ " | group=\"main\" sCount=1 dsCount=0 obj=0x00000001 self=0x00000001",
+ " | sysTid=2887 nice=0 sched=0/0 cgrp=foreground handle=0000000001",
+ " | schedstat=( 0 0 0 ) utm=5954 stm=1017 core=0",
+ " at class.method1(Class.java:1)",
+ " at class.method2(Class.java:2)",
+ " at class.method2(Class.java:2)",
+ "");
+
+ traces = new TracesParser().parse(lines);
+ assertEquals(2887, traces.getPid().intValue());
+ assertEquals("com.android.package", traces.getApp());
+ assertEquals(ArrayUtil.join("\n", expectedStack), traces.getStack());
+ }
+}
diff --git a/javatests/com/android/loganalysis/parser/WakelockParserTest.java b/javatests/com/android/loganalysis/parser/WakelockParserTest.java
new file mode 100644
index 0000000..eff4ec8
--- /dev/null
+++ b/javatests/com/android/loganalysis/parser/WakelockParserTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2015 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.loganalysis.parser;
+
+import com.android.loganalysis.item.WakelockItem;
+import com.android.loganalysis.item.WakelockItem.WakeLockCategory;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link WakelockParser}
+ */
+public class WakelockParserTest extends TestCase {
+
+ /**
+ * Test that normal input is parsed.
+ */
+ public void testKernelWakelockParser() {
+ List<String> inputBlock = Arrays.asList(
+ " All kernel wake locks:",
+ " Kernel Wake lock PowerManagerService.WakeLocks: 1h 3m 50s 5ms (8 times) realtime",
+ " Kernel Wake lock event0-2656 : 3m 49s 268ms (2399 times) realtime",
+ " Kernel Wake lock wlan_wd_wake: 3m 34s 639ms (1751 times) realtime",
+ " Kernel Wake lock wlan_rx_wake: 3m 19s 887ms (225 times) realtime",
+ " Kernel Wake lock wlan_tx_wake: 2m 19s 887ms (225 times) realtime",
+ " Kernel Wake lock tx_wake: 1m 19s 887ms (225 times) realtime",
+ " "
+ );
+
+ WakelockItem wakelock = new WakelockParser().parse(inputBlock);
+
+ assertEquals(WakelockParser.TOP_WAKELOCK_COUNT,
+ wakelock.getWakeLocks(WakeLockCategory.KERNEL_WAKELOCK).size());
+ assertEquals("event0-2656 ",
+ wakelock.getWakeLocks(WakeLockCategory.KERNEL_WAKELOCK).get(0).getName());
+ assertEquals(229268, wakelock.getWakeLocks(WakeLockCategory.KERNEL_WAKELOCK).
+ get(0).getHeldTime());
+ assertEquals(2399, wakelock.getWakeLocks(WakeLockCategory.KERNEL_WAKELOCK).
+ get(0).getLockedCount());
+ }
+
+ public void testPartialWakelockParser() {
+ List<String> inputBlock = Arrays.asList(
+ " All partial wake locks:",
+ " Wake lock u0a7 NlpWakeLock: 8m 13s 203ms (1479 times) max=0 realtime",
+ " Wake lock u0a7 NlpCollectorWakeLock: 6m 29s 18ms (238 times) max=0 realtime",
+ " Wake lock u0a7 GCM_CONN_ALARM: 6m 8s 587ms (239 times) max=0 realtime",
+ " Wake lock 1000 *alarm*: 5m 11s 316ms (1469 times) max=0 realtime",
+ " Wake lock u10 xxx: 4m 11s 316ms (1469 times) max=0 realtime",
+ " Wake lock u30 cst: 2m 11s 316ms (1469 times) max=0 realtime",
+ "");
+
+ WakelockItem wakelock = new WakelockParser().parse(inputBlock);
+
+ assertEquals(WakelockParser.TOP_WAKELOCK_COUNT,
+ wakelock.getWakeLocks(WakeLockCategory.PARTIAL_WAKELOCK).size());
+ assertEquals("NlpWakeLock", wakelock.getWakeLocks(WakeLockCategory.PARTIAL_WAKELOCK).
+ get(0).getName());
+ assertEquals("u0a7", wakelock.getWakeLocks(WakeLockCategory.PARTIAL_WAKELOCK).
+ get(0).getProcessUID());
+ assertEquals(493203, wakelock.getWakeLocks(WakeLockCategory.PARTIAL_WAKELOCK).
+ get(0).getHeldTime());
+ assertEquals(1479, wakelock.getWakeLocks(WakeLockCategory.PARTIAL_WAKELOCK).
+ get(0).getLockedCount());
+ }
+
+ public void testPartialWakelockParserOnOldFormat() {
+ List<String> inputBlock = Arrays.asList(
+ " All partial wake locks:",
+ " Wake lock u0a7 NlpWakeLock: 8m 13s 203ms (1479 times) realtime",
+ " Wake lock u0a7 NlpCollectorWakeLock: 6m 29s 18ms (238 times) realtime",
+ " Wake lock u0a7 GCM_CONN_ALARM: 6m 8s 587ms (239 times) realtime",
+ " Wake lock 1000 *alarm*: 5m 11s 316ms (1469 times) realtime",
+ " Wake lock u10 xxx: 4m 11s 316ms (1469 times) realtime",
+ " Wake lock u30 cst: 2m 11s 316ms (1469 times) realtime",
+ "");
+
+ WakelockItem wakelock = new WakelockParser().parse(inputBlock);
+
+ assertEquals(WakelockParser.TOP_WAKELOCK_COUNT,
+ wakelock.getWakeLocks(WakeLockCategory.PARTIAL_WAKELOCK).size());
+ assertEquals("NlpWakeLock", wakelock.getWakeLocks(WakeLockCategory.PARTIAL_WAKELOCK).
+ get(0).getName());
+ assertEquals("u0a7", wakelock.getWakeLocks(WakeLockCategory.PARTIAL_WAKELOCK).
+ get(0).getProcessUID());
+ assertEquals(493203, wakelock.getWakeLocks(WakeLockCategory.PARTIAL_WAKELOCK).
+ get(0).getHeldTime());
+ assertEquals(1479, wakelock.getWakeLocks(WakeLockCategory.PARTIAL_WAKELOCK).
+ get(0).getLockedCount());
+ }
+
+ public void testInvalidInputWakelockParser() {
+ List<String> inputBlock = Arrays.asList(
+ " lock PowerManagerService.WakeLocks: 1h 3m 50s 5ms (8 times) realtime",
+ " lock event0-2656 : 3m 49s 268ms (2399 times) realtime",
+ " lock wlan_wd_wake: 3m 34s 639ms (1751 times) realtime",
+ " lock wlan_rx_wake: 3m 19s 887ms (225 times) realtime",
+ " wlan_tx_wake: 2m 19s 887ms (225 times) realtime",
+ " tx_wake: 1m 19s 887ms (225 times) realtime",
+ " "
+ );
+
+ WakelockItem wakelock = new WakelockParser().parse(inputBlock);
+
+ assertEquals(0,
+ wakelock.getWakeLocks(WakeLockCategory.KERNEL_WAKELOCK).size());
+ assertEquals(0,
+ wakelock.getWakeLocks(WakeLockCategory.PARTIAL_WAKELOCK).size());
+ }
+}
+
diff --git a/javatests/com/android/loganalysis/rule/InterruptRuleTest.java b/javatests/com/android/loganalysis/rule/InterruptRuleTest.java
new file mode 100644
index 0000000..7a08db0
--- /dev/null
+++ b/javatests/com/android/loganalysis/rule/InterruptRuleTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 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.loganalysis.rule;
+
+import com.android.loganalysis.item.BatteryStatsDetailedInfoItem;
+import com.android.loganalysis.item.BugreportItem;
+import com.android.loganalysis.item.DumpsysBatteryStatsItem;
+import com.android.loganalysis.item.DumpsysItem;
+import com.android.loganalysis.item.InterruptItem;
+import com.android.loganalysis.item.InterruptItem.InterruptCategory;
+
+import junit.framework.TestCase;
+
+import org.json.JSONObject;
+
+/**
+ * Unit tests for {@link InterruptRule}
+ */
+public class InterruptRuleTest extends TestCase {
+
+ BugreportItem mBugreport;
+ DumpsysItem mDumpsys;
+ DumpsysBatteryStatsItem mDumpsysBatteryStats;
+ BatteryStatsDetailedInfoItem mBatteryStatsDetailedInfo;
+
+ @Override
+ public void setUp() {
+ mBugreport = new BugreportItem();
+ mDumpsys = new DumpsysItem();
+ mDumpsysBatteryStats = new DumpsysBatteryStatsItem();
+ mBatteryStatsDetailedInfo = new BatteryStatsDetailedInfoItem();
+
+ mBatteryStatsDetailedInfo.setTimeOnBattery(3902004);
+ mDumpsysBatteryStats.setDetailedBatteryStatsItem(mBatteryStatsDetailedInfo);
+ mDumpsys.setBatteryInfo(mDumpsysBatteryStats);
+ mBugreport.setDumpsys(mDumpsys);
+ }
+
+ /**
+ * Test interrupt analysis
+ */
+ public void testInterruptAnalysis() throws Exception {
+ InterruptItem interrupt = new InterruptItem();
+ interrupt.addInterrupt("2:bcmsdh_sdmmc:2:qcom,smd:2:msmgio", 40,
+ InterruptCategory.WIFI_INTERRUPT);
+ interrupt.addInterrupt("2:qcom,smd-rpm:2:fc4c.qcom,spmi", 7,
+ InterruptCategory.UNKNOWN_INTERRUPT);
+
+ mBatteryStatsDetailedInfo.setInterruptItem(interrupt);
+
+ InterruptRule interruptRule = new InterruptRule(mBugreport);
+ interruptRule.applyRule();
+ JSONObject analysis = interruptRule.getAnalysis();
+ assertNotNull(analysis);
+ assertTrue(analysis.has("INTERRUPT_ANALYSIS"));
+ assertEquals(analysis.getString("INTERRUPT_ANALYSIS"),
+ "Frequent interrupts from WIFI_INTERRUPT (2:bcmsdh_sdmmc:2:qcom,smd:2:msmgio).");
+ }
+
+
+ public void testNoSignificantInterruptAnalysis() throws Exception {
+ InterruptItem interrupt = new InterruptItem();
+ interrupt.addInterrupt("2:bcmsdh_sdmmc:2:qcom,smd:2:msmgio", 5,
+ InterruptCategory.WIFI_INTERRUPT);
+ interrupt.addInterrupt("2:qcom,smd-rpm:2:fc4c.qcom,spmi", 7,
+ InterruptCategory.UNKNOWN_INTERRUPT);
+
+ mBatteryStatsDetailedInfo.setInterruptItem(interrupt);
+
+ InterruptRule interruptRule = new InterruptRule(mBugreport);
+ interruptRule.applyRule();
+ JSONObject analysis = interruptRule.getAnalysis();
+ assertNotNull(analysis);
+ assertTrue(analysis.has("INTERRUPT_ANALYSIS"));
+ assertEquals(analysis.getString("INTERRUPT_ANALYSIS"),
+ "No interrupts woke up device more frequent than 120 secs.");
+ }
+
+ public void testMissingInterruptAnalysis() throws Exception {
+ InterruptRule interruptRule = new InterruptRule(mBugreport);
+ interruptRule.applyRule();
+ JSONObject analysis = interruptRule.getAnalysis();
+ assertNotNull(analysis);
+ assertTrue(analysis.has("INTERRUPT_ANALYSIS"));
+ assertEquals(analysis.getString("INTERRUPT_ANALYSIS"),
+ "No interrupts woke up device more frequent than 120 secs.");
+ }
+}
diff --git a/javatests/com/android/loganalysis/rule/LocationUsageRuleTest.java b/javatests/com/android/loganalysis/rule/LocationUsageRuleTest.java
new file mode 100644
index 0000000..5dd7e9e
--- /dev/null
+++ b/javatests/com/android/loganalysis/rule/LocationUsageRuleTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2016 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.loganalysis.rule;
+
+import com.android.loganalysis.item.ActivityServiceItem;
+import com.android.loganalysis.item.BatteryStatsDetailedInfoItem;
+import com.android.loganalysis.item.BugreportItem;
+import com.android.loganalysis.item.DumpsysBatteryStatsItem;
+import com.android.loganalysis.item.DumpsysItem;
+import com.android.loganalysis.item.LocationDumpsItem;
+
+import junit.framework.TestCase;
+
+import org.json.JSONObject;
+
+/**
+ * Unit tests for {@link LocationUsageRule}
+ */
+public class LocationUsageRuleTest extends TestCase {
+
+ BugreportItem mBugreport;
+ DumpsysItem mDumpsys;
+ DumpsysBatteryStatsItem mDumpsysBatteryStats;
+ BatteryStatsDetailedInfoItem mBatteryStatsDetailedInfo;
+ ActivityServiceItem mActivityService;
+
+ @Override
+ public void setUp() {
+ mBugreport = new BugreportItem();
+ mDumpsys = new DumpsysItem();
+ mDumpsysBatteryStats = new DumpsysBatteryStatsItem();
+ mBatteryStatsDetailedInfo = new BatteryStatsDetailedInfoItem();
+ mActivityService = new ActivityServiceItem();
+
+ mBatteryStatsDetailedInfo.setTimeOnBattery(3902004);
+ mDumpsysBatteryStats.setDetailedBatteryStatsItem(mBatteryStatsDetailedInfo);
+ mDumpsys.setBatteryInfo(mDumpsysBatteryStats);
+ mBugreport.setDumpsys(mDumpsys);
+ mBugreport.setActivityService(mActivityService);
+ }
+
+ /**
+ * Test location usage analysis
+ */
+ public void testLocationUsageAnalysis() throws Exception {
+ LocationDumpsItem location = new LocationDumpsItem();
+ location.addLocationClient("com.google.android.gms", 1, 0, 0, "PRIORITY_NO_POWER", 140);
+ location.addLocationClient("com.google.android.gms", 5, 5, 5,
+ "PRIORITY_BALANCED_POWER_ACCURACY", 140);
+ mActivityService.setLocationDumps(location);
+
+ LocationUsageRule locationUsageRule = new LocationUsageRule(mBugreport);
+ locationUsageRule.applyRule();
+ JSONObject analysis = locationUsageRule.getAnalysis();
+ assertNotNull(analysis);
+ assertTrue(analysis.has("LOCATION_USAGE_ANALYSIS"));
+ assertEquals(analysis.getString("LOCATION_USAGE_ANALYSIS"),
+ "Package com.google.android.gms is requesting for location updates every 5 secs "
+ + "with priority PRIORITY_BALANCED_POWER_ACCURACY.");
+ }
+
+ public void testNoSignificantLocationUsageAnalysis() throws Exception {
+ LocationDumpsItem location = new LocationDumpsItem();
+ location.addLocationClient("com.google.android.gms", 1, 0, 0, "PRIORITY_NO_POWER", 140);
+ location.addLocationClient("com.google.android.gms", 285, 285, 285,
+ "PRIORITY_BALANCED_POWER_ACCURACY", 140);
+ mActivityService.setLocationDumps(location);
+
+ LocationUsageRule locationUsageRule = new LocationUsageRule(mBugreport);
+ locationUsageRule.applyRule();
+ JSONObject analysis = locationUsageRule.getAnalysis();
+ assertNotNull(analysis);
+ assertTrue(analysis.has("LOCATION_USAGE_ANALYSIS"));
+ assertEquals(analysis.getString("LOCATION_USAGE_ANALYSIS"),
+ "No apps requested for frequent location updates.");
+ }
+
+ public void testNoLocationUsageAnalysis() throws Exception {
+ LocationUsageRule locationUsageRule = new LocationUsageRule(mBugreport);
+ locationUsageRule.applyRule();
+ JSONObject analysis = locationUsageRule.getAnalysis();
+ assertNotNull(analysis);
+ assertTrue(analysis.has("LOCATION_USAGE_ANALYSIS"));
+ assertEquals(analysis.getString("LOCATION_USAGE_ANALYSIS"),
+ "No apps requested for frequent location updates.");
+ }
+}
diff --git a/javatests/com/android/loganalysis/rule/ProcessUsageRuleTest.java b/javatests/com/android/loganalysis/rule/ProcessUsageRuleTest.java
new file mode 100644
index 0000000..cc322dc
--- /dev/null
+++ b/javatests/com/android/loganalysis/rule/ProcessUsageRuleTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2016 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.loganalysis.rule;
+
+import com.android.loganalysis.item.BatteryStatsDetailedInfoItem;
+import com.android.loganalysis.item.BugreportItem;
+import com.android.loganalysis.item.DumpsysBatteryStatsItem;
+import com.android.loganalysis.item.DumpsysItem;
+import com.android.loganalysis.item.ProcessUsageItem;
+import com.android.loganalysis.item.ProcessUsageItem.SensorInfoItem;
+
+import java.util.LinkedList;
+import junit.framework.TestCase;
+
+import org.json.JSONObject;
+
+/**
+ * Unit tests for {@link ProcessUsageRule}
+ */
+public class ProcessUsageRuleTest extends TestCase {
+
+ BugreportItem mBugreport;
+ DumpsysItem mDumpsys;
+ DumpsysBatteryStatsItem mDumpsysBatteryStats;
+ BatteryStatsDetailedInfoItem mBatteryStatsDetailedInfo;
+
+ @Override
+ public void setUp() {
+ mBugreport = new BugreportItem();
+ mDumpsys = new DumpsysItem();
+ mDumpsysBatteryStats = new DumpsysBatteryStatsItem();
+ mBatteryStatsDetailedInfo = new BatteryStatsDetailedInfoItem();
+
+ mBatteryStatsDetailedInfo.setTimeOnBattery(3902004);
+ mDumpsysBatteryStats.setDetailedBatteryStatsItem(mBatteryStatsDetailedInfo);
+ mDumpsys.setBatteryInfo(mDumpsysBatteryStats);
+ mBugreport.setDumpsys(mDumpsys);
+ }
+
+ /**
+ * Test alarm usage analysis
+ */
+ public void testAlarmAnalysis() throws Exception {
+ ProcessUsageItem processUsage = new ProcessUsageItem();
+ LinkedList<SensorInfoItem> uid0Sensor = new LinkedList<SensorInfoItem>();
+ uid0Sensor.add(new SensorInfoItem("0", 9908));
+ uid0Sensor.add(new SensorInfoItem("1", 9997));
+
+ LinkedList<SensorInfoItem> uidU0a9Sensor = new LinkedList<SensorInfoItem>();
+ uidU0a9Sensor.add(new SensorInfoItem("2", 1315));
+
+ processUsage.addProcessUsage("0", 0, uid0Sensor);
+ processUsage.addProcessUsage("u0a9", 180, uidU0a9Sensor);
+ processUsage.addProcessUsage("u0a8", 0, null);
+
+ mBatteryStatsDetailedInfo.setProcessUsageItem(processUsage);
+ ProcessUsageRule usage = new ProcessUsageRule(mBugreport);
+ usage.applyRule();
+ JSONObject analysis = usage.getAnalysis();
+ assertNotNull(analysis);
+ assertTrue(analysis.has("ALARM_USAGE_ANALYSIS"));
+ assertEquals(analysis.getString("ALARM_USAGE_ANALYSIS"),
+ "UID u0a9 has requested frequent repeating alarms.");
+ assertTrue(analysis.has("SENSOR_USAGE_ANALYSIS"));
+ assertEquals(analysis.getString("SENSOR_USAGE_ANALYSIS"),
+ "No apps used sensors more than 10% time on battery.");
+ }
+
+ /**
+ * Test sensor usage analysis
+ */
+ public void testSensorAnalysis() throws Exception {
+ ProcessUsageItem processUsage = new ProcessUsageItem();
+ LinkedList<SensorInfoItem> uid0Sensor = new LinkedList<SensorInfoItem>();
+ uid0Sensor.add(new SensorInfoItem("0", 9908));
+ uid0Sensor.add(new SensorInfoItem("1", 9997));
+
+ LinkedList<SensorInfoItem> uidU0a9Sensor = new LinkedList<SensorInfoItem>();
+ uidU0a9Sensor.add(new SensorInfoItem("2", 913015));
+
+ processUsage.addProcessUsage("0", 0, uid0Sensor);
+ processUsage.addProcessUsage("u0a9", 15, uidU0a9Sensor);
+ processUsage.addProcessUsage("u0a8", 0, null);
+
+ mBatteryStatsDetailedInfo.setProcessUsageItem(processUsage);
+ ProcessUsageRule usage = new ProcessUsageRule(mBugreport);
+ usage.applyRule();
+ JSONObject analysis = usage.getAnalysis();
+ assertNotNull(analysis);
+ assertTrue(analysis.has("SENSOR_USAGE_ANALYSIS"));
+ assertEquals(analysis.getString("SENSOR_USAGE_ANALYSIS"),
+ "sensor 2 was used for 0d 0h 15m 13s by UID u0a9.");
+
+ assertTrue(analysis.has("ALARM_USAGE_ANALYSIS"));
+ assertEquals(analysis.getString("ALARM_USAGE_ANALYSIS"),
+ "No apps requested for alarms more frequent than 60 secs.");
+ }
+}
diff --git a/javatests/com/android/loganalysis/rule/WakelockRuleTest.java b/javatests/com/android/loganalysis/rule/WakelockRuleTest.java
new file mode 100644
index 0000000..6900c07
--- /dev/null
+++ b/javatests/com/android/loganalysis/rule/WakelockRuleTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 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.loganalysis.rule;
+
+import com.android.loganalysis.item.BatteryStatsDetailedInfoItem;
+import com.android.loganalysis.item.BugreportItem;
+import com.android.loganalysis.item.DumpsysBatteryStatsItem;
+import com.android.loganalysis.item.DumpsysItem;
+import com.android.loganalysis.item.WakelockItem;
+import com.android.loganalysis.item.WakelockItem.WakeLockCategory;
+
+import junit.framework.TestCase;
+
+import org.json.JSONObject;
+
+/**
+ * Unit tests for {@link WakelockRule}
+ */
+public class WakelockRuleTest extends TestCase {
+
+ BugreportItem mBugreport;
+ DumpsysItem mDumpsys;
+ DumpsysBatteryStatsItem mDumpsysBatteryStats;
+ BatteryStatsDetailedInfoItem mBatteryStatsDetailedInfo;
+
+ @Override
+ public void setUp() {
+ mBugreport = new BugreportItem();
+ mDumpsys = new DumpsysItem();
+ mDumpsysBatteryStats = new DumpsysBatteryStatsItem();
+ mBatteryStatsDetailedInfo = new BatteryStatsDetailedInfoItem();
+
+ mBatteryStatsDetailedInfo.setTimeOnBattery(3902004);
+ mDumpsysBatteryStats.setDetailedBatteryStatsItem(mBatteryStatsDetailedInfo);
+ mDumpsys.setBatteryInfo(mDumpsysBatteryStats);
+ mBugreport.setDumpsys(mDumpsys);
+ }
+
+ /**
+ * Test wakelock analysis
+ */
+ public void testWakelockAnalysis() throws Exception {
+ WakelockItem wakelock = new WakelockItem();
+ wakelock.addWakeLock("PowerManagerService.WakeLocks", 310006, 2,
+ WakeLockCategory.KERNEL_WAKELOCK);
+ wakelock.addWakeLock("msm_serial_hs_rx", 133612, 258,
+ WakeLockCategory.KERNEL_WAKELOCK);
+
+ wakelock.addWakeLock("ProxyController", "1001", 3887565, 4,
+ WakeLockCategory.PARTIAL_WAKELOCK);
+ wakelock.addWakeLock("AudioMix", "1013", 1979, 3,
+ WakeLockCategory.PARTIAL_WAKELOCK);
+
+ mBatteryStatsDetailedInfo.setWakelockItem(wakelock);
+ WakelockRule wakelockRule = new WakelockRule(mBugreport);
+ wakelockRule.applyRule();
+ JSONObject analysis = wakelockRule.getAnalysis();
+ assertNotNull(analysis);
+ assertTrue(analysis.has("WAKELOCK_ANALYSIS"));
+ assertEquals(analysis.getString("WAKELOCK_ANALYSIS"),
+ "ProxyController PARTIAL_WAKELOCK is held for 0d 1h 4m 47s.");
+ }
+
+ public void testNoSignificantWakelockAnalysis() throws Exception {
+ WakelockItem wakelock = new WakelockItem();
+ wakelock.addWakeLock("PowerManagerService.WakeLocks", 310006, 2,
+ WakeLockCategory.KERNEL_WAKELOCK);
+ wakelock.addWakeLock("msm_serial_hs_rx", 133612, 258,
+ WakeLockCategory.KERNEL_WAKELOCK);
+
+ wakelock.addWakeLock("ProxyController", "1001", 287565, 4,
+ WakeLockCategory.PARTIAL_WAKELOCK);
+ wakelock.addWakeLock("AudioMix", "1013", 1979, 3,
+ WakeLockCategory.PARTIAL_WAKELOCK);
+
+ mBatteryStatsDetailedInfo.setWakelockItem(wakelock);
+ WakelockRule wakelockRule = new WakelockRule(mBugreport);
+ wakelockRule.applyRule();
+ JSONObject analysis = wakelockRule.getAnalysis();
+ assertNotNull(analysis);
+ assertTrue(analysis.has("WAKELOCK_ANALYSIS"));
+ assertEquals(analysis.getString("WAKELOCK_ANALYSIS"),
+ "No wakelocks were held for more than 10% of time on battery.");
+ }
+
+ public void testNoWakelockAnalysis() throws Exception {
+ WakelockRule wakelockRule = new WakelockRule(mBugreport);
+ wakelockRule.applyRule();
+ JSONObject analysis = wakelockRule.getAnalysis();
+ assertNotNull(analysis);
+ assertTrue(analysis.has("WAKELOCK_ANALYSIS"));
+ assertEquals(analysis.getString("WAKELOCK_ANALYSIS"),
+ "No wakelocks were held for more than 10% of time on battery.");
+ }
+}
diff --git a/javatests/com/android/loganalysis/rule/WifiStatsRuleTest.java b/javatests/com/android/loganalysis/rule/WifiStatsRuleTest.java
new file mode 100644
index 0000000..984ed20
--- /dev/null
+++ b/javatests/com/android/loganalysis/rule/WifiStatsRuleTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2016 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.loganalysis.rule;
+
+import com.android.loganalysis.item.BatteryStatsDetailedInfoItem;
+import com.android.loganalysis.item.BugreportItem;
+import com.android.loganalysis.item.DumpsysBatteryStatsItem;
+import com.android.loganalysis.item.DumpsysItem;
+import com.android.loganalysis.item.DumpsysWifiStatsItem;
+
+import junit.framework.TestCase;
+
+import org.json.JSONObject;
+
+/**
+ */
+public class WifiStatsRuleTest extends TestCase {
+
+ BugreportItem mBugreport;
+ DumpsysItem mDumpsys;
+ DumpsysBatteryStatsItem mDumpsysBatteryStats;
+ BatteryStatsDetailedInfoItem mBatteryStatsDetailedInfo;
+
+ @Override
+ public void setUp() {
+ mBugreport = new BugreportItem();
+ mDumpsys = new DumpsysItem();
+ mDumpsysBatteryStats = new DumpsysBatteryStatsItem();
+ mBatteryStatsDetailedInfo = new BatteryStatsDetailedInfoItem();
+
+ mBatteryStatsDetailedInfo.setTimeOnBattery(302004);
+ mDumpsysBatteryStats.setDetailedBatteryStatsItem(mBatteryStatsDetailedInfo);
+ mDumpsys.setBatteryInfo(mDumpsysBatteryStats);
+ mBugreport.setDumpsys(mDumpsys);
+ }
+
+ /**
+ * Test wifistats analysis
+ */
+ public void testWifiDisconnectAnalysis() throws Exception {
+ DumpsysWifiStatsItem wifiStats = new DumpsysWifiStatsItem();
+ wifiStats.setNumWifiDisconnect(1);
+ wifiStats.setNumWifiScan(0);
+ wifiStats.setNumWifiAssociation(0);
+
+ mDumpsys.setWifiStats(wifiStats);
+ WifiStatsRule wifiStatsRule = new WifiStatsRule(mBugreport);
+ wifiStatsRule.applyRule();
+ JSONObject analysis = wifiStatsRule.getAnalysis();
+ assertNotNull(analysis);
+ assertTrue(analysis.has("WIFI_STATS"));
+ assertEquals(analysis.getString("WIFI_STATS"),
+ "No apps requested for frequent wifi scans. Wifi got disconnected 1 times. "
+ + "No frequent wifi associations were observed.");
+ }
+
+ public void testWifiScanAnalysis() throws Exception {
+ DumpsysWifiStatsItem wifiStats = new DumpsysWifiStatsItem();
+ wifiStats.setNumWifiDisconnect(0);
+ wifiStats.setNumWifiScan(3);
+ wifiStats.setNumWifiAssociation(0);
+
+ mDumpsys.setWifiStats(wifiStats);
+ WifiStatsRule wifiStatsRule = new WifiStatsRule(mBugreport);
+ wifiStatsRule.applyRule();
+ JSONObject analysis = wifiStatsRule.getAnalysis();
+ assertNotNull(analysis);
+ assertTrue(analysis.has("WIFI_STATS"));
+ assertEquals(analysis.getString("WIFI_STATS"),
+ "Wifi scans happened every 100 seconds. No frequent wifi disconnects were "
+ + "observed. No frequent wifi associations were observed.");
+ }
+
+ public void testWifiAssociationAnalysis() throws Exception {
+ DumpsysWifiStatsItem wifiStats = new DumpsysWifiStatsItem();
+ wifiStats.setNumWifiDisconnect(0);
+ wifiStats.setNumWifiScan(0);
+ wifiStats.setNumWifiAssociation(3);
+
+ mDumpsys.setWifiStats(wifiStats);
+ WifiStatsRule wifiStatsRule = new WifiStatsRule(mBugreport);
+ wifiStatsRule.applyRule();
+ JSONObject analysis = wifiStatsRule.getAnalysis();
+ assertNotNull(analysis);
+ assertTrue(analysis.has("WIFI_STATS"));
+ assertEquals(analysis.getString("WIFI_STATS"),
+ "No apps requested for frequent wifi scans. No frequent wifi disconnects were "
+ + "observed. Wifi got associated with AP 3 times.");
+ }
+}
diff --git a/javatests/com/android/loganalysis/util/ArrayUtilTest.java b/javatests/com/android/loganalysis/util/ArrayUtilTest.java
new file mode 100644
index 0000000..98d85e6
--- /dev/null
+++ b/javatests/com/android/loganalysis/util/ArrayUtilTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.loganalysis.util;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link ArrayUtil}
+ */
+public class ArrayUtilTest extends TestCase {
+
+ /**
+ * Simple test for {@link ArrayUtil#buildArray(String[][])}
+ */
+ public void testBuildArray_arrays() {
+ String[] newArray = ArrayUtil.buildArray(new String[] {"1", "2"}, new String[] {"3"},
+ new String[] {"4"});
+ assertEquals(4, newArray.length);
+ for (int i = 0; i < 4; i++) {
+ assertEquals(Integer.toString(i+1), newArray[i]);
+ }
+ }
+
+ /**
+ * Make sure that Collections aren't double-wrapped
+ */
+ public void testJoinCollection() {
+ List<String> list = Arrays.asList("alpha", "beta", "gamma");
+ final String expected = "alpha, beta, gamma";
+ String str = ArrayUtil.join(", ", list);
+ assertEquals(expected, str);
+ }
+
+ /**
+ * Make sure that Arrays aren't double-wrapped
+ */
+ public void testJoinArray() {
+ String[] ary = new String[] {"alpha", "beta", "gamma"};
+ final String expected = "alpha, beta, gamma";
+ String str = ArrayUtil.join(", ", (Object[]) ary);
+ assertEquals(expected, str);
+ }
+
+ /**
+ * Make sure that join on varargs arrays work as expected
+ */
+ public void testJoinNormal() {
+ final String expected = "alpha, beta, gamma";
+ String str = ArrayUtil.join(", ", "alpha", "beta", "gamma");
+ assertEquals(expected, str);
+ }
+}
diff --git a/javatests/com/android/loganalysis/util/LogPatternUtilTest.java b/javatests/com/android/loganalysis/util/LogPatternUtilTest.java
new file mode 100644
index 0000000..98226d4
--- /dev/null
+++ b/javatests/com/android/loganalysis/util/LogPatternUtilTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2013 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.loganalysis.util;
+
+import junit.framework.TestCase;
+
+import java.util.regex.Pattern;
+
+/**
+ * Unit tests for {@link LogPatternUtil}.
+ */
+public class LogPatternUtilTest extends TestCase {
+
+ /**
+ * Test basic pattern matching.
+ */
+ public void testPatternMatching() {
+ LogPatternUtil patternUtil = new LogPatternUtil();
+ patternUtil.addPattern(Pattern.compile("abc"), "cat1");
+ patternUtil.addPattern(Pattern.compile("123"), "cat2");
+
+ assertNull(patternUtil.checkMessage("xyz"));
+ assertEquals("cat1", patternUtil.checkMessage("abc"));
+ assertEquals("cat2", patternUtil.checkMessage("123"));
+ }
+
+ /**
+ * Test pattern matching with extras.
+ */
+ public void testExtrasMatching() {
+ LogPatternUtil patternUtil = new LogPatternUtil();
+ patternUtil.addPattern(Pattern.compile("abc"), null, "cat1");
+ patternUtil.addPattern(Pattern.compile("123"), "E/tag1", "cat2");
+ patternUtil.addPattern(Pattern.compile("123"), "E/tag2", "cat3");
+
+ assertNull(patternUtil.checkMessage("xyz"));
+ assertEquals("cat1", patternUtil.checkMessage("abc"));
+ assertEquals("cat1", patternUtil.checkMessage("abc", "E/tag1"));
+ assertEquals("cat1", patternUtil.checkMessage("abc", "E/tag2"));
+ assertNull(patternUtil.checkMessage("123"));
+ assertEquals("cat2", patternUtil.checkMessage("123", "E/tag1"));
+ assertEquals("cat3", patternUtil.checkMessage("123", "E/tag2"));
+ }
+}
diff --git a/javatests/com/android/loganalysis/util/LogTailUtilTest.java b/javatests/com/android/loganalysis/util/LogTailUtilTest.java
new file mode 100644
index 0000000..d7316c6
--- /dev/null
+++ b/javatests/com/android/loganalysis/util/LogTailUtilTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2013 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.loganalysis.util;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link LogTailUtil}.
+ */
+public class LogTailUtilTest extends TestCase {
+
+ /**
+ * Test that last and id tails of the log are returned correctly.
+ */
+ public void testGetPreambles() {
+ LogTailUtil preambleUtil = new LogTailUtil(500, 3, 3);
+
+ assertEquals("", preambleUtil.getLastTail());
+ assertEquals("", preambleUtil.getIdTail(1));
+
+ preambleUtil.addLine(1, "line 1");
+ preambleUtil.addLine(2, "line 2");
+
+ assertEquals("line 1\nline 2", preambleUtil.getLastTail());
+ assertEquals("line 1", preambleUtil.getIdTail(1));
+
+ preambleUtil.addLine(1, "line 3");
+ preambleUtil.addLine(2, "line 4");
+ preambleUtil.addLine(1, "line 5");
+ preambleUtil.addLine(2, "line 6");
+ preambleUtil.addLine(1, "line 7");
+ preambleUtil.addLine(2, "line 8");
+
+ assertEquals("line 6\nline 7\nline 8", preambleUtil.getLastTail());
+ assertEquals("line 3\nline 5\nline 7", preambleUtil.getIdTail(1));
+ }
+
+ /**
+ * Test that the ring buffer is limited to a certain size.
+ */
+ public void testRingBufferSize() {
+ LogTailUtil preambleUtil = new LogTailUtil(5, 3, 3);
+ preambleUtil.addLine(1, "line 1");
+ preambleUtil.addLine(2, "line 2");
+ preambleUtil.addLine(2, "line 3");
+ preambleUtil.addLine(2, "line 4");
+ preambleUtil.addLine(2, "line 5");
+ preambleUtil.addLine(2, "line 6");
+
+ // The first line should roll off the end of the buffer.
+ assertEquals("", preambleUtil.getIdTail(1));
+ }
+}
diff --git a/javatests/com/android/loganalysis/util/RegexTrieTest.java b/javatests/com/android/loganalysis/util/RegexTrieTest.java
new file mode 100644
index 0000000..4b689b6
--- /dev/null
+++ b/javatests/com/android/loganalysis/util/RegexTrieTest.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2010 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.loganalysis.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.loganalysis.util.RegexTrie.CompPattern;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/** Set of unit tests to verify the behavior of the RegexTrie */
+@RunWith(JUnit4.class)
+public class RegexTrieTest {
+ private RegexTrie<Integer> mTrie = null;
+ private static final Integer STORED_VAL = 42;
+ private static final List<String> NULL_LIST = Arrays.asList((String)null);
+
+ @Before
+ public void setUp() throws Exception {
+ mTrie = new RegexTrie<Integer>();
+ }
+
+ @Test
+ public void testStringPattern() {
+ mTrie.put(STORED_VAL, "[p]art1", "[p]art2", "[p]art3");
+ Integer retrieved = mTrie.retrieve("part1", "part2", "part3");
+ assertEquals(STORED_VAL, retrieved);
+ }
+
+ @Test
+ public void testAlternation_single() {
+ mTrie.put(STORED_VAL, "alpha|beta");
+ Integer retrieved;
+ retrieved = mTrie.retrieve("alpha");
+ assertEquals(STORED_VAL, retrieved);
+ retrieved = mTrie.retrieve("beta");
+ assertEquals(STORED_VAL, retrieved);
+ retrieved = mTrie.retrieve("alpha|beta");
+ assertNull(retrieved);
+ retrieved = mTrie.retrieve("gamma");
+ assertNull(retrieved);
+ retrieved = mTrie.retrieve("alph");
+ assertNull(retrieved);
+ }
+
+ @Test
+ public void testAlternation_multiple() {
+ mTrie.put(STORED_VAL, "a|alpha", "b|beta");
+ Integer retrieved;
+ retrieved = mTrie.retrieve("a", "b");
+ assertEquals(STORED_VAL, retrieved);
+ retrieved = mTrie.retrieve("a", "beta");
+ assertEquals(STORED_VAL, retrieved);
+ retrieved = mTrie.retrieve("alpha", "b");
+ assertEquals(STORED_VAL, retrieved);
+ retrieved = mTrie.retrieve("alpha", "beta");
+ assertEquals(STORED_VAL, retrieved);
+
+ retrieved = mTrie.retrieve("alpha");
+ assertNull(retrieved);
+ retrieved = mTrie.retrieve("beta");
+ assertNull(retrieved);
+ retrieved = mTrie.retrieve("alpha", "bet");
+ assertNull(retrieved);
+ }
+
+ @Test
+ public void testGroups_fullMatch() {
+ mTrie.put(STORED_VAL, "a|(alpha)", "b|(beta)");
+ Integer retrieved;
+ List<List<String>> groups = new ArrayList<List<String>>();
+
+ retrieved = mTrie.retrieve(groups, "a", "b");
+ assertEquals(STORED_VAL, retrieved);
+ assertEquals(2, groups.size());
+ assertEquals(NULL_LIST, groups.get(0));
+ assertEquals(NULL_LIST, groups.get(1));
+
+ retrieved = mTrie.retrieve(groups, "a", "beta");
+ assertEquals(STORED_VAL, retrieved);
+ assertEquals(2, groups.size());
+ assertEquals(NULL_LIST, groups.get(0));
+ assertEquals(Arrays.asList("beta"), groups.get(1));
+
+ retrieved = mTrie.retrieve(groups, "alpha", "b");
+ assertEquals(STORED_VAL, retrieved);
+ assertEquals(2, groups.size());
+ assertEquals(Arrays.asList("alpha"), groups.get(0));
+ assertEquals(NULL_LIST, groups.get(1));
+
+ retrieved = mTrie.retrieve(groups, "alpha", "beta");
+ assertEquals(STORED_VAL, retrieved);
+ assertEquals(2, groups.size());
+ assertEquals(Arrays.asList("alpha"), groups.get(0));
+ assertEquals(Arrays.asList("beta"), groups.get(1));
+ }
+
+ @Test
+ public void testGroups_partialMatch() {
+ mTrie.put(STORED_VAL, "a|(alpha)", "b|(beta)");
+ Integer retrieved;
+ List<List<String>> groups = new ArrayList<List<String>>();
+
+ retrieved = mTrie.retrieve(groups, "alpha");
+ assertNull(retrieved);
+ assertEquals(1, groups.size());
+ assertEquals(Arrays.asList("alpha"), groups.get(0));
+
+ retrieved = mTrie.retrieve(groups, "beta");
+ assertNull(retrieved);
+ assertEquals(0, groups.size());
+
+ retrieved = mTrie.retrieve(groups, "alpha", "bet");
+ assertNull(retrieved);
+ assertEquals(1, groups.size());
+ assertEquals(Arrays.asList("alpha"), groups.get(0));
+
+ retrieved = mTrie.retrieve(groups, "alpha", "betar");
+ assertNull(retrieved);
+ assertEquals(1, groups.size());
+ assertEquals(Arrays.asList("alpha"), groups.get(0));
+
+ retrieved = mTrie.retrieve(groups, "alpha", "beta", "gamma");
+ assertNull(retrieved);
+ assertEquals(2, groups.size());
+ assertEquals(Arrays.asList("alpha"), groups.get(0));
+ assertEquals(Arrays.asList("beta"), groups.get(1));
+ }
+
+ /** Make sure that the wildcard functionality works */
+ @Test
+ public void testWildcard() {
+ mTrie.put(STORED_VAL, "a", null);
+ Integer retrieved;
+ List<List<String>> groups = new ArrayList<List<String>>();
+
+ retrieved = mTrie.retrieve(groups, "a", "b", "c");
+ assertEquals(STORED_VAL, retrieved);
+ assertEquals(3, groups.size());
+ assertTrue(groups.get(0).isEmpty());
+ assertEquals(Arrays.asList("b"), groups.get(1));
+ assertEquals(Arrays.asList("c"), groups.get(2));
+
+ retrieved = mTrie.retrieve(groups, "a");
+ assertNull(retrieved);
+ assertEquals(1, groups.size());
+ assertTrue(groups.get(0).isEmpty());
+ }
+
+ /**
+ * Make sure that if a wildcard and a more specific match could both match, that the more
+ * specific match takes precedence
+ */
+ @Test
+ public void testWildcard_precedence() {
+ // Do one before and one after the wildcard to check for ordering effects
+ mTrie.put(STORED_VAL + 1, "a", "(b)");
+ mTrie.put(STORED_VAL, "a", null);
+ mTrie.put(STORED_VAL + 2, "a", "(c)");
+ Integer retrieved;
+ List<List<String>> groups = new ArrayList<List<String>>();
+
+ retrieved = mTrie.retrieve(groups, "a", "d");
+ assertEquals(STORED_VAL, retrieved);
+ assertEquals(2, groups.size());
+ assertTrue(groups.get(0).isEmpty());
+ assertEquals(Arrays.asList("d"), groups.get(1));
+
+ retrieved = mTrie.retrieve(groups, "a", "b");
+ assertEquals((Integer)(STORED_VAL + 1), retrieved);
+ assertEquals(2, groups.size());
+ assertTrue(groups.get(0).isEmpty());
+ assertEquals(Arrays.asList("b"), groups.get(1));
+
+ retrieved = mTrie.retrieve(groups, "a", "c");
+ assertEquals((Integer)(STORED_VAL + 2), retrieved);
+ assertEquals(2, groups.size());
+ assertTrue(groups.get(0).isEmpty());
+ assertEquals(Arrays.asList("c"), groups.get(1));
+ }
+
+ /**
+ * Verify a bugfix: make sure that no NPE results from calling #retrieve with a wildcard but
+ * without a place to retrieve captures.
+ */
+ @Test
+ public void testWildcard_noCapture() throws NullPointerException {
+ mTrie.put(STORED_VAL, "a", null);
+ String[] key = new String[] {"a", "b", "c"};
+
+ mTrie.retrieve(key);
+ mTrie.retrieve(null, key);
+ // test passes if no exceptions were thrown
+ }
+
+ @Test
+ public void testMultiChild() {
+ mTrie.put(STORED_VAL + 1, "a", "b");
+ mTrie.put(STORED_VAL + 2, "a", "c");
+
+ Object retrieved;
+ retrieved = mTrie.retrieve("a", "b");
+ assertEquals(STORED_VAL + 1, retrieved);
+ retrieved = mTrie.retrieve("a", "c");
+ assertEquals(STORED_VAL + 2, retrieved);
+ }
+
+ /**
+ * Make sure that {@link CompPattern#equals} works as expected. Shake a proverbial fist at Java
+ */
+ @Test
+ public void testCompPattern_equality() {
+ String regex = "regex";
+ Pattern p1 = Pattern.compile(regex);
+ Pattern p2 = Pattern.compile(regex);
+ Pattern pOther = Pattern.compile("other");
+ CompPattern cp1 = new CompPattern(p1);
+ CompPattern cp2 = new CompPattern(p2);
+ CompPattern cpOther = new CompPattern(pOther);
+
+ // This is the problem with Pattern as implemented
+ assertNotEquals(p1, p2);
+ assertNotEquals(p2, p1);
+
+ // Make sure that wrapped patterns with the same regex are considered equivalent
+ assertEquals(cp2, p1);
+ assertEquals(cp2, p1);
+ assertEquals(cp2, cp1);
+
+ // And make sure that wrapped patterns with different regexen are still considered different
+ assertNotEquals(cp2, pOther);
+ assertNotEquals(cp2, cpOther);
+ }
+
+ @Test
+ public void testCompPattern_hashmap() {
+ HashMap<CompPattern, Integer> map = new HashMap<CompPattern, Integer>();
+ String regex = "regex";
+ Pattern p1 = Pattern.compile(regex);
+ Pattern p2 = Pattern.compile(regex);
+ Pattern pOther = Pattern.compile("other");
+ CompPattern cp1 = new CompPattern(p1);
+ CompPattern cp2 = new CompPattern(p2);
+ CompPattern cpOther = new CompPattern(pOther);
+
+ map.put(cp1, STORED_VAL);
+ assertTrue(map.containsKey(cp1));
+ assertTrue(map.containsKey(cp2));
+ assertFalse(map.containsKey(cpOther));
+
+ map.put(cpOther, STORED_VAL);
+ assertEquals(map.size(), 2);
+ assertTrue(map.containsKey(cp1));
+ assertTrue(map.containsKey(cp2));
+ assertTrue(map.containsKey(cpOther));
+ }
+}
+
diff --git a/javatests/com/android/loganalysis/util/config/ArgsOptionParserTest.java b/javatests/com/android/loganalysis/util/config/ArgsOptionParserTest.java
new file mode 100644
index 0000000..524cfa0
--- /dev/null
+++ b/javatests/com/android/loganalysis/util/config/ArgsOptionParserTest.java
@@ -0,0 +1,629 @@
+/*
+ * Copyright (C) 2010 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.loganalysis.util.config;
+
+import com.android.loganalysis.util.config.Option.Importance;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link ArgsOptionParser}.
+ */
+@SuppressWarnings("unused")
+public class ArgsOptionParserTest extends TestCase {
+
+ /**
+ * An option source with one {@link Option} specified.
+ */
+ private static class OneOptionSource {
+
+ private static final String DEFAULT_VALUE = "default";
+ private static final String OPTION_NAME = "my_option";
+ private static final String OPTION_DESC = "option description";
+
+ @Option(name=OPTION_NAME, shortName='o', description=OPTION_DESC)
+ private String mMyOption = DEFAULT_VALUE;
+ }
+
+ /**
+ * An option source with one {@link Option} specified.
+ */
+ private static class MapOptionSource {
+
+ private static final String OPTION_NAME = "my_option";
+ private static final String OPTION_DESC = "option description";
+
+ @Option(name=OPTION_NAME, shortName='o', description=OPTION_DESC)
+ private Map<Integer, Boolean> mMyOption = new HashMap<Integer, Boolean>();
+ }
+
+ /**
+ * An option source with boolean {@link Option} specified.
+ */
+ private static class BooleanOptionSource {
+
+ private static final boolean DEFAULT_BOOL = false;
+ private static final String DEFAULT_VALUE = "default";
+
+ @Option(name="my_boolean", shortName='b')
+ private boolean mMyBool = DEFAULT_BOOL;
+
+ @Option(name="my_option", shortName='o')
+ protected String mMyOption = DEFAULT_VALUE;
+ }
+
+ /**
+ * An option source with boolean {@link Option} specified with default = true.
+ */
+ private static class BooleanTrueOptionSource {
+
+ private static final boolean DEFAULT_BOOL = true;
+
+ @Option(name="my_boolean", shortName='b')
+ private boolean mMyBool = DEFAULT_BOOL;
+ }
+
+ /**
+ * An option source that has a superclass with options
+ */
+ private static class InheritedOptionSource extends OneOptionSource {
+
+ private static final String OPTION_NAME = "my_sub_option";
+ private static final String OPTION_DESC = "sub description";
+
+ @Option(name=OPTION_NAME, description=OPTION_DESC)
+ private String mMySubOption = "";
+ }
+
+ /**
+ * An option source for testing the {@link Option#importance()} settings
+ */
+ private static class ImportantOptionSource {
+
+ private static final String IMPORTANT_OPTION_NAME = "important_option";
+ private static final String IMPORTANT_UNSET_OPTION_NAME = "unset_important_option";
+ private static final String UNIMPORTANT_OPTION_NAME = "unimportant_option";
+
+ @Option(name = IMPORTANT_OPTION_NAME, description = IMPORTANT_OPTION_NAME,
+ importance = Importance.ALWAYS)
+ private String mImportantOption = "foo";
+
+ @Option(name = IMPORTANT_UNSET_OPTION_NAME, description = IMPORTANT_UNSET_OPTION_NAME,
+ importance = Importance.IF_UNSET)
+ private String mImportantUnsetOption = null;
+
+ @Option(name = UNIMPORTANT_OPTION_NAME, description = UNIMPORTANT_OPTION_NAME,
+ importance = Importance.NEVER)
+ private String mUnimportantOption = null;
+
+ ImportantOptionSource(String setOption) {
+ mImportantUnsetOption = setOption;
+ }
+
+ ImportantOptionSource() {
+ }
+ }
+
+ /**
+ * Option source whose options shouldn't end up in the global namespace
+ */
+ @OptionClass(alias = "ngos", global_namespace = false)
+ private static class NonGlobalOptionSource {
+ @Option(name = "option")
+ Boolean mOption = null;
+ }
+
+ /**
+ * Option source with mandatory options
+ */
+ private static class MandatoryOptionSourceNoDefault {
+ @Option(name = "no-default", mandatory = true)
+ private String mNoDefaultOption;
+ }
+
+ /**
+ * Option source with mandatory options
+ */
+ private static class MandatoryOptionSourceNull {
+ @Option(name = "null", mandatory = true)
+ private String mNullOption = null;
+ }
+
+ /**
+ * Option source with mandatory options
+ */
+ private static class MandatoryOptionSourceEmptyCollection {
+ @Option(name = "empty-collection", mandatory = true)
+ private Collection<String> mEmptyCollection = new ArrayList<String>(0);
+ }
+
+ /**
+ * Option source with mandatory options
+ */
+ private static class MandatoryOptionSourceEmptyMap {
+ @Option(name = "empty-map", mandatory = true)
+ private Map<String, String> mEmptyMap = new HashMap<String, String>();
+ }
+
+ /**
+ * An option source that exercises the {@link OptionUpdateRule}s.
+ */
+ private static class OptionUpdateRuleSource {
+
+ public static final String DEFAULT_VALUE = "5 default";
+ public static final String BIGGER_VALUE = "9 bigger";
+ public static final String SMALLER_VALUE = "0 smaller";
+
+ @Option(name = "default")
+ private String mDefaultOption = DEFAULT_VALUE;
+
+ @Option(name = "first", updateRule = OptionUpdateRule.FIRST)
+ private String mFirstOption = DEFAULT_VALUE;
+
+ @Option(name = "last", updateRule = OptionUpdateRule.LAST)
+ private String mLastOption = DEFAULT_VALUE;
+
+ @Option(name = "greatest", updateRule = OptionUpdateRule.GREATEST)
+ private String mGreatestOption = DEFAULT_VALUE;
+
+ @Option(name = "least", updateRule = OptionUpdateRule.LEAST)
+ private String mLeastOption = DEFAULT_VALUE;
+
+ @Option(name = "immutable", updateRule = OptionUpdateRule.IMMUTABLE)
+ private String mImmutableOption = DEFAULT_VALUE;
+
+ @Option(name = "null-immutable", updateRule = OptionUpdateRule.IMMUTABLE)
+ private String mNullImmutableOption = null;
+ }
+
+ /**
+ * Verify that {@link OptionUpdateRule}s work properly when the update compares to greater-than
+ * the default value.
+ */
+ public void testOptionUpdateRule_greater() throws Exception {
+ OptionUpdateRuleSource object = new OptionUpdateRuleSource();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ final String current = OptionUpdateRuleSource.DEFAULT_VALUE;
+ final String big = OptionUpdateRuleSource.BIGGER_VALUE;
+
+ parser.parse(new String[] {"--default", big, "--first", big, "--last", big,
+ "--greatest", big, "--least", big});
+ assertEquals(current, object.mFirstOption);
+ assertEquals(big, object.mLastOption);
+ assertEquals(big, object.mDefaultOption); // default should be LAST
+ assertEquals(big, object.mGreatestOption);
+ assertEquals(current, object.mLeastOption);
+ }
+
+ /**
+ * Verify that {@link OptionUpdateRule}s work properly when the update compares to greater-than
+ * the default value.
+ */
+ public void testOptionUpdateRule_lesser() throws Exception {
+ OptionUpdateRuleSource object = new OptionUpdateRuleSource();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ final String current = OptionUpdateRuleSource.DEFAULT_VALUE;
+ final String small = OptionUpdateRuleSource.SMALLER_VALUE;
+
+ parser.parse(new String[] {"--default", small, "--first", small, "--last", small,
+ "--greatest", small, "--least", small});
+ assertEquals(current, object.mFirstOption);
+ assertEquals(small, object.mLastOption);
+ assertEquals(small, object.mDefaultOption); // default should be LAST
+ assertEquals(current, object.mGreatestOption);
+ assertEquals(small, object.mLeastOption);
+ }
+
+ /**
+ * Verify that {@link OptionUpdateRule}s work properly when the update compares to greater-than
+ * the default value.
+ */
+ public void testOptionUpdateRule_immutable() throws Exception {
+ OptionUpdateRuleSource object = new OptionUpdateRuleSource();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ final String update = OptionUpdateRuleSource.BIGGER_VALUE;
+
+ try {
+ parser.parse(new String[] {"--immutable", update});
+ fail("ConfigurationException not thrown when updating an IMMUTABLE option");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+
+ assertNull(object.mNullImmutableOption);
+ parser.parse(new String[] {"--null-immutable", update});
+ assertEquals(update, object.mNullImmutableOption);
+
+ try {
+ parser.parse(new String[] {"--null-immutable", update});
+ fail("ConfigurationException not thrown when updating an IMMUTABLE option");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Setting an option with a namespace alias should work fine
+ */
+ public void testNonGlobalOptionSource_alias() throws Exception {
+ NonGlobalOptionSource source = new NonGlobalOptionSource();
+ ArgsOptionParser parser = new ArgsOptionParser(source);
+
+ assertNull(source.mOption);
+ parser.parse(new String[] {"--ngos:option"});
+ assertTrue(source.mOption);
+ parser.parse(new String[] {"--ngos:no-option"});
+ assertFalse(source.mOption);
+ }
+
+ /**
+ * Setting an option with a classname namespace should work fine
+ */
+ public void testNonGlobalOptionSource_className() throws Exception {
+ NonGlobalOptionSource source = new NonGlobalOptionSource();
+ ArgsOptionParser parser = new ArgsOptionParser(source);
+
+ assertNull(source.mOption);
+ parser.parse(new String[] {String.format("--%s:option", source.getClass().getName())});
+ assertTrue(source.mOption);
+ parser.parse(new String[] {String.format("--%s:no-option", source.getClass().getName())});
+ assertFalse(source.mOption);
+ }
+
+ /**
+ * Setting an option without a namespace should fail
+ */
+ public void testNonGlobalOptionSource_global() throws Exception {
+ NonGlobalOptionSource source = new NonGlobalOptionSource();
+ ArgsOptionParser parser = new ArgsOptionParser(source);
+
+ assertNull(source.mOption);
+ try {
+ parser.parse(new String[] {"--option"});
+ fail("ConfigurationException not thrown when assigning a global option to an @Option " +
+ "field in a non-global-namespace class");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+
+ try {
+ parser.parse(new String[] {"--no-option"});
+ fail("ConfigurationException not thrown when assigning a global option to an @Option " +
+ "field in a non-global-namespace class");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Test passing an empty argument list for an object that has one option specified.
+ * <p/>
+ * Expected that the option field should retain its default value.
+ */
+ public void testParse_noArg() throws ConfigurationException {
+ OneOptionSource object = new OneOptionSource();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ parser.parse(new String[] {});
+ assertEquals(OneOptionSource.DEFAULT_VALUE, object.mMyOption);
+ }
+
+ /**
+ * Test passing an single argument for an object that has one option specified.
+ */
+ public void testParse_oneArg() throws ConfigurationException {
+ OneOptionSource object = new OneOptionSource();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ final String expectedValue = "set";
+ parser.parse(new String[] {"--my_option", expectedValue});
+ assertEquals(expectedValue, object.mMyOption);
+ }
+
+ /**
+ * Test passing an single argument for an object that has one option specified.
+ */
+ public void testParse_oneMapArg() throws ConfigurationException {
+ MapOptionSource object = new MapOptionSource();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ final int expectedKey = 13;
+ final boolean expectedValue = true;
+ parser.parse(new String[] {"--my_option", Integer.toString(expectedKey),
+ Boolean.toString(expectedValue)});
+ assertNotNull(object.mMyOption);
+ assertEquals(1, object.mMyOption.size());
+ assertEquals(expectedValue, (boolean) object.mMyOption.get(expectedKey));
+ }
+
+ /**
+ * Test passing an single argument for an object that has one option specified.
+ */
+ public void testParseMapArg_mismatchKeyType() throws ConfigurationException {
+ MapOptionSource object = new MapOptionSource();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ final String expectedKey = "istanbul";
+ final boolean expectedValue = true;
+ try {
+ parser.parse(new String[] {"--my_option", expectedKey, Boolean.toString(expectedValue)});
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expect an exception that explicitly mentions that the "key" is incorrect
+ assertTrue(String.format("Expected exception message to contain 'key': %s",
+ e.getMessage()), e.getMessage().contains("key"));
+ assertTrue(String.format("Expected exception message to contain '%s': %s",
+ expectedKey, e.getMessage()), e.getMessage().contains(expectedKey));
+ }
+ }
+
+ /**
+ * Test passing an single argument for an object that has one option specified.
+ */
+ public void testParseMapArg_mismatchValueType() throws ConfigurationException {
+ MapOptionSource object = new MapOptionSource();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ final int expectedKey = 13;
+ final String expectedValue = "notconstantinople";
+ try {
+ parser.parse(new String[] {"--my_option", Integer.toString(expectedKey), expectedValue});
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expect an exception that explicitly mentions that the "value" is incorrect
+ assertTrue(String.format("Expected exception message to contain 'value': '%s'",
+ e.getMessage()), e.getMessage().contains("value"));
+ assertTrue(String.format("Expected exception message to contain '%s': %s",
+ expectedValue, e.getMessage()), e.getMessage().contains(expectedValue));
+ }
+ }
+
+ /**
+ * Test passing an single argument for an object that has one option specified.
+ */
+ public void testParseMapArg_missingKey() throws ConfigurationException {
+ MapOptionSource object = new MapOptionSource();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ try {
+ parser.parse(new String[] {"--my_option"});
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expect an exception that explicitly mentions that the "key" is incorrect
+ assertTrue(String.format("Expected exception message to contain 'key': '%s'",
+ e.getMessage()), e.getMessage().contains("key"));
+ }
+ }
+
+ /**
+ * Test passing an single argument for an object that has one option specified.
+ */
+ public void testParseMapArg_missingValue() throws ConfigurationException {
+ MapOptionSource object = new MapOptionSource();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ final int expectedKey = 13;
+ try {
+ parser.parse(new String[] {"--my_option", Integer.toString(expectedKey)});
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expect an exception that explicitly mentions that the "value" is incorrect
+ assertTrue(String.format("Expected exception message to contain 'value': '%s'",
+ e.getMessage()), e.getMessage().contains("value"));
+ }
+ }
+
+ /**
+ * Test passing an single argument for an object that has one option specified, using the
+ * option=value notation.
+ */
+ public void testParse_oneArgEquals() throws ConfigurationException {
+ OneOptionSource object = new OneOptionSource();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ final String expectedValue = "set";
+ parser.parse(new String[] {String.format("--my_option=%s", expectedValue)});
+ assertEquals(expectedValue, object.mMyOption);
+ }
+
+ /**
+ * Test passing a single argument for an object that has one option specified, using the
+ * short option notation.
+ */
+ public void testParse_oneShortArg() throws ConfigurationException {
+ OneOptionSource object = new OneOptionSource();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ final String expectedValue = "set";
+ parser.parse(new String[] {"-o", expectedValue});
+ assertEquals(expectedValue, object.mMyOption);
+ }
+
+ /**
+ * Test that "--" marks the beginning of positional arguments
+ */
+ public void testParse_posArgs() throws ConfigurationException {
+ OneOptionSource object = new OneOptionSource();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ final String expectedValue = "set";
+ // have a position argument with a long option prefix, to try to confuse the parser
+ final String posArg = "--unused";
+ List<String> leftOver = parser.parse(new String[] {"-o", expectedValue, "--", posArg});
+ assertEquals(expectedValue, object.mMyOption);
+ assertTrue(leftOver.contains(posArg));
+ }
+
+ /**
+ * Test passing a single boolean argument.
+ */
+ public void testParse_boolArg() throws ConfigurationException {
+ BooleanOptionSource object = new BooleanOptionSource();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ parser.parse(new String[] {"-b"});
+ assertTrue(object.mMyBool);
+ }
+
+ /**
+ * Test passing a boolean argument with another short argument.
+ */
+ public void testParse_boolTwoArg() throws ConfigurationException {
+ BooleanOptionSource object = new BooleanOptionSource();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ final String expectedValue = "set";
+ parser.parse(new String[] {"-bo", expectedValue});
+ assertTrue(object.mMyBool);
+ assertEquals(expectedValue, object.mMyOption);
+ }
+
+ /**
+ * Test passing a boolean argument with another short argument, with value concatenated.
+ * e.g -bovalue
+ */
+ public void testParse_boolTwoArgValue() throws ConfigurationException {
+ BooleanOptionSource object = new BooleanOptionSource();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ final String expectedValue = "set";
+ parser.parse(new String[] {String.format("-bo%s", expectedValue)});
+ assertTrue(object.mMyBool);
+ assertEquals(expectedValue, object.mMyOption);
+ }
+
+ /**
+ * Test the "--no-<bool option>" syntax
+ */
+ public void testParse_boolFalse() throws ConfigurationException {
+ BooleanTrueOptionSource object = new BooleanTrueOptionSource();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ parser.parse(new String[] {"--no-my_boolean"});
+ assertFalse(object.mMyBool);
+ }
+
+ /**
+ * Test the boolean long option syntax
+ */
+ public void testParse_boolLong() throws ConfigurationException {
+ BooleanOptionSource object = new BooleanOptionSource();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ parser.parse(new String[] {"--my_boolean"});
+ assertTrue(object.mMyBool);
+ }
+
+ /**
+ * Test passing arg string where value is missing
+ */
+ public void testParse_missingValue() throws ConfigurationException {
+ OneOptionSource object = new OneOptionSource();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ try {
+ parser.parse(new String[] {"--my_option"});
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Test parsing args for an option that does not exist.
+ */
+ public void testParse_optionNotPresent() throws ConfigurationException {
+ OneOptionSource object = new OneOptionSource();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ try {
+ parser.parse(new String[] {"--my_option", "set", "--not_here", "value"});
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Test that help text is displayed for all fields
+ */
+ public void testGetOptionHelp() {
+ String help = ArgsOptionParser.getOptionHelp(false, new InheritedOptionSource());
+ assertTrue(help.contains(InheritedOptionSource.OPTION_NAME));
+ assertTrue(help.contains(InheritedOptionSource.OPTION_DESC));
+ assertTrue(help.contains(OneOptionSource.OPTION_NAME));
+ assertTrue(help.contains(OneOptionSource.OPTION_DESC));
+ assertTrue(help.contains(OneOptionSource.DEFAULT_VALUE));
+ }
+
+ /**
+ * Test displaying important only help text
+ */
+ public void testGetOptionHelp_important() {
+ String help = ArgsOptionParser.getOptionHelp(true, new ImportantOptionSource());
+ assertTrue(help.contains(ImportantOptionSource.IMPORTANT_OPTION_NAME));
+ assertTrue(help.contains(ImportantOptionSource.IMPORTANT_UNSET_OPTION_NAME));
+ assertFalse(help.contains(ImportantOptionSource.UNIMPORTANT_OPTION_NAME));
+ }
+
+ /**
+ * Test that {@link Importance#IF_UNSET} {@link Option}s are hidden from help if set.
+ */
+ public void testGetOptionHelp_importantUnset() {
+ String help = ArgsOptionParser.getOptionHelp(true, new ImportantOptionSource("foo"));
+ assertTrue(help.contains(ImportantOptionSource.IMPORTANT_OPTION_NAME));
+ assertFalse(help.contains(ImportantOptionSource.IMPORTANT_UNSET_OPTION_NAME));
+ assertFalse(help.contains(ImportantOptionSource.UNIMPORTANT_OPTION_NAME));
+ }
+
+ public void testMandatoryOption_noDefault() throws Exception {
+ MandatoryOptionSourceNoDefault object = new MandatoryOptionSourceNoDefault();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ // expect success
+ parser.parse(new String[] {});
+ try {
+ parser.validateMandatoryOptions();
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ public void testMandatoryOption_null() throws Exception {
+ MandatoryOptionSourceNull object = new MandatoryOptionSourceNull();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ parser.parse(new String[] {});
+ try {
+ parser.validateMandatoryOptions();
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ public void testMandatoryOption_emptyCollection() throws Exception {
+ MandatoryOptionSourceEmptyCollection object = new MandatoryOptionSourceEmptyCollection();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ parser.parse(new String[] {});
+ try {
+ parser.validateMandatoryOptions();
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ public void testMandatoryOption_emptyMap() throws Exception {
+ MandatoryOptionSourceEmptyMap object = new MandatoryOptionSourceEmptyMap();
+ ArgsOptionParser parser = new ArgsOptionParser(object);
+ parser.parse(new String[] {});
+ try {
+ parser.validateMandatoryOptions();
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+}
diff --git a/javatests/com/android/loganalysis/util/config/OptionSetterTest.java b/javatests/com/android/loganalysis/util/config/OptionSetterTest.java
new file mode 100644
index 0000000..308e895
--- /dev/null
+++ b/javatests/com/android/loganalysis/util/config/OptionSetterTest.java
@@ -0,0 +1,828 @@
+/*
+ * Copyright (C) 2010 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.loganalysis.util.config;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link OptionSetter}.
+ */
+public class OptionSetterTest extends TestCase {
+
+ /** Option source with generic type. */
+ private static class GenericTypeOptionSource {
+ @Option(name = "my_option", shortName = 'o')
+ private Collection<?> mMyOption;
+ }
+
+ /** Option source with unparameterized type. */
+ @SuppressWarnings("rawtypes")
+ private static class CollectionTypeOptionSource {
+ @Option(name = "my_option", shortName = 'o')
+ private Collection mMyOption;
+ }
+
+ private static class MyGeneric<T> {
+ }
+
+ /** Option source with unparameterized type. */
+ private static class NonCollectionGenericTypeOptionSource {
+ @Option(name = "my_option", shortName = 'o')
+ private MyGeneric<String> mMyOption;
+ }
+
+ /** Option source with options with same name. */
+ private static class DuplicateOptionSource {
+ @Option(name = "string", shortName = 's')
+ private String mMyOption;
+
+ @Option(name = "string", shortName = 's')
+ private String mMyDuplicateOption;
+ }
+
+ /** Option source with an option with same name as AllTypesOptionSource. */
+ @OptionClass(alias = "shared")
+ private static class SharedOptionSource {
+ @Option(name = "string", shortName = 's')
+ private String mMyOption;
+
+ @Option(name = "enum")
+ private DefaultEnumClass mEnum = null;
+
+ @Option(name = "string_collection")
+ private Collection<String> mStringCollection = new ArrayList<String>();
+
+ @Option(name = "enumMap")
+ private Map<DefaultEnumClass, CustomEnumClass> mEnumMap =
+ new HashMap<DefaultEnumClass, CustomEnumClass>();
+
+ @Option(name = "enumCollection")
+ private Collection<DefaultEnumClass> mEnumCollection =
+ new ArrayList<DefaultEnumClass>();
+ }
+
+ /**
+ * Option source with an option with same name as AllTypesOptionSource, but a different type.
+ */
+ private static class SharedOptionWrongTypeSource {
+ @Option(name = "string", shortName = 's')
+ private int mMyOption;
+ }
+
+ /** option source with all supported types. */
+ @OptionClass(alias = "all")
+ private static class AllTypesOptionSource {
+ @Option(name = "string_collection")
+ private final Collection<String> mStringCollection = new ArrayList<String>();
+
+ @Option(name = "string_string_map")
+ private Map<String, String> mStringMap = new HashMap<String, String>();
+
+ @Option(name = "string")
+ private String mString = null;
+
+ @Option(name = "boolean")
+ private boolean mBool = false;
+
+ @Option(name = "booleanObj")
+ private Boolean mBooleanObj = false;
+
+ @Option(name = "byte")
+ private byte mByte = 0;
+
+ @Option(name = "byteObj")
+ private Byte mByteObj = 0;
+
+ @Option(name = "short")
+ private short mShort = 0;
+
+ @Option(name = "shortObj")
+ private Short mShortObj = null;
+
+ @Option(name = "int")
+ private int mInt = 0;
+
+ @Option(name = "intObj")
+ private Integer mIntObj = 0;
+
+ @Option(name = "long")
+ private long mLong = 0;
+
+ @Option(name = "longObj")
+ private Long mLongObj = null;
+
+ @Option(name = "float")
+ private float mFloat = 0;
+
+ @Option(name = "floatObj")
+ private Float mFloatObj = null;
+
+ @Option(name = "double")
+ private double mDouble = 0;
+
+ @Option(name = "doubleObj")
+ private Double mDoubleObj = null;
+
+ @Option(name = "file")
+ private File mFile = null;
+
+ @Option(name = "enum")
+ private DefaultEnumClass mEnum = null;
+
+ @Option(name = "customEnum")
+ private CustomEnumClass mCustomEnum = null;
+
+ @Option(name = "enumMap")
+ private Map<DefaultEnumClass, CustomEnumClass> mEnumMap =
+ new HashMap<DefaultEnumClass, CustomEnumClass>();
+
+ @Option(name = "enumCollection")
+ private Collection<DefaultEnumClass> mEnumCollection =
+ new ArrayList<DefaultEnumClass>();
+ }
+
+ private static class ParentOptionSource {
+ @Option(name = "string")
+ private String mString = null;
+
+ protected String getParentString() {
+ return mString;
+ }
+ }
+
+ private static class ChildOptionSource extends ParentOptionSource {
+ @Option(name = "child-string")
+ private String mChildString = null;
+ }
+
+ /**
+ * Option source with invalid option name.
+ */
+ private static class BadOptionNameSource {
+ @Option(name = "bad:string", shortName = 's')
+ private int mMyOption;
+ }
+
+ private static enum DefaultEnumClass {
+ VAL1, VAL3, VAL2;
+ }
+
+ private static enum CustomEnumClass {
+ VAL1(42);
+
+ private int mVal;
+
+ CustomEnumClass(int val) {
+ mVal = val;
+ }
+
+ public int getVal() {
+ return mVal;
+ }
+ }
+
+ private static class FinalOption {
+ @Option(name = "final-string", description="final field, not allowed")
+ private final String mFinal= "foo";
+ }
+
+ /**
+ * Test creating an {@link OptionSetter} for a source with invalid option type.
+ */
+ public void testOptionSetter_noType() {
+ try {
+ new OptionSetter(new GenericTypeOptionSource());
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Test creating an {@link OptionSetter} for a source with duplicate option names.
+ */
+ public void testOptionSetter_duplicateOptions() {
+ try {
+ new OptionSetter(new DuplicateOptionSource());
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Test option with same name can be used in multiple option sources.
+ */
+ public void testOptionSetter_sharedOptions() throws ConfigurationException {
+ AllTypesOptionSource object1 = new AllTypesOptionSource();
+ SharedOptionSource object2 = new SharedOptionSource();
+ OptionSetter setter = new OptionSetter(object1, object2);
+ setter.setOptionValue("string", "test");
+ assertEquals("test", object1.mString);
+ assertEquals("test", object2.mMyOption);
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for Enums used as the key and value
+ * of a {@link Map}.
+ */
+ public void testOptionSetter_sharedEnumMap() throws ConfigurationException {
+ AllTypesOptionSource object1 = new AllTypesOptionSource();
+ SharedOptionSource object2 = new SharedOptionSource();
+
+ final String key = "VAL1";
+ final String value = "VAL1";
+ final DefaultEnumClass expectedKey = DefaultEnumClass.VAL1;
+ final CustomEnumClass expectedValue = CustomEnumClass.VAL1;
+
+ // Actually set the key/value pair
+ OptionSetter parser = new OptionSetter(object1, object2);
+ parser.setOptionMapValue("enumMap", key, value);
+
+ // verify object1
+ assertEquals(1, object1.mEnumMap.size());
+ assertNotNull(object1.mEnumMap.get(expectedKey));
+ assertEquals(expectedValue, object1.mEnumMap.get(expectedKey));
+
+ // verify object2
+ assertEquals(1, object2.mEnumMap.size());
+ assertNotNull(object2.mEnumMap.get(expectedKey));
+ assertEquals(expectedValue, object2.mEnumMap.get(expectedKey));
+ }
+
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for Enums used as the key and value
+ * of a {@link Map}.
+ */
+ public void testOptionSetter_sharedEnumCollection() throws ConfigurationException {
+ AllTypesOptionSource object1 = new AllTypesOptionSource();
+ SharedOptionSource object2 = new SharedOptionSource();
+
+ final String value = "VAL1";
+ final DefaultEnumClass expectedValue = DefaultEnumClass.VAL1;
+
+ // Actually add the element
+ OptionSetter parser = new OptionSetter(object1, object2);
+ parser.setOptionValue("enumCollection", value);
+
+ // verify object1
+ assertEquals(1, object1.mEnumCollection.size());
+ assertTrue(object1.mEnumCollection.contains(expectedValue));
+
+ // verify object2
+ assertEquals(1, object2.mEnumCollection.size());
+ assertTrue(object2.mEnumCollection.contains(expectedValue));
+ }
+
+
+ /**
+ * Test that multiple options with same name must have the same type.
+ */
+ public void testOptionSetter_sharedOptionsDiffType() {
+ try {
+ new OptionSetter(new AllTypesOptionSource(), new SharedOptionWrongTypeSource());
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Test namespaced options using class names.
+ */
+ public void testOptionSetter_namespacedClassName() throws ConfigurationException {
+ AllTypesOptionSource object1 = new AllTypesOptionSource();
+ SharedOptionSource object2 = new SharedOptionSource();
+ OptionSetter setter = new OptionSetter(object1, object2);
+ setter.setOptionValue(AllTypesOptionSource.class.getName() + ":string", "alltest");
+ setter.setOptionValue(SharedOptionSource.class.getName() + ":string", "sharedtest");
+ assertEquals("alltest", object1.mString);
+ assertEquals("sharedtest", object2.mMyOption);
+ }
+
+ /**
+ * Test namespaced options using OptionClass aliases
+ */
+ public void testOptionSetter_namespacedAlias() throws ConfigurationException {
+ AllTypesOptionSource object1 = new AllTypesOptionSource();
+ SharedOptionSource object2 = new SharedOptionSource();
+ OptionSetter setter = new OptionSetter(object1, object2);
+ setter.setOptionValue("all:string", "alltest");
+ setter.setOptionValue("shared:string", "sharedtest");
+ assertEquals("alltest", object1.mString);
+ assertEquals("sharedtest", object2.mMyOption);
+ }
+
+ /**
+ * Test creating an {@link OptionSetter} for a Collection with no type.
+ */
+ public void testOptionSetter_unparamType() {
+ try {
+ new OptionSetter(new CollectionTypeOptionSource());
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Test creating an {@link OptionSetter} for a non collection option with generic type
+ */
+ public void testOptionSetter_genericType() {
+ try {
+ new OptionSetter(new NonCollectionGenericTypeOptionSource());
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Test creating an {@link OptionSetter} for class with inherited options
+ */
+ public void testOptionSetter_inheritedOptions() throws ConfigurationException {
+ ChildOptionSource source = new ChildOptionSource();
+ OptionSetter setter = new OptionSetter(source);
+ setter.setOptionValue("string", "parent");
+ setter.setOptionValue("child-string", "child");
+ assertEquals("parent", source.getParentString());
+ assertEquals("child", source.mChildString);
+ }
+
+ /**
+ * Test that options with {@link OptionSetter#NAMESPACE_SEPARATOR} are rejected
+ */
+ public void testOptionSetter_badOptionName() {
+ try {
+ new OptionSetter(new BadOptionNameSource());
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Test {@link OptionSetter#isBooleanOption(String)} when passed an unknown option name
+ */
+ public void testIsBooleanOption_unknown() throws ConfigurationException {
+ OptionSetter parser = new OptionSetter(new AllTypesOptionSource());
+ try {
+ parser.isBooleanOption("unknown");
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Test {@link OptionSetter#isBooleanOption(String)} when passed boolean option name
+ */
+ public void testIsBooleanOption_true() throws ConfigurationException {
+ OptionSetter parser = new OptionSetter(new AllTypesOptionSource());
+ assertTrue(parser.isBooleanOption("boolean"));
+ }
+
+ /**
+ * Test {@link OptionSetter#isBooleanOption(String)} when passed boolean option name for a
+ * Boolean object
+ */
+ public void testIsBooleanOption_objTrue() throws ConfigurationException {
+ OptionSetter parser = new OptionSetter(new AllTypesOptionSource());
+ assertTrue(parser.isBooleanOption("booleanObj"));
+ }
+
+ /**
+ * Test {@link OptionSetter#isBooleanOption(String)} when passed non-boolean option
+ */
+ public void testIsBooleanOption_false() throws ConfigurationException {
+ OptionSetter parser = new OptionSetter(new AllTypesOptionSource());
+ assertFalse(parser.isBooleanOption("string"));
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} when passed an unknown option name
+ */
+ public void testSetOptionValue_unknown() throws ConfigurationException {
+ OptionSetter parser = new OptionSetter(new AllTypesOptionSource());
+ try {
+ parser.setOptionValue("unknown", "foo");
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Test setting a value for a option with an unknown generic type.
+ */
+ public void testSetOptionValue_unknownType() throws ConfigurationException {
+ OptionSetter parser = new OptionSetter(new AllTypesOptionSource());
+ try {
+ parser.setOptionValue("my_option", "foo");
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Test setting a value for a non-parameterized Collection
+ */
+ public void testSetOptionValue_unparameterizedType() throws ConfigurationException {
+ OptionSetter parser = new OptionSetter(new AllTypesOptionSource());
+ try {
+ parser.setOptionValue("my_option", "foo");
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a String.
+ */
+ public void testSetOptionValue_string() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ final String expectedValue = "stringvalue";
+ assertSetOptionValue(optionSource, "string", expectedValue);
+ assertEquals(expectedValue, optionSource.mString);
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a Collection.
+ */
+ public void testSetOptionValue_collection() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ final String expectedValue = "stringvalue";
+ assertSetOptionValue(optionSource, "string_collection", expectedValue);
+ assertEquals(1, optionSource.mStringCollection.size());
+ assertTrue(optionSource.mStringCollection.contains(expectedValue));
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a Map.
+ */
+ public void testSetOptionValue_map() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ final String expectedKey = "stringkey";
+ final String expectedValue = "stringvalue";
+
+ // Actually set the key/value pair
+ OptionSetter parser = new OptionSetter(optionSource);
+ parser.setOptionMapValue("string_string_map", expectedKey, expectedValue);
+
+ assertEquals(1, optionSource.mStringMap.size());
+ assertNotNull(optionSource.mStringMap.get(expectedKey));
+ assertEquals(expectedValue, optionSource.mStringMap.get(expectedKey));
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a boolean.
+ */
+ public void testSetOptionValue_boolean() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValue(optionSource, "boolean", "true");
+ assertEquals(true, optionSource.mBool);
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a boolean for a non-boolean
+ * value.
+ */
+ public void testSetOptionValue_booleanInvalid() {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValueInvalid(optionSource, "boolean", "blah");
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a Boolean.
+ */
+ public void testSetOptionValue_booleanObj() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValue(optionSource, "booleanObj", "true");
+ assertTrue(optionSource.mBooleanObj);
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a byte.
+ */
+ public void testSetOptionValue_byte() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValue(optionSource, "byte", "2");
+ assertEquals(2, optionSource.mByte);
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a byte for an invalid value.
+ */
+ public void testSetOptionValue_byteInvalid() {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValueInvalid(optionSource, "byte", "blah");
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a Byte.
+ */
+ public void testSetOptionValue_byteObj() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValue(optionSource, "byteObj", "2");
+ assertTrue(2 == optionSource.mByteObj);
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a short.
+ */
+ public void testSetOptionValue_short() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValue(optionSource, "short", "2");
+ assertTrue(2 == optionSource.mShort);
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a Short.
+ */
+ public void testSetOptionValue_shortObj() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValue(optionSource, "shortObj", "2");
+ assertTrue(2 == optionSource.mShortObj);
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a short for an invalid value.
+ */
+ public void testSetOptionValue_shortInvalid() {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValueInvalid(optionSource, "short", "blah");
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a int.
+ */
+ public void testSetOptionValue_int() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValue(optionSource, "int", "2");
+ assertTrue(2 == optionSource.mInt);
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a Integer.
+ */
+ public void testSetOptionValue_intObj() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValue(optionSource, "intObj", "2");
+ assertTrue(2 == optionSource.mIntObj);
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a int for an invalid value.
+ */
+ public void testSetOptionValue_intInvalid() {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValueInvalid(optionSource, "int", "blah");
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a long.
+ */
+ public void testSetOptionValue_long() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValue(optionSource, "long", "2");
+ assertTrue(2 == optionSource.mLong);
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a Long.
+ */
+ public void testSetOptionValue_longObj() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValue(optionSource, "longObj", "2");
+ assertTrue(2 == optionSource.mLongObj);
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a long for an invalid value.
+ */
+ public void testSetOptionValue_longInvalid() {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValueInvalid(optionSource, "long", "blah");
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a float.
+ */
+ public void testSetOptionValue_float() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValue(optionSource, "float", "2.1");
+ assertEquals(2.1, optionSource.mFloat, 0.01);
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a Float.
+ */
+ public void testSetOptionValue_floatObj() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValue(optionSource, "floatObj", "2.1");
+ assertEquals(2.1, optionSource.mFloatObj, 0.01);
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a float for an invalid value.
+ */
+ public void testSetOptionValue_floatInvalid() {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValueInvalid(optionSource, "float", "blah");
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a float.
+ */
+ public void testSetOptionValue_double() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValue(optionSource, "double", "2.1");
+ assertEquals(2.1, optionSource.mDouble, 0.01);
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a Float.
+ */
+ public void testSetOptionValue_doubleObj() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValue(optionSource, "doubleObj", "2.1");
+ assertEquals(2.1, optionSource.mDoubleObj, 0.01);
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a double for an invalid value.
+ */
+ public void testSetOptionValue_doubleInvalid() {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValueInvalid(optionSource, "double", "blah");
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for a File.
+ */
+ public void testSetOptionValue_file() throws ConfigurationException, IOException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ File tmpFile = File.createTempFile("testSetOptionValue_file", "txt");
+ try {
+ assertSetOptionValue(optionSource, "file", tmpFile.getAbsolutePath());
+ assertEquals(tmpFile.getAbsolutePath(), optionSource.mFile.getAbsolutePath());
+ } finally {
+ tmpFile.delete();
+ }
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for an Enum.
+ */
+ public void testSetOptionValue_enum() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValue(optionSource, "enum", "VAL1");
+ assertEquals(DefaultEnumClass.VAL1, optionSource.mEnum);
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for an Enum. Specifically make sure
+ * that we fall back properly, so that a mixed-case value will be silently mapped to an
+ * uppercase version, since Enum constants tend to be uppercase by convention.
+ */
+ public void testSetOptionValue_enumMixedCase() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValue(optionSource, "enum", "Val1");
+ assertEquals(DefaultEnumClass.VAL1, optionSource.mEnum);
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for an Enum with custom values.
+ */
+ public void testSetOptionValue_customEnum() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ assertSetOptionValue(optionSource, "customEnum", "VAL1");
+ assertEquals(CustomEnumClass.VAL1, optionSource.mCustomEnum);
+ assertEquals(42, optionSource.mCustomEnum.getVal());
+ }
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for Enums used as the key and value
+ * of a {@link Map}.
+ */
+ public void testSetOptionValue_enumMap() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+
+ final String key = "VAL1";
+ final String value = "VAL1";
+ final DefaultEnumClass expectedKey = DefaultEnumClass.VAL1;
+ final CustomEnumClass expectedValue = CustomEnumClass.VAL1;
+
+ // Actually set the key/value pair
+ OptionSetter parser = new OptionSetter(optionSource);
+ parser.setOptionMapValue("enumMap", key, value);
+
+ assertEquals(1, optionSource.mEnumMap.size());
+ assertNotNull(optionSource.mEnumMap.get(expectedKey));
+ assertEquals(expectedValue, optionSource.mEnumMap.get(expectedKey));
+ }
+
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for Enums used as the key and value
+ * of a {@link Map}.
+ */
+ public void testSetOptionValue_enumCollection() throws ConfigurationException {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+
+ final String value = "VAL1";
+ final DefaultEnumClass expectedValue = DefaultEnumClass.VAL1;
+
+ assertSetOptionValue(optionSource, "enumCollection", value);
+
+ assertEquals(1, optionSource.mEnumCollection.size());
+ assertTrue(optionSource.mEnumCollection.contains(expectedValue));
+ }
+
+
+ /**
+ * Test {@link OptionSetter#setOptionValue(String, String)} for an Enum.
+ */
+ public void testSetOptionValue_enumBadValue() {
+ AllTypesOptionSource optionSource = new AllTypesOptionSource();
+ try {
+ assertSetOptionValue(optionSource, "enum", "noexist");
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Make sure that Enum documentation shows the defaults properly
+ */
+ public void testEnumDocs() throws Exception {
+ // We assume here that the fields are returned in declaration order, as documented in the
+ // {@link Enum} javadoc.
+ String expectedValues = " Valid values: [VAL1, VAL3, VAL2]";
+ Field field = AllTypesOptionSource.class.getDeclaredField("mEnum");
+ String actualValues = OptionSetter.getEnumFieldValuesAsString(field);
+ assertEquals(expectedValues, actualValues);
+ }
+
+ /**
+ * Test {@link OptionSetter} for a final field
+ */
+ public void testOptionSetter_finalField() {
+ FinalOption optionSource = new FinalOption();
+ try {
+ new OptionSetter(optionSource);
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Perform {@link OptionSetter#setOptionValue(String, String)} for a given option.
+ */
+ private void assertSetOptionValue(AllTypesOptionSource optionSource, final String optionName,
+ final String expectedValue) throws ConfigurationException {
+ OptionSetter parser = new OptionSetter(optionSource);
+ parser.setOptionValue(optionName, expectedValue);
+ }
+
+ /**
+ * Perform {@link OptionSetter#setOptionValue(String, String)} for a given option, with an
+ * invalid value for the option type.
+ */
+ private void assertSetOptionValueInvalid(AllTypesOptionSource optionSource,
+ final String optionName, final String expectedValue) {
+ try {
+ assertSetOptionValue(optionSource, optionName, expectedValue);
+ fail("ConfigurationException not thrown");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+}
diff --git a/javatests/com/android/loganalysis/util/config/OptionUpdateRuleTest.java b/javatests/com/android/loganalysis/util/config/OptionUpdateRuleTest.java
new file mode 100644
index 0000000..0e899ec
--- /dev/null
+++ b/javatests/com/android/loganalysis/util/config/OptionUpdateRuleTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2010 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.loganalysis.util.config;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link OptionUpdateRule}
+ */
+public class OptionUpdateRuleTest extends TestCase {
+ private static final String OPTION_NAME = "option-name";
+ private static final Object CURRENT = "5 current value";
+ private static final Object UPDATE = "5 update value";
+ private static final Object SMALL_UPDATE = "0 update value";
+ private static final Object BIG_UPDATE = "9 update value";
+
+ public void testFirst_simple() throws Exception {
+ assertEquals(UPDATE, OptionUpdateRule.FIRST.update(OPTION_NAME, null, UPDATE));
+ assertEquals(CURRENT, OptionUpdateRule.FIRST.update(OPTION_NAME, CURRENT, UPDATE));
+ }
+
+ public void testLast_simple() throws Exception {
+ assertEquals(UPDATE, OptionUpdateRule.LAST.update(OPTION_NAME, null, UPDATE));
+ assertEquals(UPDATE, OptionUpdateRule.LAST.update(OPTION_NAME, CURRENT, UPDATE));
+ }
+
+ public void testGreatest_simple() throws Exception {
+ assertEquals(
+ SMALL_UPDATE, OptionUpdateRule.GREATEST.update(OPTION_NAME, null, SMALL_UPDATE));
+ assertEquals(CURRENT, OptionUpdateRule.GREATEST.update(OPTION_NAME, CURRENT, SMALL_UPDATE));
+ assertEquals(
+ BIG_UPDATE, OptionUpdateRule.GREATEST.update(OPTION_NAME, CURRENT, BIG_UPDATE));
+ }
+
+ public void testLeast_simple() throws Exception {
+ assertEquals(BIG_UPDATE, OptionUpdateRule.LEAST.update(OPTION_NAME, null, BIG_UPDATE));
+ assertEquals(
+ SMALL_UPDATE, OptionUpdateRule.LEAST.update(OPTION_NAME, CURRENT, SMALL_UPDATE));
+ assertEquals(CURRENT, OptionUpdateRule.LEAST.update(OPTION_NAME, CURRENT, BIG_UPDATE));
+ }
+
+ public void testImmutable_simple() throws Exception {
+ assertEquals(UPDATE, OptionUpdateRule.IMMUTABLE.update(OPTION_NAME, null, UPDATE));
+ try {
+ OptionUpdateRule.IMMUTABLE.update(OPTION_NAME, CURRENT, UPDATE);
+ fail("ConfigurationException not thrown when updating an IMMUTABLE option");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ public void testInvalidComparison() throws Exception {
+ try {
+ // Strings aren't comparable with integers
+ OptionUpdateRule.GREATEST.update(OPTION_NAME, 13, UPDATE);
+ fail("ConfigurationException not thrown for invalid comparison.");
+ } catch (ConfigurationException e) {
+ // Expected. Moreover, the exception should be actionable, so make sure we mention the
+ // specific mismatching types.
+ final String msg = e.getMessage();
+ assertTrue(msg.contains("Integer"));
+ assertTrue(msg.contains("String"));
+ }
+ }
+
+ public void testNotComparable() throws Exception {
+ try {
+ OptionUpdateRule.LEAST.update(OPTION_NAME, new Exception("hi"), UPDATE);
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+}
+