diff options
Diffstat (limited to 'javatests/com')
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 + } + } +} + |