1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.server.telecom.tests; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertNotNull; 22 import static org.junit.Assert.assertNull; 23 import static org.junit.Assert.assertTrue; 24 25 import android.telecom.Logging.Session; 26 import android.telecom.Logging.SessionManager; 27 import android.test.suitebuilder.annotation.SmallTest; 28 29 import org.junit.After; 30 import org.junit.Before; 31 import org.junit.Test; 32 import org.junit.runner.RunWith; 33 import org.junit.runners.JUnit4; 34 35 import java.lang.ref.WeakReference; 36 37 /** 38 * Unit tests for android.telecom.Logging.SessionManager 39 */ 40 41 @RunWith(JUnit4.class) 42 public class SessionManagerTest extends TelecomTestCase { 43 44 private static final String TEST_PARENT_NAME = "testParent"; 45 private static final int TEST_PARENT_THREAD_ID = 0; 46 private static final String TEST_CHILD_NAME = "testChild"; 47 private static final int TEST_CHILD_THREAD_ID = 1; 48 private static final int TEST_DELAY_TIME = 100; // ms 49 50 private SessionManager mTestSessionManager; 51 // Used to verify sessionComplete callback 52 private long mfullSessionCompleteTime = Session.UNDEFINED; 53 private String mFullSessionMethodName = ""; 54 55 @Override 56 @Before setUp()57 public void setUp() throws Exception { 58 super.setUp(); 59 mTestSessionManager = new SessionManager(); 60 mTestSessionManager.registerSessionListener(((sessionName, timeMs) -> { 61 mfullSessionCompleteTime = timeMs; 62 mFullSessionMethodName = sessionName; 63 })); 64 // Remove automatic stale session cleanup for testing 65 mTestSessionManager.mCleanStaleSessions = null; 66 } 67 68 @Override 69 @After tearDown()70 public void tearDown() throws Exception { 71 mFullSessionMethodName = ""; 72 mfullSessionCompleteTime = Session.UNDEFINED; 73 mTestSessionManager = null; 74 super.tearDown(); 75 } 76 77 /** 78 * Starts a Session on the current thread and verifies that it exists in the HashMap 79 */ 80 @SmallTest 81 @Test testStartSession()82 public void testStartSession() { 83 assertTrue(mTestSessionManager.mSessionMapper.isEmpty()); 84 85 // Set the thread Id to 0 86 mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID; 87 mTestSessionManager.startSession(TEST_PARENT_NAME, null); 88 89 Session testSession = mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID); 90 assertEquals(TEST_PARENT_NAME, testSession.getShortMethodName()); 91 assertFalse(testSession.isSessionCompleted()); 92 assertFalse(testSession.isStartedFromActiveSession()); 93 } 94 95 /** 96 * Starts two sessions in the same thread. The first session will be parented to the second 97 * session and the second session will be attached to that thread ID. 98 */ 99 @SmallTest 100 @Test testStartInvisibleChildSession()101 public void testStartInvisibleChildSession() { 102 assertTrue(mTestSessionManager.mSessionMapper.isEmpty()); 103 104 // Set the thread Id to 0 for the parent 105 mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID; 106 mTestSessionManager.startSession(TEST_PARENT_NAME, null); 107 // Create invisible child session - same Thread ID as parent 108 mTestSessionManager.startSession(TEST_CHILD_NAME, null); 109 110 // There should only be one session in the mapper (the child) 111 assertEquals(1, mTestSessionManager.mSessionMapper.size()); 112 Session testChildSession = mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID); 113 assertEquals( TEST_CHILD_NAME, testChildSession.getShortMethodName()); 114 assertTrue(testChildSession.isStartedFromActiveSession()); 115 assertNotNull(testChildSession.getParentSession()); 116 assertEquals(TEST_PARENT_NAME, testChildSession.getParentSession().getShortMethodName()); 117 assertFalse(testChildSession.isSessionCompleted()); 118 assertFalse(testChildSession.getParentSession().isSessionCompleted()); 119 } 120 121 /** 122 * End the active Session and verify that it is completed and removed from mSessionMapper. 123 */ 124 @SmallTest 125 @Test testEndSession()126 public void testEndSession() { 127 assertTrue(mTestSessionManager.mSessionMapper.isEmpty()); 128 // Set the thread Id to 0 129 mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID; 130 mTestSessionManager.startSession(TEST_PARENT_NAME, null); 131 Session testSession = mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID); 132 133 assertEquals(1, mTestSessionManager.mSessionMapper.size()); 134 try { 135 // Make sure execution time is > 0 136 Thread.sleep(1); 137 } catch (InterruptedException ignored) {} 138 mTestSessionManager.endSession(); 139 140 assertTrue(testSession.isSessionCompleted()); 141 assertTrue(testSession.getLocalExecutionTime() > 0); 142 assertTrue(mTestSessionManager.mSessionMapper.isEmpty()); 143 } 144 145 /** 146 * Ends an active invisible child session and verifies that the parent session is moved back 147 * into mSessionMapper. 148 */ 149 @SmallTest 150 @Test testEndInvisibleChildSession()151 public void testEndInvisibleChildSession() { 152 assertTrue(mTestSessionManager.mSessionMapper.isEmpty()); 153 // Set the thread Id to 0 for the parent 154 mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID; 155 mTestSessionManager.startSession(TEST_PARENT_NAME, null); 156 // Create invisible child session - same Thread ID as parent 157 mTestSessionManager.startSession(TEST_CHILD_NAME, null); 158 Session testChildSession = mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID); 159 160 mTestSessionManager.endSession(); 161 162 // There should only be one session in the mapper (the parent) 163 assertEquals(1, mTestSessionManager.mSessionMapper.size()); 164 Session testParentSession = mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID); 165 assertEquals(TEST_PARENT_NAME, testParentSession.getShortMethodName()); 166 assertFalse(testParentSession.isStartedFromActiveSession()); 167 assertTrue(testChildSession.isSessionCompleted()); 168 assertFalse(testParentSession.isSessionCompleted()); 169 } 170 171 /** 172 * Creates a subsession (child Session) of the current session and prepares it to be continued 173 * in a different thread. 174 */ 175 @SmallTest 176 @Test testCreateSubsession()177 public void testCreateSubsession() { 178 mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID; 179 mTestSessionManager.startSession(TEST_PARENT_NAME, null); 180 181 Session testSession = mTestSessionManager.createSubsession(); 182 183 assertEquals(1, mTestSessionManager.mSessionMapper.size()); 184 Session parentSession = mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID); 185 assertNotNull(testSession.getParentSession()); 186 assertEquals(TEST_PARENT_NAME, testSession.getParentSession().getShortMethodName()); 187 assertEquals(TEST_PARENT_NAME, parentSession.getShortMethodName()); 188 assertTrue(parentSession.getChildSessions().contains(testSession)); 189 assertFalse(testSession.isSessionCompleted()); 190 assertFalse(testSession.isStartedFromActiveSession()); 191 assertTrue(testSession.getChildSessions().isEmpty()); 192 } 193 194 /** 195 * Cancels a subsession that was started before it was continued and verifies that it is 196 * marked as completed and never added to mSessionMapper. 197 */ 198 @SmallTest 199 @Test testCancelSubsession()200 public void testCancelSubsession() { 201 mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID; 202 mTestSessionManager.startSession(TEST_PARENT_NAME, null); 203 Session parentSession = mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID); 204 Session testSession = mTestSessionManager.createSubsession(); 205 206 mTestSessionManager.cancelSubsession(testSession); 207 208 assertTrue(testSession.isSessionCompleted()); 209 assertFalse(parentSession.isSessionCompleted()); 210 assertEquals(Session.UNDEFINED, testSession.getLocalExecutionTime()); 211 assertNull(testSession.getParentSession()); 212 } 213 214 215 /** 216 * Continues a subsession in a different thread and verifies that both the new subsession and 217 * its parent are in mSessionMapper. 218 */ 219 @SmallTest 220 @Test testContinueSubsession()221 public void testContinueSubsession() { 222 mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID; 223 mTestSessionManager.startSession(TEST_PARENT_NAME, null); 224 Session parentSession = mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID); 225 Session testSession = mTestSessionManager.createSubsession(); 226 227 mTestSessionManager.mCurrentThreadId = () -> TEST_CHILD_THREAD_ID; 228 mTestSessionManager.continueSession(testSession, TEST_CHILD_NAME); 229 230 assertEquals(2, mTestSessionManager.mSessionMapper.size()); 231 assertEquals(testSession, mTestSessionManager.mSessionMapper.get(TEST_CHILD_THREAD_ID)); 232 assertEquals(parentSession, testSession.getParentSession()); 233 assertFalse(parentSession.isStartedFromActiveSession()); 234 assertFalse(parentSession.isSessionCompleted()); 235 assertFalse(testSession.isSessionCompleted()); 236 assertFalse(testSession.isStartedFromActiveSession()); 237 } 238 239 /** 240 * Ends a subsession that exists in a different thread and verifies that it is completed and 241 * no longer exists in mSessionMapper. 242 */ 243 @SmallTest 244 @Test testEndSubsession()245 public void testEndSubsession() { 246 mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID; 247 mTestSessionManager.startSession(TEST_PARENT_NAME, null); 248 Session parentSession = mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID); 249 Session testSession = mTestSessionManager.createSubsession(); 250 mTestSessionManager.mCurrentThreadId = () -> TEST_CHILD_THREAD_ID; 251 mTestSessionManager.continueSession(testSession, TEST_CHILD_NAME); 252 253 mTestSessionManager.endSession(); 254 255 assertTrue(testSession.isSessionCompleted()); 256 assertNull(mTestSessionManager.mSessionMapper.get(TEST_CHILD_THREAD_ID)); 257 assertFalse(parentSession.isSessionCompleted()); 258 assertEquals(parentSession, mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID)); 259 } 260 261 /** 262 * When there are subsessions in multiple threads, the parent session may end before the 263 * subsessions themselves. When the subsession ends, we need to recursively clean up the parent 264 * sessions that are complete as well and note the completion time of the entire chain. 265 */ 266 @SmallTest 267 @Test testEndSubsessionWithParentComplete()268 public void testEndSubsessionWithParentComplete() { 269 mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID; 270 mTestSessionManager.startSession(TEST_PARENT_NAME, null); 271 Session parentSession = mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID); 272 Session childSession = mTestSessionManager.createSubsession(); 273 mTestSessionManager.mCurrentThreadId = () -> TEST_CHILD_THREAD_ID; 274 mTestSessionManager.continueSession(childSession, TEST_CHILD_NAME); 275 // Switch to the parent session ID and end the session. 276 mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID; 277 mTestSessionManager.endSession(); 278 assertTrue(parentSession.isSessionCompleted()); 279 assertFalse(childSession.isSessionCompleted()); 280 281 mTestSessionManager.mCurrentThreadId = () -> TEST_CHILD_THREAD_ID; 282 try { 283 Thread.sleep(TEST_DELAY_TIME); 284 } catch (InterruptedException ignored) {} 285 mTestSessionManager.endSession(); 286 287 assertEquals(0, mTestSessionManager.mSessionMapper.size()); 288 assertTrue(parentSession.getChildSessions().isEmpty()); 289 assertNull(childSession.getParentSession()); 290 assertTrue(childSession.isSessionCompleted()); 291 assertEquals(TEST_PARENT_NAME, mFullSessionMethodName); 292 // Reduce flakiness by assuming that the true completion time is within a threshold of 293 // +-50 ms 294 assertTrue(mfullSessionCompleteTime >= TEST_DELAY_TIME / 2); 295 assertTrue(mfullSessionCompleteTime <= TEST_DELAY_TIME * 1.5); 296 } 297 298 /** 299 * Tests that starting an external session packages up the parent session information and 300 * correctly generates the child session. 301 */ 302 @SmallTest 303 @Test testStartExternalSession()304 public void testStartExternalSession() { 305 mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID; 306 mTestSessionManager.startSession(TEST_PARENT_NAME, null); 307 Session.Info sessionInfo = 308 mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID).getInfo(); 309 mTestSessionManager.mCurrentThreadId = () -> TEST_CHILD_THREAD_ID; 310 311 mTestSessionManager.startExternalSession(sessionInfo, TEST_CHILD_NAME); 312 313 Session externalSession = mTestSessionManager.mSessionMapper.get(TEST_CHILD_THREAD_ID); 314 assertNotNull(externalSession); 315 assertFalse(externalSession.isSessionCompleted()); 316 assertEquals(TEST_CHILD_NAME, externalSession.getShortMethodName()); 317 // First subsession of the parent external Session, so the session will be _0. 318 assertEquals("0", externalSession.getSessionId()); 319 } 320 321 /** 322 * Verifies that ending an external session tears down the session correctly and removes the 323 * external session from mSessionMapper. 324 */ 325 @SmallTest 326 @Test testEndExternalSession()327 public void testEndExternalSession() { 328 mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID; 329 mTestSessionManager.startSession(TEST_PARENT_NAME, null); 330 Session.Info sessionInfo = 331 mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID).getInfo(); 332 mTestSessionManager.mCurrentThreadId = () -> TEST_CHILD_THREAD_ID; 333 mTestSessionManager.startExternalSession(sessionInfo, TEST_CHILD_NAME); 334 Session externalSession = mTestSessionManager.mSessionMapper.get(TEST_CHILD_THREAD_ID); 335 336 try { 337 // Make sure execution time is > 0 338 Thread.sleep(1); 339 } catch (InterruptedException ignored) {} 340 mTestSessionManager.endSession(); 341 342 assertTrue(externalSession.isSessionCompleted()); 343 assertTrue(externalSession.getLocalExecutionTime() > 0); 344 assertNull(mTestSessionManager.mSessionMapper.get(TEST_CHILD_THREAD_ID)); 345 } 346 347 /** 348 * Verifies that the callback to inform that the top level parent Session has completed is not 349 * the external Session, but the one subsession underneath. 350 */ 351 @SmallTest 352 @Test testEndExternalSessionListenerCallback()353 public void testEndExternalSessionListenerCallback() { 354 mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID; 355 mTestSessionManager.startSession(TEST_PARENT_NAME, null); 356 Session.Info sessionInfo = 357 mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID).getInfo(); 358 mTestSessionManager.mCurrentThreadId = () -> TEST_CHILD_THREAD_ID; 359 mTestSessionManager.startExternalSession(sessionInfo, TEST_CHILD_NAME); 360 361 try { 362 // Make sure execution time is recorded correctly 363 Thread.sleep(TEST_DELAY_TIME); 364 } catch (InterruptedException ignored) {} 365 mTestSessionManager.endSession(); 366 367 assertEquals(TEST_CHILD_NAME, mFullSessionMethodName); 368 assertTrue(mfullSessionCompleteTime >= TEST_DELAY_TIME / 2); 369 assertTrue(mfullSessionCompleteTime <= TEST_DELAY_TIME * 1.5); 370 } 371 372 /** 373 * Verifies that the recursive method for getting the full ID works correctly. 374 */ 375 @SmallTest 376 @Test testFullMethodPath()377 public void testFullMethodPath() { 378 mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID; 379 mTestSessionManager.startSession(TEST_PARENT_NAME, null); 380 Session testSession = mTestSessionManager.createSubsession(); 381 mTestSessionManager.mCurrentThreadId = () -> TEST_CHILD_THREAD_ID; 382 mTestSessionManager.continueSession(testSession, TEST_CHILD_NAME); 383 384 String fullId = mTestSessionManager.getSessionId(); 385 386 assertTrue(fullId.contains(TEST_PARENT_NAME + Session.SUBSESSION_SEPARATION_CHAR 387 + TEST_CHILD_NAME)); 388 } 389 390 /** 391 * Make sure that the cleanup timer runs correctly and the GC collects the stale sessions 392 * correctly to ensure that there are no dangling sessions. 393 */ 394 @SmallTest 395 @Test testStaleSessionCleanupTimer()396 public void testStaleSessionCleanupTimer() { 397 mTestSessionManager.mCurrentThreadId = () -> TEST_PARENT_THREAD_ID; 398 mTestSessionManager.startSession(TEST_PARENT_NAME, null); 399 WeakReference<Session> sessionRef = new WeakReference<>( 400 mTestSessionManager.mSessionMapper.get(TEST_PARENT_THREAD_ID)); 401 try { 402 // Make sure that the sleep time is always > delay time. 403 Thread.sleep(2 * TEST_DELAY_TIME); 404 mTestSessionManager.cleanupStaleSessions(TEST_DELAY_TIME); 405 Runtime.getRuntime().gc(); 406 // Give it a second for GC to run. 407 Thread.sleep(1000); 408 } catch (InterruptedException ignored) {} 409 410 assertTrue(mTestSessionManager.mSessionMapper.isEmpty()); 411 assertNull(sessionRef.get()); 412 } 413 } 414