blob: cf84b7ca492a4241ca35ff1d595dfd1f7f982fcb [file] [log] [blame]
/*
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.telecom.Logging.Session;
import android.telecom.Logging.SessionManager;
import android.test.suitebuilder.annotation.SmallTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.lang.ref.WeakReference;
/**
* Unit tests for android.telecom.Logging.SessionManager
*/
@RunWith(JUnit4.class)
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
@Before
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
@After
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
@Test
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
@Test
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_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
@Test
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
@Test
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
@Test
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
@Test
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
@Test
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
@Test
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
@Test
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
// +-50 ms
assertTrue(mfullSessionCompleteTime >= TEST_DELAY_TIME / 2);
assertTrue(mfullSessionCompleteTime <= TEST_DELAY_TIME * 1.5);
}
/**
* Tests that starting an external session packages up the parent session information and
* correctly generates the child session.
*/
@SmallTest
@Test
public void testStartExternalSession() {
mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID;
mTestSessionManager.startSession(TEST_PARENT_NAME, null);
Session.Info sessionInfo =
mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID).getInfo();
mTestSessionManager.mCurrentThreadId = () -> TEST_CHILD_THREAD_ID;
mTestSessionManager.startExternalSession(sessionInfo, TEST_CHILD_NAME);
Session externalSession = mTestSessionManager.mSessionMapper.get(TEST_CHILD_THREAD_ID);
assertNotNull(externalSession);
assertFalse(externalSession.isSessionCompleted());
assertEquals(TEST_CHILD_NAME, externalSession.getShortMethodName());
// First subsession of the parent external Session, so the session will be _0.
assertEquals("0", externalSession.getSessionId());
}
/**
* Verifies that ending an external session tears down the session correctly and removes the
* external session from mSessionMapper.
*/
@SmallTest
@Test
public void testEndExternalSession() {
mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID;
mTestSessionManager.startSession(TEST_PARENT_NAME, null);
Session.Info sessionInfo =
mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID).getInfo();
mTestSessionManager.mCurrentThreadId = () -> TEST_CHILD_THREAD_ID;
mTestSessionManager.startExternalSession(sessionInfo, TEST_CHILD_NAME);
Session externalSession = mTestSessionManager.mSessionMapper.get(TEST_CHILD_THREAD_ID);
try {
// Make sure execution time is > 0
Thread.sleep(1);
} catch (InterruptedException ignored) {}
mTestSessionManager.endSession();
assertTrue(externalSession.isSessionCompleted());
assertTrue(externalSession.getLocalExecutionTime() > 0);
assertNull(mTestSessionManager.mSessionMapper.get(TEST_CHILD_THREAD_ID));
}
/**
* Verifies that the callback to inform that the top level parent Session has completed is not
* the external Session, but the one subsession underneath.
*/
@SmallTest
@Test
public void testEndExternalSessionListenerCallback() {
mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID;
mTestSessionManager.startSession(TEST_PARENT_NAME, null);
Session.Info sessionInfo =
mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID).getInfo();
mTestSessionManager.mCurrentThreadId = () -> TEST_CHILD_THREAD_ID;
mTestSessionManager.startExternalSession(sessionInfo, TEST_CHILD_NAME);
try {
// Make sure execution time is recorded correctly
Thread.sleep(TEST_DELAY_TIME);
} catch (InterruptedException ignored) {}
mTestSessionManager.endSession();
assertEquals(TEST_CHILD_NAME, mFullSessionMethodName);
assertTrue(mfullSessionCompleteTime >= TEST_DELAY_TIME / 2);
assertTrue(mfullSessionCompleteTime <= TEST_DELAY_TIME * 1.5);
}
/**
* Verifies that the recursive method for getting the full ID works correctly.
*/
@SmallTest
@Test
public void testFullMethodPath() {
mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID;
mTestSessionManager.startSession(TEST_PARENT_NAME, null);
Session testSession = mTestSessionManager.createSubsession();
mTestSessionManager.mCurrentThreadId = () -> TEST_CHILD_THREAD_ID;
mTestSessionManager.continueSession(testSession, TEST_CHILD_NAME);
String fullId = mTestSessionManager.getSessionId();
assertTrue(fullId.contains(TEST_PARENT_NAME + Session.SUBSESSION_SEPARATION_CHAR
+ TEST_CHILD_NAME));
}
/**
* Make sure that the cleanup timer runs correctly and the GC collects the stale sessions
* correctly to ensure that there are no dangling sessions.
*/
@SmallTest
@Test
public void testStaleSessionCleanupTimer() {
mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID;
mTestSessionManager.startSession(TEST_PARENT_NAME, null);
WeakReference<Session> sessionRef = new WeakReference<>(
mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID));
try {
// Make sure that the sleep time is always > delay time.
Thread.sleep(2 * TEST_DELAY_TIME);
mTestSessionManager.cleanupStaleSessions(TEST_DELAY_TIME);
Runtime.getRuntime().gc();
// Give it a second for GC to run.
Thread.sleep(1000);
} catch (InterruptedException ignored) {}
assertTrue(mTestSessionManager.mSessionMapper.isEmpty());
assertNull(sessionRef.get());
}
}