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