Adds tests for SessionManager and EventManager am: 0d2c1e61fc am: df69bcad27
am: a22965ab71

Change-Id: I462f07fc515423a8f8c7cfeb9a4e1525ef09469b
diff --git a/src/com/android/server/telecom/Log.java b/src/com/android/server/telecom/Log.java
index 2282ff0..011d038 100644
--- a/src/com/android/server/telecom/Log.java
+++ b/src/com/android/server/telecom/Log.java
@@ -593,7 +593,7 @@
     /**
      * Cancels a subsession that had Log.createSubsession() called on it, but will never have
      * Log.continueSession(...) called on it due to an error. Allows the subsession to be cleaned
-     * gracefully instead of being removed by the sSessionCleanupHandler forcefully later.
+     * gracefully instead of being removed by the mSessionCleanupHandler forcefully later.
      */
     public static synchronized void cancelSubsession(Session subsession) {
         if (subsession == null) {
diff --git a/tests/src/com/android/server/telecom/tests/EventManagerTest.java b/tests/src/com/android/server/telecom/tests/EventManagerTest.java
new file mode 100644
index 0000000..bb07306
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/EventManagerTest.java
@@ -0,0 +1,202 @@
+/*
+ * 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.server.telecom.tests;
+
+import android.telecom.Logging.EventManager;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.stream.Collectors;
+
+/**
+ * Unit tests for android.telecom.Logging.EventManager.
+ */
+
+public class EventManagerTest extends TelecomTestCase {
+
+    private EventManager mTestEventManager;
+    // A reference to the recently added event record, populated from the eventRecordAdded callback
+    private EventManager.EventRecord mAddedEventRecord;
+
+    private static final String TEST_EVENT = "testEvent";
+    private static final String TEST_START_EVENT = "testStartEvent";
+    private static final String TEST_END_EVENT = "testEndEvent";
+    private static final String TEST_TIMED_EVENT = "TimedEvent";
+    private static final int TEST_DELAY_TIME = 100; // ms
+
+    private class TestRecord implements EventManager.Loggable {
+        private String mId;
+        private String mDescription;
+
+        TestRecord(String id, String description) {
+            mId = id;
+            mDescription = description;
+        }
+
+        @Override
+        public String getId() {
+            return mId;
+        }
+
+        @Override
+        public String getDescription() {
+            return mDescription;
+        }
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mTestEventManager = new EventManager(() -> "");
+        mTestEventManager.registerEventListener((e) -> mAddedEventRecord = e);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mTestEventManager = null;
+        mAddedEventRecord = null;
+        super.tearDown();
+    }
+
+    /**
+     * Tests EventManager#addEventRecord to make sure that new events are added properly and that
+     * the eventRecordAdded callback is working.
+     */
+    @SmallTest
+    public void testAddEventRecord() throws Exception {
+        TestRecord testRecord = new TestRecord("testId", "testDescription");
+        mTestEventManager.event(testRecord, TEST_EVENT, null);
+
+        assertNotNull(mAddedEventRecord);
+        assertEquals(testRecord, mAddedEventRecord.getRecordEntry());
+        assertTrue(mTestEventManager.getEventRecords().contains(mAddedEventRecord));
+        assertTrue(mTestEventManager.getCallEventRecordMap().containsKey(
+                mAddedEventRecord.getRecordEntry()));
+    }
+
+    /**
+     * Tests EventManager#addEventRecord for the case when we overflow the cached record entries and
+     * the oldest entry is dropped.
+     */
+    @SmallTest
+    public void testAddEventRecordOverflowMaxEvents() throws Exception {
+        TestRecord oldestRecordEntry = new TestRecord("id0", "desc0");
+        // Add the oldest record separately so that we can verify it is dropped later
+        mTestEventManager.event(oldestRecordEntry, TEST_EVENT, null);
+        // Record the EventRecord created by the oldest event
+        assertNotNull(mAddedEventRecord);
+        EventManager.EventRecord oldestRecord = mAddedEventRecord;
+        for (int i = 1; i < EventManager.DEFAULT_EVENTS_TO_CACHE; i++) {
+            mTestEventManager.event(new TestRecord("id" + i, "desc" + i), TEST_EVENT, null);
+        }
+
+        // Add a new event that overflows the cache
+        TestRecord overflowRecord = new TestRecord("newestId", "newestDesc");
+        // Add the oldest record separately so that we can verify it is dropped later
+        mTestEventManager.event(overflowRecord, TEST_EVENT, null);
+
+        assertFalse(mTestEventManager.getEventRecords().contains(oldestRecord));
+        assertTrue(mTestEventManager.getEventRecords().contains(mAddedEventRecord));
+    }
+
+    /**
+     * Tests the restructuring of the record entry queue when it is changed (usually in debugging).
+     * If the queue is resized to be smaller, the oldest records are dropped.
+     */
+    @SmallTest
+    public void testChangeQueueSize() throws Exception {
+        TestRecord oldestRecordEntry = new TestRecord("id0", "desc0");
+        // Add the oldest record separately so that we can verify it is dropped later
+        mTestEventManager.event(oldestRecordEntry, TEST_EVENT, null);
+        // Record the EventRecord created by the oldest event
+        assertNotNull(mAddedEventRecord);
+        EventManager.EventRecord oldestRecord = mAddedEventRecord;
+        for (int i = 1; i < EventManager.DEFAULT_EVENTS_TO_CACHE; i++) {
+            mTestEventManager.event(new TestRecord("id" + i, "desc" + i), TEST_EVENT, null);
+        }
+
+        mTestEventManager.changeEventCacheSize(EventManager.DEFAULT_EVENTS_TO_CACHE - 1);
+
+        assertFalse(mTestEventManager.getEventRecords().contains(oldestRecord));
+        // Check to make sure the other event records are there (id1-9)
+        LinkedBlockingQueue<EventManager.EventRecord> eventRecords =
+                mTestEventManager.getEventRecords();
+        for (int i = 1; i < EventManager.DEFAULT_EVENTS_TO_CACHE; i++) {
+            final int index = i;
+            List<EventManager.EventRecord> filteredEvent = eventRecords.stream()
+                    .filter(e -> e.getRecordEntry().getId().equals("id" + index))
+                    .collect(Collectors.toList());
+            assertEquals(1, filteredEvent.size());
+            assertEquals("desc" + index, filteredEvent.get(0).getRecordEntry().getDescription());
+        }
+    }
+
+    /**
+     * Tests adding TimedEventPairs and generating the paired events as well as verifies that the
+     * timing response is correct.
+     */
+    @SmallTest
+    public void testExtractEventTimings() throws Exception {
+        TestRecord testRecord = new TestRecord("testId", "testDesc");
+        // Add unassociated event
+        mTestEventManager.event(testRecord, TEST_EVENT, null);
+        mTestEventManager.addRequestResponsePair(new EventManager.TimedEventPair(TEST_START_EVENT,
+                TEST_END_EVENT, TEST_TIMED_EVENT));
+
+        // Add Start/End Event
+        mTestEventManager.event(testRecord, TEST_START_EVENT, null);
+        try {
+            Thread.sleep(TEST_DELAY_TIME);
+        } catch (InterruptedException ignored) { }
+        mTestEventManager.event(testRecord, TEST_END_EVENT, null);
+
+        // Verify that the events were captured and that the timing is correct.
+        List<EventManager.EventRecord.EventTiming> timings =
+                mAddedEventRecord.extractEventTimings();
+        assertEquals(1, timings.size());
+        assertEquals(TEST_TIMED_EVENT, timings.get(0).name);
+        // Verify that the timing is correct with a +-10 ms buffer
+        assertTrue(timings.get(0).time >= TEST_DELAY_TIME - 10);
+        assertTrue(timings.get(0).time <= TEST_DELAY_TIME + 10);
+    }
+
+    /**
+     * Verify that adding events to different records does not create a valid TimedEventPair
+     */
+    @SmallTest
+    public void testExtractEventTimingsDifferentRecords() throws Exception {
+        TestRecord testRecord = new TestRecord("testId", "testDesc");
+        TestRecord testRecord2 = new TestRecord("testId2", "testDesc2");
+        mTestEventManager.addRequestResponsePair(new EventManager.TimedEventPair(TEST_START_EVENT,
+                TEST_END_EVENT, TEST_TIMED_EVENT));
+
+        // Add Start event for two separate records
+        mTestEventManager.event(testRecord, TEST_START_EVENT, null);
+        EventManager.EventRecord eventRecord1 = mAddedEventRecord;
+        mTestEventManager.event(testRecord2, TEST_END_EVENT, null);
+        EventManager.EventRecord eventRecord2 = mAddedEventRecord;
+
+        // Verify that the events were captured and that the timing is correct.
+        List<EventManager.EventRecord.EventTiming> timings1 =
+                eventRecord1.extractEventTimings();
+        List<EventManager.EventRecord.EventTiming> timings2 =
+                eventRecord2.extractEventTimings();
+        assertEquals(0, timings1.size());
+        assertEquals(0, timings2.size());
+    }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/server/telecom/tests/LogTest.java b/tests/src/com/android/server/telecom/tests/LogTest.java
index f61c3b7..0665338 100644
--- a/tests/src/com/android/server/telecom/tests/LogTest.java
+++ b/tests/src/com/android/server/telecom/tests/LogTest.java
@@ -441,7 +441,7 @@
         verifyContinueEventResult(sessionName, "", "", 0, 0);
         verifyMethodCall("", sessionName, 0, "", TEST_ENTER_METHOD4, 0);
 
-        // Verify the session is still active in sSessionMapper
+        // Verify the session is still active in mSessionMapper
         assertEquals(Log.sSessionMapper.size(), 1);
         assertEquals(true, mTestSystemLogger.isMessagesEmpty());
 
diff --git a/tests/src/com/android/server/telecom/tests/SessionManagerTest.java b/tests/src/com/android/server/telecom/tests/SessionManagerTest.java
new file mode 100644
index 0000000..8e96068
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/SessionManagerTest.java
@@ -0,0 +1,272 @@
+/*
+ * 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.server.telecom.tests;
+
+import android.telecom.Logging.Session;
+import android.telecom.Logging.SessionManager;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Unit tests for android.telecom.Logging.SessionManager
+ */
+
+public class SessionManagerTest extends TelecomTestCase {
+
+    private static final String TEST_PARENT_NAME = "testParent";
+    private static final int TEST_PARENT_THREAD_ID = 0;
+    private static final String TEST_CHILD_NAME = "testChild";
+    private static final int TEST_CHILD_THREAD_ID = 1;
+    private static final int TEST_DELAY_TIME = 100; //ms
+
+    private SessionManager mTestSessionManager;
+    // Used to verify sessionComplete callback
+    private long mfullSessionCompleteTime = Session.UNDEFINED;
+    private String mFullSessionMethodName = "";
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mTestSessionManager = new SessionManager();
+        mTestSessionManager.registerSessionListener(((sessionName, timeMs) -> {
+            mfullSessionCompleteTime = timeMs;
+            mFullSessionMethodName = sessionName;
+        }));
+        // Remove automatic stale session cleanup for testing
+        mTestSessionManager.mCleanStaleSessions = null;
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mFullSessionMethodName = "";
+        mfullSessionCompleteTime = Session.UNDEFINED;
+        mTestSessionManager = null;
+        super.tearDown();
+    }
+
+    /**
+     * Starts a Session on the current thread and verifies that it exists in the HashMap
+     */
+    @SmallTest
+    public void testStartSession() {
+        assertTrue(mTestSessionManager.mSessionMapper.isEmpty());
+
+        // Set the thread Id to 0
+        mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID;
+        mTestSessionManager.startSession(TEST_PARENT_NAME, null);
+
+        Session testSession = mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID);
+        assertEquals(TEST_PARENT_NAME, testSession.getShortMethodName());
+        assertFalse(testSession.isSessionCompleted());
+        assertFalse(testSession.isStartedFromActiveSession());
+    }
+
+    /**
+     * Starts two sessions in the same thread. The first session will be parented to the second
+     * session and the second session will be attached to that thread ID.
+     */
+    @SmallTest
+    public void testStartInvisibleChildSession() {
+        assertTrue(mTestSessionManager.mSessionMapper.isEmpty());
+
+        // Set the thread Id to 0 for the parent
+        mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID;
+        mTestSessionManager.startSession(TEST_PARENT_NAME, null);
+        // Create invisible child session - same Thread ID as parent
+        mTestSessionManager.startSession(TEST_CHILD_NAME, null);
+
+        // There should only be one session in the mapper (the child)
+        assertEquals(1, mTestSessionManager.mSessionMapper.size());
+        Session testChildSession = mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID);
+        assertEquals(TEST_PARENT_NAME + "->" + TEST_CHILD_NAME,
+                testChildSession.getShortMethodName());
+        assertTrue(testChildSession.isStartedFromActiveSession());
+        assertNotNull(testChildSession.getParentSession());
+        assertEquals(TEST_PARENT_NAME, testChildSession.getParentSession().getShortMethodName());
+        assertFalse(testChildSession.isSessionCompleted());
+        assertFalse(testChildSession.getParentSession().isSessionCompleted());
+    }
+
+    /**
+     * End the active Session and verify that it is completed and removed from mSessionMapper.
+     */
+    @SmallTest
+    public void testEndSession() {
+        assertTrue(mTestSessionManager.mSessionMapper.isEmpty());
+        // Set the thread Id to 0
+        mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID;
+        mTestSessionManager.startSession(TEST_PARENT_NAME, null);
+        Session testSession = mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID);
+
+        assertEquals(1, mTestSessionManager.mSessionMapper.size());
+        try {
+            // Make sure execution time is > 0
+            Thread.sleep(1);
+        } catch (InterruptedException ignored) {}
+        mTestSessionManager.endSession();
+
+        assertTrue(testSession.isSessionCompleted());
+        assertTrue(testSession.getLocalExecutionTime() > 0);
+        assertTrue(mTestSessionManager.mSessionMapper.isEmpty());
+    }
+
+    /**
+     * Ends an active invisible child session and verifies that the parent session is moved back
+     * into mSessionMapper.
+     */
+    @SmallTest
+    public void testEndInvisibleChildSession() {
+        assertTrue(mTestSessionManager.mSessionMapper.isEmpty());
+        // Set the thread Id to 0 for the parent
+        mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID;
+        mTestSessionManager.startSession(TEST_PARENT_NAME, null);
+        // Create invisible child session - same Thread ID as parent
+        mTestSessionManager.startSession(TEST_CHILD_NAME, null);
+        Session testChildSession = mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID);
+
+        mTestSessionManager.endSession();
+
+        // There should only be one session in the mapper (the parent)
+        assertEquals(1, mTestSessionManager.mSessionMapper.size());
+        Session testParentSession = mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID);
+        assertEquals(TEST_PARENT_NAME, testParentSession.getShortMethodName());
+        assertFalse(testParentSession.isStartedFromActiveSession());
+        assertTrue(testChildSession.isSessionCompleted());
+        assertFalse(testParentSession.isSessionCompleted());
+    }
+
+    /**
+     * Creates a subsession (child Session) of the current session and prepares it to be continued
+     * in a different thread.
+     */
+    @SmallTest
+    public void testCreateSubsession() {
+        mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID;
+        mTestSessionManager.startSession(TEST_PARENT_NAME, null);
+
+        Session testSession = mTestSessionManager.createSubsession();
+
+        assertEquals(1, mTestSessionManager.mSessionMapper.size());
+        Session parentSession = mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID);
+        assertNotNull(testSession.getParentSession());
+        assertEquals(TEST_PARENT_NAME, testSession.getParentSession().getShortMethodName());
+        assertEquals(TEST_PARENT_NAME, parentSession.getShortMethodName());
+        assertTrue(parentSession.getChildSessions().contains(testSession));
+        assertFalse(testSession.isSessionCompleted());
+        assertFalse(testSession.isStartedFromActiveSession());
+        assertTrue(testSession.getChildSessions().isEmpty());
+    }
+
+    /**
+     * Cancels a subsession that was started before it was continued and verifies that it is
+     * marked as completed and never added to mSessionMapper.
+     */
+    @SmallTest
+    public void testCancelSubsession() {
+        mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID;
+        mTestSessionManager.startSession(TEST_PARENT_NAME, null);
+        Session parentSession = mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID);
+        Session testSession = mTestSessionManager.createSubsession();
+
+        mTestSessionManager.cancelSubsession(testSession);
+
+        assertTrue(testSession.isSessionCompleted());
+        assertFalse(parentSession.isSessionCompleted());
+        assertEquals(Session.UNDEFINED, testSession.getLocalExecutionTime());
+        assertNull(testSession.getParentSession());
+    }
+
+
+    /**
+     * Continues a subsession in a different thread and verifies that both the new subsession and
+     * its parent are in mSessionMapper.
+     */
+    @SmallTest
+    public void testContinueSubsession() {
+        mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID;
+        mTestSessionManager.startSession(TEST_PARENT_NAME, null);
+        Session parentSession = mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID);
+        Session testSession = mTestSessionManager.createSubsession();
+
+        mTestSessionManager.mCurrentThreadId = () -> TEST_CHILD_THREAD_ID;
+        mTestSessionManager.continueSession(testSession, TEST_CHILD_NAME);
+
+        assertEquals(2, mTestSessionManager.mSessionMapper.size());
+        assertEquals(testSession, mTestSessionManager.mSessionMapper.get(TEST_CHILD_THREAD_ID));
+        assertEquals(parentSession, testSession.getParentSession());
+        assertFalse(parentSession.isStartedFromActiveSession());
+        assertFalse(parentSession.isSessionCompleted());
+        assertFalse(testSession.isSessionCompleted());
+        assertFalse(testSession.isStartedFromActiveSession());
+    }
+
+    /**
+     * Ends a subsession that exists in a different thread and verifies that it is completed and
+     * no longer exists in mSessionMapper.
+     */
+    @SmallTest
+    public void testEndSubsession() {
+        mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID;
+        mTestSessionManager.startSession(TEST_PARENT_NAME, null);
+        Session parentSession = mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID);
+        Session testSession = mTestSessionManager.createSubsession();
+        mTestSessionManager.mCurrentThreadId = () -> TEST_CHILD_THREAD_ID;
+        mTestSessionManager.continueSession(testSession, TEST_CHILD_NAME);
+
+        mTestSessionManager.endSession();
+
+        assertTrue(testSession.isSessionCompleted());
+        assertNull(mTestSessionManager.mSessionMapper.get(TEST_CHILD_THREAD_ID));
+        assertFalse(parentSession.isSessionCompleted());
+        assertEquals(parentSession, mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID));
+    }
+
+    /**
+     * When there are subsessions in multiple threads, the parent session may end before the
+     * subsessions themselves. When the subsession ends, we need to recursively clean up the parent
+     * sessions that are complete as well and note the completion time of the entire chain.
+     */
+    @SmallTest
+    public void testEndSubsessionWithParentComplete() {
+        mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID;
+        mTestSessionManager.startSession(TEST_PARENT_NAME, null);
+        Session parentSession = mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID);
+        Session childSession = mTestSessionManager.createSubsession();
+        mTestSessionManager.mCurrentThreadId = () -> TEST_CHILD_THREAD_ID;
+        mTestSessionManager.continueSession(childSession, TEST_CHILD_NAME);
+        // Switch to the parent session ID and end the session.
+        mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID;
+        mTestSessionManager.endSession();
+        assertTrue(parentSession.isSessionCompleted());
+        assertFalse(childSession.isSessionCompleted());
+
+        mTestSessionManager.mCurrentThreadId = () -> TEST_CHILD_THREAD_ID;
+        try {
+            Thread.sleep(TEST_DELAY_TIME);
+        } catch (InterruptedException ignored) {}
+        mTestSessionManager.endSession();
+
+        assertEquals(0, mTestSessionManager.mSessionMapper.size());
+        assertTrue(parentSession.getChildSessions().isEmpty());
+        assertNull(childSession.getParentSession());
+        assertTrue(childSession.isSessionCompleted());
+        assertEquals(TEST_PARENT_NAME, mFullSessionMethodName);
+        // Reduce flakiness by assuming that the true completion time is within a threshold of
+        // +-10 ms
+        assertTrue(mfullSessionCompleteTime >= TEST_DELAY_TIME - 10);
+        assertTrue(mfullSessionCompleteTime <= TEST_DELAY_TIME + 10);
+    }
+}